@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.
- package/LICENSE +21 -0
- package/dist/browser/index.d.ts +1104 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +7 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/loggers/index.cjs +1 -0
- package/dist/loggers/index.d.ts +151 -0
- package/dist/loggers/index.d.ts.map +1 -0
- package/dist/loggers/index.js +2 -0
- package/dist/loggers/index.js.map +1 -0
- package/dist/node/anthropic/index.cjs +1 -0
- package/dist/node/anthropic/index.d.ts +158 -0
- package/dist/node/anthropic/index.d.ts.map +1 -0
- package/dist/node/anthropic/index.js +1 -0
- package/dist/node/anthropic--1vgLC-e.js +5 -0
- package/dist/node/anthropic--1vgLC-e.js.map +1 -0
- package/dist/node/anthropic-BFQ6DSCP.cjs +4 -0
- package/dist/node/bytedance/index.cjs +1 -0
- package/dist/node/bytedance/index.d.ts +74 -0
- package/dist/node/bytedance/index.d.ts.map +1 -0
- package/dist/node/bytedance/index.js +1 -0
- package/dist/node/bytedance-C_0sF_pJ.js +2 -0
- package/dist/node/bytedance-C_0sF_pJ.js.map +1 -0
- package/dist/node/bytedance-DVPxqEiC.cjs +1 -0
- package/dist/node/chunk-Bmb41Sf3.cjs +1 -0
- package/dist/node/deepseek/index.cjs +1 -0
- package/dist/node/deepseek/index.d.ts +2 -0
- package/dist/node/deepseek/index.js +1 -0
- package/dist/node/deepseek-_8Ixx7rA.js +2 -0
- package/dist/node/deepseek-_8Ixx7rA.js.map +1 -0
- package/dist/node/deepseek-oA2Y6bD0.cjs +1 -0
- package/dist/node/gemini/index.cjs +1 -0
- package/dist/node/gemini/index.d.ts +173 -0
- package/dist/node/gemini/index.d.ts.map +1 -0
- package/dist/node/gemini/index.js +1 -0
- package/dist/node/gemini-Bh2U87MY.js +4 -0
- package/dist/node/gemini-Bh2U87MY.js.map +1 -0
- package/dist/node/gemini-DSaNCxZj.cjs +3 -0
- package/dist/node/gemma/index.cjs +1 -0
- package/dist/node/gemma/index.d.ts +2 -0
- package/dist/node/gemma/index.js +1 -0
- package/dist/node/gemma-Dp_AfCUR.js +2 -0
- package/dist/node/gemma-Dp_AfCUR.js.map +1 -0
- package/dist/node/gemma-G-Pf_PnX.cjs +1 -0
- package/dist/node/google/index.cjs +1 -0
- package/dist/node/google/index.d.ts +14 -0
- package/dist/node/google/index.d.ts.map +1 -0
- package/dist/node/google/index.js +2 -0
- package/dist/node/google/index.js.map +1 -0
- package/dist/node/index-B6PnlDMd.d.ts +82 -0
- package/dist/node/index-B6PnlDMd.d.ts.map +1 -0
- package/dist/node/index-B7UvPJcI.d.ts +315 -0
- package/dist/node/index-B7UvPJcI.d.ts.map +1 -0
- package/dist/node/index-BLPOTNb5.d.ts +98 -0
- package/dist/node/index-BLPOTNb5.d.ts.map +1 -0
- package/dist/node/index-BqixM_XD.d.ts +231 -0
- package/dist/node/index-BqixM_XD.d.ts.map +1 -0
- package/dist/node/index-C3beaqKO.d.ts +231 -0
- package/dist/node/index-C3beaqKO.d.ts.map +1 -0
- package/dist/node/index-Cp2XRh9G.d.ts +82 -0
- package/dist/node/index-Cp2XRh9G.d.ts.map +1 -0
- package/dist/node/index-DSv5xruI.d.ts +98 -0
- package/dist/node/index-DSv5xruI.d.ts.map +1 -0
- package/dist/node/index-w0bV1uaP.d.ts +315 -0
- package/dist/node/index-w0bV1uaP.d.ts.map +1 -0
- package/dist/node/index.cjs +1 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +1 -0
- package/dist/node/openai/index.cjs +1 -0
- package/dist/node/openai/index.d.ts +2 -0
- package/dist/node/openai/index.js +1 -0
- package/dist/node/openai-CRQjg4xF.js +2 -0
- package/dist/node/openai-CRQjg4xF.js.map +1 -0
- package/dist/node/openai-compatible-BYfyY5lb.cjs +1 -0
- package/dist/node/openai-compatible-Dm4Sof9e.js +2 -0
- package/dist/node/openai-compatible-Dm4Sof9e.js.map +1 -0
- package/dist/node/openai-xWC6pY7r.cjs +1 -0
- package/dist/node/qwen/index.cjs +1 -0
- package/dist/node/qwen/index.d.ts +2 -0
- package/dist/node/qwen/index.js +1 -0
- package/dist/node/qwen-ChUZobTL.js +2 -0
- package/dist/node/qwen-ChUZobTL.js.map +1 -0
- package/dist/node/qwen-CjT71vSM.cjs +1 -0
- package/package.json +157 -0
- package/src/anthropic/__tests__/abort-streaming.test.ts +199 -0
- package/src/anthropic/__tests__/model-catalog-refresh.test.ts +92 -0
- package/src/anthropic/__tests__/provider-definition.test.ts +55 -0
- package/src/anthropic/__tests__/provider.test.ts +1357 -0
- package/src/anthropic/__tests__/response-parser.test.ts +326 -0
- package/src/anthropic/index.ts +22 -0
- package/src/anthropic/message-converter.ts +181 -0
- package/src/anthropic/model-catalog-refresh.ts +128 -0
- package/src/anthropic/parsers/response-parser.ts +184 -0
- package/src/anthropic/provider-definition.ts +93 -0
- package/src/anthropic/provider.ts +290 -0
- package/src/anthropic/streaming-handler.ts +204 -0
- package/src/anthropic/types/api-types.ts +158 -0
- package/src/anthropic/types.ts +79 -0
- package/src/bytedance/http-client.test.ts +288 -0
- package/src/bytedance/http-client.ts +163 -0
- package/src/bytedance/index.ts +2 -0
- package/src/bytedance/provider.spec.ts +320 -0
- package/src/bytedance/provider.ts +171 -0
- package/src/bytedance/status-mapper.test.ts +299 -0
- package/src/bytedance/status-mapper.ts +141 -0
- package/src/bytedance/types.ts +68 -0
- package/src/deepseek/defaults.ts +4 -0
- package/src/deepseek/index.ts +22 -0
- package/src/deepseek/model-catalog-refresh.test.ts +57 -0
- package/src/deepseek/model-catalog-refresh.ts +105 -0
- package/src/deepseek/model-catalog.ts +55 -0
- package/src/deepseek/provider-definition.test.ts +109 -0
- package/src/deepseek/provider-definition.ts +132 -0
- package/src/deepseek/provider.test.ts +324 -0
- package/src/deepseek/provider.ts +298 -0
- package/src/deepseek/types.ts +37 -0
- package/src/gemini/execution-helpers.ts +233 -0
- package/src/gemini/genai-transport.test.ts +208 -0
- package/src/gemini/image-operations.test.ts +448 -0
- package/src/gemini/image-operations.ts +261 -0
- package/src/gemini/index.ts +11 -0
- package/src/gemini/message-converter.test.ts +616 -0
- package/src/gemini/message-converter.ts +140 -0
- package/src/gemini/model-catalog-refresh.test.ts +107 -0
- package/src/gemini/model-catalog-refresh.ts +92 -0
- package/src/gemini/provider-definition.test.ts +70 -0
- package/src/gemini/provider-definition.ts +78 -0
- package/src/gemini/provider-extended.test.ts +898 -0
- package/src/gemini/provider.spec.ts +216 -0
- package/src/gemini/provider.ts +279 -0
- package/src/gemini/request-converter.ts +226 -0
- package/src/gemini/tool-schema-converter.ts +78 -0
- package/src/gemini/types/api-types.ts +235 -0
- package/src/gemini/types.ts +121 -0
- package/src/gemma/index.ts +5 -0
- package/src/gemma/message-factory.ts +38 -0
- package/src/gemma/provider-definition.test.ts +43 -0
- package/src/gemma/provider-definition.ts +84 -0
- package/src/gemma/provider-projection.ts +49 -0
- package/src/gemma/provider.test.ts +628 -0
- package/src/gemma/provider.ts +308 -0
- package/src/gemma/pseudo-command-envelope.ts +58 -0
- package/src/gemma/pseudo-tool-call-projector.ts +243 -0
- package/src/gemma/pseudo-tool-call-tag-parser.ts +153 -0
- package/src/gemma/pseudo-tool-call-types.ts +31 -0
- package/src/gemma/reasoning-projector.test.ts +52 -0
- package/src/gemma/reasoning-projector.ts +144 -0
- package/src/gemma/streaming-projection.ts +79 -0
- package/src/gemma/tool-call-argument-parser.ts +126 -0
- package/src/gemma/tool-call-projector.test.ts +227 -0
- package/src/gemma/tool-call-projector.ts +264 -0
- package/src/gemma/types.ts +27 -0
- package/src/google/index.ts +11 -0
- package/src/google/provider-compat.test.ts +19 -0
- package/src/google/provider-definition.ts +6 -0
- package/src/google/provider.ts +10 -0
- package/src/google/types.ts +5 -0
- package/src/index.ts +9 -0
- package/src/openai/adapter.test.ts +494 -0
- package/src/openai/adapter.ts +145 -0
- package/src/openai/chat-completions-chat.ts +189 -0
- package/src/openai/executor-integration.test.ts +206 -0
- package/src/openai/index.ts +21 -0
- package/src/openai/interfaces/payload-logger.ts +48 -0
- package/src/openai/loggers/console-payload-logger.test.ts +173 -0
- package/src/openai/loggers/console-payload-logger.ts +94 -0
- package/src/openai/loggers/console.ts +9 -0
- package/src/openai/loggers/file-payload-logger.test.ts +238 -0
- package/src/openai/loggers/file-payload-logger.ts +112 -0
- package/src/openai/loggers/file.ts +9 -0
- package/src/openai/loggers/index.ts +12 -0
- package/src/openai/loggers/sanitize-openai-log-data.test.ts +89 -0
- package/src/openai/loggers/sanitize-openai-log-data.ts +14 -0
- package/src/openai/message-converter.ts +22 -0
- package/src/openai/model-catalog-refresh.test.ts +92 -0
- package/src/openai/model-catalog-refresh.ts +115 -0
- package/src/openai/openai-request-format.ts +92 -0
- package/src/openai/parsers/response-parser.test.ts +407 -0
- package/src/openai/parsers/response-parser.ts +47 -0
- package/src/openai/provider-definition.test.ts +75 -0
- package/src/openai/provider-definition.ts +132 -0
- package/src/openai/provider.test.ts +1402 -0
- package/src/openai/provider.ts +237 -0
- package/src/openai/responses-chat.ts +258 -0
- package/src/openai/responses-converter.ts +112 -0
- package/src/openai/responses-parser.ts +285 -0
- package/src/openai/responses-stream-utils.ts +45 -0
- package/src/openai/responses-types.ts +195 -0
- package/src/openai/streaming/stream-assembler.ts +3 -0
- package/src/openai/streaming/stream-handler.test.ts +367 -0
- package/src/openai/streaming/stream-handler.ts +119 -0
- package/src/openai/types/api-types.ts +112 -0
- package/src/openai/types.ts +194 -0
- package/src/qwen/defaults.ts +26 -0
- package/src/qwen/index.ts +5 -0
- package/src/qwen/model-catalog-refresh.test.ts +91 -0
- package/src/qwen/model-catalog-refresh.ts +97 -0
- package/src/qwen/provider-capabilities.ts +34 -0
- package/src/qwen/provider-definition.test.ts +139 -0
- package/src/qwen/provider-definition.ts +173 -0
- package/src/qwen/provider-streaming-assembly.ts +40 -0
- package/src/qwen/provider.test.ts +640 -0
- package/src/qwen/provider.ts +293 -0
- package/src/qwen/responses-chat.ts +194 -0
- package/src/qwen/responses-converter.ts +104 -0
- package/src/qwen/responses-parser.ts +299 -0
- package/src/qwen/responses-stream-utils.ts +38 -0
- package/src/qwen/types.ts +228 -0
- package/src/shared/openai-compatible/endpoint-probe.test.ts +52 -0
- package/src/shared/openai-compatible/endpoint-probe.ts +43 -0
- package/src/shared/openai-compatible/index.ts +6 -0
- package/src/shared/openai-compatible/message-converter.test.ts +111 -0
- package/src/shared/openai-compatible/message-converter.ts +84 -0
- package/src/shared/openai-compatible/native-payload-observer.test.ts +43 -0
- package/src/shared/openai-compatible/native-payload-observer.ts +26 -0
- package/src/shared/openai-compatible/response-parser.test.ts +172 -0
- package/src/shared/openai-compatible/response-parser.ts +180 -0
- package/src/shared/openai-compatible/stream-assembler.test.ts +266 -0
- package/src/shared/openai-compatible/stream-assembler.ts +248 -0
- 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
|
+
}
|