@robota-sdk/agent-provider 3.0.0-beta.64

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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/dist/browser/index.d.ts +1104 -0
  3. package/dist/browser/index.d.ts.map +1 -0
  4. package/dist/browser/index.js +7 -0
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/loggers/index.cjs +1 -0
  7. package/dist/loggers/index.d.ts +151 -0
  8. package/dist/loggers/index.d.ts.map +1 -0
  9. package/dist/loggers/index.js +2 -0
  10. package/dist/loggers/index.js.map +1 -0
  11. package/dist/node/anthropic/index.cjs +1 -0
  12. package/dist/node/anthropic/index.d.ts +158 -0
  13. package/dist/node/anthropic/index.d.ts.map +1 -0
  14. package/dist/node/anthropic/index.js +1 -0
  15. package/dist/node/anthropic--1vgLC-e.js +5 -0
  16. package/dist/node/anthropic--1vgLC-e.js.map +1 -0
  17. package/dist/node/anthropic-BFQ6DSCP.cjs +4 -0
  18. package/dist/node/bytedance/index.cjs +1 -0
  19. package/dist/node/bytedance/index.d.ts +74 -0
  20. package/dist/node/bytedance/index.d.ts.map +1 -0
  21. package/dist/node/bytedance/index.js +1 -0
  22. package/dist/node/bytedance-C_0sF_pJ.js +2 -0
  23. package/dist/node/bytedance-C_0sF_pJ.js.map +1 -0
  24. package/dist/node/bytedance-DVPxqEiC.cjs +1 -0
  25. package/dist/node/chunk-Bmb41Sf3.cjs +1 -0
  26. package/dist/node/deepseek/index.cjs +1 -0
  27. package/dist/node/deepseek/index.d.ts +2 -0
  28. package/dist/node/deepseek/index.js +1 -0
  29. package/dist/node/deepseek-_8Ixx7rA.js +2 -0
  30. package/dist/node/deepseek-_8Ixx7rA.js.map +1 -0
  31. package/dist/node/deepseek-oA2Y6bD0.cjs +1 -0
  32. package/dist/node/gemini/index.cjs +1 -0
  33. package/dist/node/gemini/index.d.ts +173 -0
  34. package/dist/node/gemini/index.d.ts.map +1 -0
  35. package/dist/node/gemini/index.js +1 -0
  36. package/dist/node/gemini-Bh2U87MY.js +4 -0
  37. package/dist/node/gemini-Bh2U87MY.js.map +1 -0
  38. package/dist/node/gemini-DSaNCxZj.cjs +3 -0
  39. package/dist/node/gemma/index.cjs +1 -0
  40. package/dist/node/gemma/index.d.ts +2 -0
  41. package/dist/node/gemma/index.js +1 -0
  42. package/dist/node/gemma-Dp_AfCUR.js +2 -0
  43. package/dist/node/gemma-Dp_AfCUR.js.map +1 -0
  44. package/dist/node/gemma-G-Pf_PnX.cjs +1 -0
  45. package/dist/node/google/index.cjs +1 -0
  46. package/dist/node/google/index.d.ts +14 -0
  47. package/dist/node/google/index.d.ts.map +1 -0
  48. package/dist/node/google/index.js +2 -0
  49. package/dist/node/google/index.js.map +1 -0
  50. package/dist/node/index-B6PnlDMd.d.ts +82 -0
  51. package/dist/node/index-B6PnlDMd.d.ts.map +1 -0
  52. package/dist/node/index-B7UvPJcI.d.ts +315 -0
  53. package/dist/node/index-B7UvPJcI.d.ts.map +1 -0
  54. package/dist/node/index-BLPOTNb5.d.ts +98 -0
  55. package/dist/node/index-BLPOTNb5.d.ts.map +1 -0
  56. package/dist/node/index-BqixM_XD.d.ts +231 -0
  57. package/dist/node/index-BqixM_XD.d.ts.map +1 -0
  58. package/dist/node/index-C3beaqKO.d.ts +231 -0
  59. package/dist/node/index-C3beaqKO.d.ts.map +1 -0
  60. package/dist/node/index-Cp2XRh9G.d.ts +82 -0
  61. package/dist/node/index-Cp2XRh9G.d.ts.map +1 -0
  62. package/dist/node/index-DSv5xruI.d.ts +98 -0
  63. package/dist/node/index-DSv5xruI.d.ts.map +1 -0
  64. package/dist/node/index-w0bV1uaP.d.ts +315 -0
  65. package/dist/node/index-w0bV1uaP.d.ts.map +1 -0
  66. package/dist/node/index.cjs +1 -0
  67. package/dist/node/index.d.ts +8 -0
  68. package/dist/node/index.js +1 -0
  69. package/dist/node/openai/index.cjs +1 -0
  70. package/dist/node/openai/index.d.ts +2 -0
  71. package/dist/node/openai/index.js +1 -0
  72. package/dist/node/openai-CRQjg4xF.js +2 -0
  73. package/dist/node/openai-CRQjg4xF.js.map +1 -0
  74. package/dist/node/openai-compatible-BYfyY5lb.cjs +1 -0
  75. package/dist/node/openai-compatible-Dm4Sof9e.js +2 -0
  76. package/dist/node/openai-compatible-Dm4Sof9e.js.map +1 -0
  77. package/dist/node/openai-xWC6pY7r.cjs +1 -0
  78. package/dist/node/qwen/index.cjs +1 -0
  79. package/dist/node/qwen/index.d.ts +2 -0
  80. package/dist/node/qwen/index.js +1 -0
  81. package/dist/node/qwen-ChUZobTL.js +2 -0
  82. package/dist/node/qwen-ChUZobTL.js.map +1 -0
  83. package/dist/node/qwen-CjT71vSM.cjs +1 -0
  84. package/package.json +157 -0
  85. package/src/anthropic/__tests__/abort-streaming.test.ts +199 -0
  86. package/src/anthropic/__tests__/model-catalog-refresh.test.ts +92 -0
  87. package/src/anthropic/__tests__/provider-definition.test.ts +55 -0
  88. package/src/anthropic/__tests__/provider.test.ts +1357 -0
  89. package/src/anthropic/__tests__/response-parser.test.ts +326 -0
  90. package/src/anthropic/index.ts +22 -0
  91. package/src/anthropic/message-converter.ts +181 -0
  92. package/src/anthropic/model-catalog-refresh.ts +128 -0
  93. package/src/anthropic/parsers/response-parser.ts +184 -0
  94. package/src/anthropic/provider-definition.ts +93 -0
  95. package/src/anthropic/provider.ts +290 -0
  96. package/src/anthropic/streaming-handler.ts +204 -0
  97. package/src/anthropic/types/api-types.ts +158 -0
  98. package/src/anthropic/types.ts +79 -0
  99. package/src/bytedance/http-client.test.ts +288 -0
  100. package/src/bytedance/http-client.ts +163 -0
  101. package/src/bytedance/index.ts +2 -0
  102. package/src/bytedance/provider.spec.ts +320 -0
  103. package/src/bytedance/provider.ts +171 -0
  104. package/src/bytedance/status-mapper.test.ts +299 -0
  105. package/src/bytedance/status-mapper.ts +141 -0
  106. package/src/bytedance/types.ts +68 -0
  107. package/src/deepseek/defaults.ts +4 -0
  108. package/src/deepseek/index.ts +22 -0
  109. package/src/deepseek/model-catalog-refresh.test.ts +57 -0
  110. package/src/deepseek/model-catalog-refresh.ts +105 -0
  111. package/src/deepseek/model-catalog.ts +55 -0
  112. package/src/deepseek/provider-definition.test.ts +109 -0
  113. package/src/deepseek/provider-definition.ts +132 -0
  114. package/src/deepseek/provider.test.ts +324 -0
  115. package/src/deepseek/provider.ts +298 -0
  116. package/src/deepseek/types.ts +37 -0
  117. package/src/gemini/execution-helpers.ts +233 -0
  118. package/src/gemini/genai-transport.test.ts +208 -0
  119. package/src/gemini/image-operations.test.ts +448 -0
  120. package/src/gemini/image-operations.ts +261 -0
  121. package/src/gemini/index.ts +11 -0
  122. package/src/gemini/message-converter.test.ts +616 -0
  123. package/src/gemini/message-converter.ts +140 -0
  124. package/src/gemini/model-catalog-refresh.test.ts +107 -0
  125. package/src/gemini/model-catalog-refresh.ts +92 -0
  126. package/src/gemini/provider-definition.test.ts +70 -0
  127. package/src/gemini/provider-definition.ts +78 -0
  128. package/src/gemini/provider-extended.test.ts +898 -0
  129. package/src/gemini/provider.spec.ts +216 -0
  130. package/src/gemini/provider.ts +279 -0
  131. package/src/gemini/request-converter.ts +226 -0
  132. package/src/gemini/tool-schema-converter.ts +78 -0
  133. package/src/gemini/types/api-types.ts +235 -0
  134. package/src/gemini/types.ts +121 -0
  135. package/src/gemma/index.ts +5 -0
  136. package/src/gemma/message-factory.ts +38 -0
  137. package/src/gemma/provider-definition.test.ts +43 -0
  138. package/src/gemma/provider-definition.ts +84 -0
  139. package/src/gemma/provider-projection.ts +49 -0
  140. package/src/gemma/provider.test.ts +628 -0
  141. package/src/gemma/provider.ts +308 -0
  142. package/src/gemma/pseudo-command-envelope.ts +58 -0
  143. package/src/gemma/pseudo-tool-call-projector.ts +243 -0
  144. package/src/gemma/pseudo-tool-call-tag-parser.ts +153 -0
  145. package/src/gemma/pseudo-tool-call-types.ts +31 -0
  146. package/src/gemma/reasoning-projector.test.ts +52 -0
  147. package/src/gemma/reasoning-projector.ts +144 -0
  148. package/src/gemma/streaming-projection.ts +79 -0
  149. package/src/gemma/tool-call-argument-parser.ts +126 -0
  150. package/src/gemma/tool-call-projector.test.ts +227 -0
  151. package/src/gemma/tool-call-projector.ts +264 -0
  152. package/src/gemma/types.ts +27 -0
  153. package/src/google/index.ts +11 -0
  154. package/src/google/provider-compat.test.ts +19 -0
  155. package/src/google/provider-definition.ts +6 -0
  156. package/src/google/provider.ts +10 -0
  157. package/src/google/types.ts +5 -0
  158. package/src/index.ts +9 -0
  159. package/src/openai/adapter.test.ts +494 -0
  160. package/src/openai/adapter.ts +145 -0
  161. package/src/openai/chat-completions-chat.ts +189 -0
  162. package/src/openai/executor-integration.test.ts +206 -0
  163. package/src/openai/index.ts +21 -0
  164. package/src/openai/interfaces/payload-logger.ts +48 -0
  165. package/src/openai/loggers/console-payload-logger.test.ts +173 -0
  166. package/src/openai/loggers/console-payload-logger.ts +94 -0
  167. package/src/openai/loggers/console.ts +9 -0
  168. package/src/openai/loggers/file-payload-logger.test.ts +238 -0
  169. package/src/openai/loggers/file-payload-logger.ts +112 -0
  170. package/src/openai/loggers/file.ts +9 -0
  171. package/src/openai/loggers/index.ts +12 -0
  172. package/src/openai/loggers/sanitize-openai-log-data.test.ts +89 -0
  173. package/src/openai/loggers/sanitize-openai-log-data.ts +14 -0
  174. package/src/openai/message-converter.ts +22 -0
  175. package/src/openai/model-catalog-refresh.test.ts +92 -0
  176. package/src/openai/model-catalog-refresh.ts +115 -0
  177. package/src/openai/openai-request-format.ts +92 -0
  178. package/src/openai/parsers/response-parser.test.ts +407 -0
  179. package/src/openai/parsers/response-parser.ts +47 -0
  180. package/src/openai/provider-definition.test.ts +75 -0
  181. package/src/openai/provider-definition.ts +132 -0
  182. package/src/openai/provider.test.ts +1402 -0
  183. package/src/openai/provider.ts +237 -0
  184. package/src/openai/responses-chat.ts +258 -0
  185. package/src/openai/responses-converter.ts +112 -0
  186. package/src/openai/responses-parser.ts +285 -0
  187. package/src/openai/responses-stream-utils.ts +45 -0
  188. package/src/openai/responses-types.ts +195 -0
  189. package/src/openai/streaming/stream-assembler.ts +3 -0
  190. package/src/openai/streaming/stream-handler.test.ts +367 -0
  191. package/src/openai/streaming/stream-handler.ts +119 -0
  192. package/src/openai/types/api-types.ts +112 -0
  193. package/src/openai/types.ts +194 -0
  194. package/src/qwen/defaults.ts +26 -0
  195. package/src/qwen/index.ts +5 -0
  196. package/src/qwen/model-catalog-refresh.test.ts +91 -0
  197. package/src/qwen/model-catalog-refresh.ts +97 -0
  198. package/src/qwen/provider-capabilities.ts +34 -0
  199. package/src/qwen/provider-definition.test.ts +139 -0
  200. package/src/qwen/provider-definition.ts +173 -0
  201. package/src/qwen/provider-streaming-assembly.ts +40 -0
  202. package/src/qwen/provider.test.ts +640 -0
  203. package/src/qwen/provider.ts +293 -0
  204. package/src/qwen/responses-chat.ts +194 -0
  205. package/src/qwen/responses-converter.ts +104 -0
  206. package/src/qwen/responses-parser.ts +299 -0
  207. package/src/qwen/responses-stream-utils.ts +38 -0
  208. package/src/qwen/types.ts +228 -0
  209. package/src/shared/openai-compatible/endpoint-probe.test.ts +52 -0
  210. package/src/shared/openai-compatible/endpoint-probe.ts +43 -0
  211. package/src/shared/openai-compatible/index.ts +6 -0
  212. package/src/shared/openai-compatible/message-converter.test.ts +111 -0
  213. package/src/shared/openai-compatible/message-converter.ts +84 -0
  214. package/src/shared/openai-compatible/native-payload-observer.test.ts +43 -0
  215. package/src/shared/openai-compatible/native-payload-observer.ts +26 -0
  216. package/src/shared/openai-compatible/response-parser.test.ts +172 -0
  217. package/src/shared/openai-compatible/response-parser.ts +180 -0
  218. package/src/shared/openai-compatible/stream-assembler.test.ts +266 -0
  219. package/src/shared/openai-compatible/stream-assembler.ts +248 -0
  220. package/src/shared/openai-compatible/types.ts +59 -0
@@ -0,0 +1,184 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { TUniversalMessage, logger } from '@robota-sdk/agent-core';
3
+ import type Anthropic from '@anthropic-ai/sdk';
4
+ import type { IAnthropicMessage } from '../types/api-types';
5
+
6
+ /**
7
+ * Anthropic response parser utility
8
+ *
9
+ * Handles parsing of responses from Anthropic API into standardized formats.
10
+ * Extracts parsing logic from the main provider for better modularity.
11
+ */
12
+ export class AnthropicResponseParser {
13
+ /**
14
+ * Parse complete Anthropic message response
15
+ *
16
+ * @param response - Raw Anthropic API response
17
+ * @returns Standardized universal message
18
+ */
19
+ static parseResponse(response: IAnthropicMessage): TUniversalMessage {
20
+ try {
21
+ const content = response.content?.[0]?.text || '';
22
+
23
+ // Parse tool calls if present
24
+ const toolUseBlocks = response.content?.filter((block) => block.type === 'tool_use') || [];
25
+ const toolCalls = toolUseBlocks
26
+ .filter((toolBlock) => toolBlock.id && toolBlock.name)
27
+ .map((toolBlock) => ({
28
+ id: toolBlock.id!,
29
+ type: 'function' as const,
30
+ function: {
31
+ name: toolBlock.name!,
32
+ arguments: JSON.stringify(toolBlock.input || {}),
33
+ },
34
+ }));
35
+
36
+ // Calculate token usage
37
+ const usage = response.usage
38
+ ? {
39
+ promptTokens: response.usage.input_tokens,
40
+ completionTokens: response.usage.output_tokens,
41
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens,
42
+ }
43
+ : undefined;
44
+
45
+ const result: TUniversalMessage = {
46
+ id: randomUUID(),
47
+ state: 'complete' as const,
48
+ role: 'assistant',
49
+ content: toolCalls.length > 0 ? null : content,
50
+ timestamp: new Date(),
51
+ ...(toolCalls.length > 0 && { toolCalls }),
52
+ ...(usage && { usage }),
53
+ metadata: {
54
+ model: response.model,
55
+ finishReason: response.stop_reason || 'unknown',
56
+ },
57
+ };
58
+
59
+ return result;
60
+ } catch (error) {
61
+ const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
62
+ logger.error('Error parsing Anthropic response:', { message: errorMessage });
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Parse Anthropic streaming chunk
69
+ *
70
+ * @param chunk - Raw streaming chunk from Anthropic API
71
+ * @returns Parsed universal message or null if no content
72
+ */
73
+ static parseStreamingChunk(chunk: Anthropic.MessageStreamEvent): TUniversalMessage | null {
74
+ try {
75
+ // Handle different chunk types
76
+ switch (chunk.type) {
77
+ case 'content_block_start':
78
+ if (chunk.content_block?.type === 'text') {
79
+ return {
80
+ id: randomUUID(),
81
+ state: 'complete' as const,
82
+ role: 'assistant',
83
+ content: '',
84
+ timestamp: new Date(),
85
+ metadata: {
86
+ isStreamChunk: true,
87
+ isComplete: false,
88
+ },
89
+ };
90
+ }
91
+ if (chunk.content_block?.type === 'tool_use') {
92
+ return {
93
+ id: randomUUID(),
94
+ state: 'complete' as const,
95
+ role: 'assistant',
96
+ content: null,
97
+ timestamp: new Date(),
98
+ toolCalls: [
99
+ {
100
+ id: chunk.content_block.id,
101
+ type: 'function' as const,
102
+ function: {
103
+ name: chunk.content_block.name || '',
104
+ arguments: JSON.stringify(chunk.content_block.input || {}),
105
+ },
106
+ },
107
+ ],
108
+ metadata: {
109
+ isStreamChunk: true,
110
+ isComplete: false,
111
+ },
112
+ };
113
+ }
114
+ break;
115
+
116
+ case 'content_block_delta':
117
+ if (chunk.delta?.type === 'text_delta') {
118
+ return {
119
+ id: randomUUID(),
120
+ state: 'complete' as const,
121
+ role: 'assistant',
122
+ content: chunk.delta.text || '',
123
+ timestamp: new Date(),
124
+ metadata: {
125
+ isStreamChunk: true,
126
+ isComplete: false,
127
+ },
128
+ };
129
+ }
130
+ if (chunk.delta?.type === 'input_json_delta') {
131
+ // Handle tool call argument streaming
132
+ return {
133
+ id: randomUUID(),
134
+ state: 'complete' as const,
135
+ role: 'assistant',
136
+ content: null,
137
+ timestamp: new Date(),
138
+ metadata: {
139
+ isStreamChunk: true,
140
+ isComplete: false,
141
+ },
142
+ };
143
+ }
144
+ break;
145
+
146
+ case 'content_block_stop':
147
+ return {
148
+ id: randomUUID(),
149
+ state: 'complete' as const,
150
+ role: 'assistant',
151
+ content: '',
152
+ timestamp: new Date(),
153
+ metadata: {
154
+ isStreamChunk: true,
155
+ isComplete: false,
156
+ },
157
+ };
158
+
159
+ case 'message_stop':
160
+ return {
161
+ id: randomUUID(),
162
+ state: 'complete' as const,
163
+ role: 'assistant',
164
+ content: '',
165
+ timestamp: new Date(),
166
+ metadata: {
167
+ isStreamChunk: true,
168
+ isComplete: true,
169
+ },
170
+ };
171
+
172
+ default:
173
+ // Unknown chunk type, skip
174
+ break;
175
+ }
176
+
177
+ return null;
178
+ } catch (error) {
179
+ const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
180
+ logger.error('Error parsing Anthropic streaming chunk:', { message: errorMessage });
181
+ return null;
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,93 @@
1
+ import {
2
+ CLAUDE_MODELS,
3
+ type IProviderDefinition,
4
+ type IProviderModelCatalogEntry,
5
+ } from '@robota-sdk/agent-core';
6
+ import { AnthropicProvider } from './provider';
7
+ import { refreshAnthropicModelCatalog } from './model-catalog-refresh';
8
+
9
+ export const DEFAULT_ANTHROPIC_PROVIDER_MODEL = 'claude-sonnet-4-6';
10
+ export const DEFAULT_ANTHROPIC_PROVIDER_API_KEY_ENV = 'ANTHROPIC_API_KEY';
11
+ export const DEFAULT_ANTHROPIC_PROVIDER_API_KEY_REFERENCE = `$ENV:${DEFAULT_ANTHROPIC_PROVIDER_API_KEY_ENV}`;
12
+ export const ANTHROPIC_MODEL_SOURCE_URL = 'https://platform.claude.com/docs/en/api/models/list';
13
+ export const ANTHROPIC_MODEL_LAST_VERIFIED_AT = '2026-05-04';
14
+ const ANTHROPIC_API_KEY_URL = 'https://platform.claude.com/settings/keys';
15
+ const ANTHROPIC_SETUP_SOURCE_URL = 'https://platform.claude.com/docs/en/api/overview';
16
+ const ANTHROPIC_SETUP_LAST_VERIFIED_AT = '2026-05-08';
17
+ const ANTHROPIC_SETUP_HELP_LINKS: NonNullable<IProviderDefinition['setupHelpLinks']> = [
18
+ {
19
+ kind: 'api-key',
20
+ label: 'Anthropic API keys',
21
+ url: ANTHROPIC_API_KEY_URL,
22
+ sourceUrl: ANTHROPIC_SETUP_SOURCE_URL,
23
+ lastVerifiedAt: ANTHROPIC_SETUP_LAST_VERIFIED_AT,
24
+ },
25
+ ];
26
+
27
+ export function createAnthropicProviderDefinition(): IProviderDefinition {
28
+ return {
29
+ type: 'anthropic',
30
+ displayName: 'Anthropic',
31
+ description: 'Claude models through Anthropic API',
32
+ defaults: {
33
+ model: DEFAULT_ANTHROPIC_PROVIDER_MODEL,
34
+ apiKey: DEFAULT_ANTHROPIC_PROVIDER_API_KEY_REFERENCE,
35
+ },
36
+ modelCatalog: {
37
+ status: 'fallback',
38
+ sourceUrl: ANTHROPIC_MODEL_SOURCE_URL,
39
+ lastVerifiedAt: ANTHROPIC_MODEL_LAST_VERIFIED_AT,
40
+ entries: buildAnthropicModelCatalogEntries(),
41
+ },
42
+ setupHelpLinks: ANTHROPIC_SETUP_HELP_LINKS,
43
+ setupSteps: [
44
+ {
45
+ key: 'apiKey',
46
+ title: 'Anthropic API key',
47
+ defaultValue: DEFAULT_ANTHROPIC_PROVIDER_API_KEY_REFERENCE,
48
+ masked: true,
49
+ },
50
+ {
51
+ key: 'model',
52
+ title: 'Anthropic model',
53
+ defaultValue: DEFAULT_ANTHROPIC_PROVIDER_MODEL,
54
+ },
55
+ ],
56
+ refreshModelCatalog: ({ profile }) => refreshAnthropicModelCatalog(profile),
57
+ modelCatalogCacheTtlSeconds: 86400,
58
+ requiresApiKey: true,
59
+ createProvider: (config) =>
60
+ new AnthropicProvider({
61
+ apiKey: requireAnthropicApiKey(config.apiKey),
62
+ ...(config.baseURL !== undefined && { baseURL: config.baseURL }),
63
+ ...(config.timeout !== undefined && { timeout: config.timeout }),
64
+ defaultModel: config.model,
65
+ }),
66
+ };
67
+ }
68
+
69
+ function buildAnthropicModelCatalogEntries(): IProviderModelCatalogEntry[] {
70
+ const seen = new Set<string>();
71
+ const entries: IProviderModelCatalogEntry[] = [];
72
+ for (const model of Object.values(CLAUDE_MODELS)) {
73
+ if (seen.has(model.name)) continue;
74
+ seen.add(model.name);
75
+ entries.push({
76
+ id: model.id,
77
+ displayName: model.name,
78
+ contextWindow: model.contextWindow,
79
+ capabilities: ['tools', 'vision', 'json_schema', 'reasoning', 'streaming'],
80
+ lifecycle: 'active',
81
+ sourceUrl: ANTHROPIC_MODEL_SOURCE_URL,
82
+ lastVerifiedAt: ANTHROPIC_MODEL_LAST_VERIFIED_AT,
83
+ });
84
+ }
85
+ return entries;
86
+ }
87
+
88
+ function requireAnthropicApiKey(apiKey: string | undefined): string {
89
+ if (!apiKey) {
90
+ throw new Error('Provider anthropic requires apiKey');
91
+ }
92
+ return apiKey;
93
+ }
@@ -0,0 +1,290 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import Anthropic from '@anthropic-ai/sdk';
3
+ import type { IAnthropicProviderOptions } from './types';
4
+ import { AbstractAIProvider, getModelMaxOutput } from '@robota-sdk/agent-core';
5
+ import type {
6
+ IProviderCapabilities,
7
+ IProviderNativeWebToolRequest,
8
+ TUniversalMessage,
9
+ IChatOptions,
10
+ TTextDeltaCallback,
11
+ } from '@robota-sdk/agent-core';
12
+ import { convertToAnthropicFormat, convertToolsToAnthropicFormat } from './message-converter';
13
+ import { streamAndAssemble } from './streaming-handler';
14
+
15
+ /**
16
+ * Anthropic provider implementation for Robota
17
+ *
18
+ * IMPORTANT PROVIDER-SPECIFIC RULES:
19
+ * 1. This provider MUST extend BaseAIProvider from @robota-sdk/agent-core
20
+ * 2. Content handling for Anthropic API:
21
+ * - When tool_calls are present: content MUST be null (not empty string)
22
+ * - For regular assistant messages: content should be a string
23
+ * 3. Use override keyword for all methods inherited from BaseAIProvider
24
+ * 4. Provider-specific API behavior should be documented here
25
+ *
26
+ * @public
27
+ */
28
+ export class AnthropicProvider extends AbstractAIProvider {
29
+ override readonly name = 'anthropic';
30
+ override readonly version = '1.0.0';
31
+
32
+ private readonly client?: Anthropic;
33
+ private readonly options: IAnthropicProviderOptions;
34
+
35
+ /**
36
+ * When true, Anthropic server tools (web_search) are included in every request.
37
+ * The server executes these tools internally — no local tool registration needed.
38
+ */
39
+ enableWebTools = false;
40
+
41
+ /**
42
+ * Optional callback for text deltas during streaming.
43
+ * Set by the consumer (e.g., Session) to receive real-time text chunks.
44
+ * When set, chat() uses streaming internally while still returning
45
+ * the complete assembled message.
46
+ */
47
+ onTextDelta?: TTextDeltaCallback;
48
+
49
+ /** Callback when a server tool (web_search etc.) is invoked by the API */
50
+ onServerToolUse?: (toolName: string, input: Record<string, string>) => void;
51
+
52
+ constructor(options: IAnthropicProviderOptions) {
53
+ super();
54
+ this.options = options;
55
+
56
+ // Set executor if provided
57
+ if (options.executor) {
58
+ this.executor = options.executor;
59
+ }
60
+
61
+ // Only create client if not using executor
62
+ if (!this.executor) {
63
+ // Create client from apiKey if not provided.
64
+ if (options.client) {
65
+ this.client = options.client;
66
+ } else if (options.apiKey) {
67
+ this.client = new Anthropic({
68
+ apiKey: options.apiKey,
69
+ ...(options.timeout && { timeout: options.timeout }),
70
+ ...(options.baseURL && { baseURL: options.baseURL }),
71
+ });
72
+ } else {
73
+ throw new Error('Either Anthropic client, apiKey, or executor is required');
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Generate response using TUniversalMessage
80
+ */
81
+ override async chat(
82
+ messages: TUniversalMessage[],
83
+ options?: IChatOptions,
84
+ ): Promise<TUniversalMessage> {
85
+ this.validateMessages(messages);
86
+ this.validateNativeWebTools(options?.nativeWebTools);
87
+
88
+ // Use executor when configured; otherwise use direct execution
89
+ if (this.executor) {
90
+ try {
91
+ return await this.executeViaExecutorOrDirect(messages, options);
92
+ } catch (error) {
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ // Direct execution with Anthropic client
98
+ if (!this.client) {
99
+ throw new Error(
100
+ 'Anthropic client not available. Either provide a client/apiKey or use an executor.',
101
+ );
102
+ }
103
+
104
+ // Separate system messages for the Anthropic system parameter
105
+ const systemMessages = messages.filter((m) => m.role === 'system');
106
+ const nonSystemMessages = messages.filter((m) => m.role !== 'system');
107
+ const anthropicMessages = convertToAnthropicFormat(nonSystemMessages);
108
+ const systemPrompt = systemMessages.map((m) => m.content || '').join('\n\n') || undefined;
109
+
110
+ if (!options?.model) {
111
+ throw new Error(
112
+ 'Model is required in chat options. Please specify a model in defaultModel configuration.',
113
+ );
114
+ }
115
+
116
+ const functionTools = options?.tools ? convertToolsToAnthropicFormat(options.tools) : [];
117
+ const serverTools: Anthropic.Messages.ToolUnion[] = this.enableWebTools
118
+ ? [{ type: 'web_search_20250305' as const, name: 'web_search' }]
119
+ : [];
120
+ const allTools: Anthropic.Messages.ToolUnion[] = [...functionTools, ...serverTools];
121
+
122
+ const baseParams: Anthropic.MessageCreateParamsNonStreaming = {
123
+ model: options.model as string,
124
+ messages: anthropicMessages,
125
+ max_tokens: options?.maxTokens || getModelMaxOutput(options.model as string),
126
+ ...(systemPrompt && { system: systemPrompt }),
127
+ ...(options?.temperature !== undefined && { temperature: options.temperature }),
128
+ ...(allTools.length > 0 && { tools: allTools }),
129
+ };
130
+
131
+ // Always use streaming to avoid Anthropic SDK's 10-minute non-streaming timeout.
132
+ // When no onTextDelta callback is available, use a no-op to silently assemble the response.
133
+ const textDeltaCb = options?.onTextDelta ?? this.onTextDelta ?? (() => {});
134
+ return streamAndAssemble(
135
+ this.client,
136
+ baseParams,
137
+ textDeltaCb,
138
+ this.onServerToolUse,
139
+ options?.signal,
140
+ options?.onProviderNativeRawPayload,
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Generate streaming response using TUniversalMessage
146
+ */
147
+ override async *chatStream(
148
+ messages: TUniversalMessage[],
149
+ options?: IChatOptions,
150
+ ): AsyncIterable<TUniversalMessage> {
151
+ this.validateMessages(messages);
152
+ this.validateNativeWebTools(options?.nativeWebTools);
153
+
154
+ // Use executor when configured; otherwise use direct execution
155
+ if (this.executor) {
156
+ try {
157
+ yield* this.executeStreamViaExecutorOrDirect(messages, options);
158
+ return;
159
+ } catch (error) {
160
+ throw error;
161
+ }
162
+ }
163
+
164
+ // Direct execution with Anthropic client
165
+ if (!this.client) {
166
+ throw new Error(
167
+ 'Anthropic client not available. Either provide a client/apiKey or use an executor.',
168
+ );
169
+ }
170
+
171
+ const anthropicMessages = convertToAnthropicFormat(messages);
172
+
173
+ if (!options?.model) {
174
+ throw new Error(
175
+ 'Model is required in chat options. Please specify a model in defaultModel configuration.',
176
+ );
177
+ }
178
+
179
+ const requestParams: Anthropic.MessageCreateParamsStreaming = {
180
+ model: options.model as string,
181
+ messages: anthropicMessages,
182
+ max_tokens: options?.maxTokens || getModelMaxOutput(options.model as string),
183
+ stream: true,
184
+ };
185
+
186
+ if (options?.temperature !== undefined) {
187
+ requestParams.temperature = options.temperature;
188
+ }
189
+
190
+ const functionTools = options?.tools ? convertToolsToAnthropicFormat(options.tools) : [];
191
+ const serverTools: Anthropic.Messages.ToolUnion[] = this.enableWebTools
192
+ ? [{ type: 'web_search_20250305' as const, name: 'web_search' }]
193
+ : [];
194
+ const allTools: Anthropic.Messages.ToolUnion[] = [...functionTools, ...serverTools];
195
+
196
+ if (allTools.length > 0) {
197
+ requestParams.tools = allTools;
198
+ }
199
+
200
+ options?.onProviderNativeRawPayload?.({
201
+ provider: 'anthropic',
202
+ apiSurface: 'anthropic-messages',
203
+ payloadKind: 'request',
204
+ payload: requestParams,
205
+ });
206
+ const stream = await this.client.messages.create(requestParams);
207
+
208
+ let sequence = 0;
209
+ for await (const chunk of stream) {
210
+ options?.onProviderNativeRawPayload?.({
211
+ provider: 'anthropic',
212
+ apiSurface: 'anthropic-messages',
213
+ payloadKind: 'stream_event',
214
+ sequence,
215
+ payload: chunk,
216
+ });
217
+ sequence++;
218
+ if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
219
+ yield {
220
+ id: randomUUID(),
221
+ role: 'assistant',
222
+ content: chunk.delta.text,
223
+ state: 'complete' as const,
224
+ timestamp: new Date(),
225
+ };
226
+ }
227
+ }
228
+ }
229
+
230
+ override supportsTools(): boolean {
231
+ return true;
232
+ }
233
+
234
+ override getCapabilities(): IProviderCapabilities {
235
+ return {
236
+ functionCalling: { supported: true },
237
+ nativeWebTools: {
238
+ webSearch: this.enableWebTools
239
+ ? { supported: true, enabled: true, source: 'anthropic-messages' }
240
+ : {
241
+ supported: true,
242
+ enabled: false,
243
+ source: 'anthropic-messages',
244
+ reason: 'Call configureNativeWebTools({ webSearch: true }) or set enableWebTools.',
245
+ },
246
+ webFetch: {
247
+ supported: false,
248
+ enabled: false,
249
+ source: 'anthropic-messages',
250
+ reason: 'Anthropic provider exposes server web search only.',
251
+ },
252
+ },
253
+ };
254
+ }
255
+
256
+ configureNativeWebTools(request: IProviderNativeWebToolRequest): IProviderCapabilities {
257
+ if (request.webSearch === true) {
258
+ this.enableWebTools = true;
259
+ }
260
+ this.validateNativeWebTools(request);
261
+ return this.getCapabilities();
262
+ }
263
+
264
+ override validateConfig(): boolean {
265
+ return !!this.client && !!this.options && !!this.options.apiKey;
266
+ }
267
+
268
+ override async dispose(): Promise<void> {
269
+ // Anthropic client doesn't need explicit cleanup
270
+ }
271
+
272
+ /**
273
+ * Validate TUniversalMessage array
274
+ */
275
+ protected override validateMessages(messages: TUniversalMessage[]): void {
276
+ if (!Array.isArray(messages)) {
277
+ throw new Error('Messages must be an array');
278
+ }
279
+
280
+ if (messages.length === 0) {
281
+ throw new Error('Messages array cannot be empty');
282
+ }
283
+
284
+ for (const message of messages) {
285
+ if (!message.role || !['user', 'assistant', 'system', 'tool'].includes(message.role)) {
286
+ throw new Error(`Invalid message role: ${message.role}`);
287
+ }
288
+ }
289
+ }
290
+ }