1414 * limitations under the License.
1515 */
1616
17- import { Message } from '@genkit-ai/ai' ;
17+ import { Message as GenkitMessage } from '@genkit-ai/ai' ;
1818import {
19+ GenerateResponseData ,
1920 GenerationCommonConfigSchema ,
2021 ModelAction ,
2122 defineModel ,
@@ -30,8 +31,21 @@ import {
3031} from '@genkit-ai/ai/model' ;
3132import Anthropic from '@anthropic-ai/sdk' ;
3233import z from 'zod' ;
34+ import {
35+ type ImageBlockParam ,
36+ type TextBlock ,
37+ type TextBlockParam ,
38+ type MessageCreateParams ,
39+ type Tool ,
40+ type ToolResultBlockParam ,
41+ type ContentBlock ,
42+ type Message ,
43+ type MessageParam ,
44+ type MessageStreamEvent ,
45+ type ToolUseBlockParam ,
46+ } from '@anthropic-ai/sdk/resources/messages.mjs' ;
3347
34- const AnthropicConfigSchema = GenerationCommonConfigSchema . extend ( {
48+ export const AnthropicConfigSchema = GenerationCommonConfigSchema . extend ( {
3549 tool_choice : z
3650 . union ( [
3751 z . object ( {
@@ -123,7 +137,7 @@ export const SUPPORTED_CLAUDE_MODELS: Record<
123137function toAnthropicRole (
124138 role : Role ,
125139 toolMessageType ?: 'tool_use' | 'tool_result'
126- ) : Anthropic . Beta . Tools . ToolsBetaMessageParam [ 'role' ] {
140+ ) : MessageParam [ 'role' ] {
127141 switch ( role ) {
128142 case 'user' :
129143 return 'user' ;
@@ -167,34 +181,41 @@ const extractDataFromBase64Url = (
167181 */
168182export function toAnthropicToolResponseContent (
169183 part : Part
170- ) : Anthropic . TextBlockParam | Anthropic . ImageBlockParam {
184+ ) : TextBlockParam | ImageBlockParam {
185+ if ( ! part . toolResponse ) {
186+ throw Error (
187+ `Invalid genkit part provided to toAnthropicToolResponseContent: ${ JSON . stringify (
188+ part
189+ ) } .`
190+ ) ;
191+ }
171192 const isMedia = isMediaObject ( part . toolResponse ?. output ) ;
172193 const isString = typeof part . toolResponse ?. output === 'string' ;
173- if ( ! isMedia && ! isString ) {
174- throw Error (
175- `Invalid genkit part provided to toAnthropicToolResponseContent: ${ part } .`
194+ let base64Data ;
195+ if ( isMedia ) {
196+ base64Data = extractDataFromBase64Url (
197+ ( part . toolResponse ?. output as Media ) . url
176198 ) ;
199+ } else if ( isString ) {
200+ base64Data = extractDataFromBase64Url ( part . toolResponse ?. output as string ) ;
177201 }
178- const base64Data = extractDataFromBase64Url (
179- isMedia
180- ? ( part . toolResponse ?. output as Media ) . url
181- : ( part . toolResponse ?. output as string )
182- ) ;
183- // @ts -expect-error TODO: improve these types
184202 return base64Data
185203 ? {
186204 type : 'image' ,
187205 source : {
188206 type : 'base64' ,
189207 data : base64Data . data ,
190208 media_type :
191- ( part . toolResponse ?. output as Media ) ?. contentType ??
209+ ( ( part . toolResponse ?. output as Media )
210+ ?. contentType as ImageBlockParam . Source [ 'media_type' ] ) ??
192211 base64Data . contentType ,
193212 } ,
194213 }
195214 : {
196215 type : 'text' ,
197- text : part . toolResponse ?. output as string ,
216+ text : isString
217+ ? ( part . toolResponse ?. output as string )
218+ : JSON . stringify ( part . toolResponse ?. output ) ,
198219 } ;
199220}
200221
@@ -206,11 +227,7 @@ export function toAnthropicToolResponseContent(
206227 */
207228export function toAnthropicMessageContent (
208229 part : Part
209- ) :
210- | Anthropic . TextBlock
211- | Anthropic . ImageBlockParam
212- | Anthropic . Beta . Tools . ToolUseBlockParam
213- | Anthropic . Beta . Tools . ToolResultBlockParam {
230+ ) : TextBlock | ImageBlockParam | ToolUseBlockParam | ToolResultBlockParam {
214231 if ( part . text ) {
215232 return {
216233 type : 'text' ,
@@ -262,20 +279,18 @@ export function toAnthropicMessageContent(
262279 */
263280export function toAnthropicMessages ( messages : MessageData [ ] ) : {
264281 system ?: string ;
265- messages : Anthropic . Beta . Tools . ToolsBetaMessageParam [ ] ;
282+ messages : MessageParam [ ] ;
266283} {
267284 const system =
268285 messages [ 0 ] ?. role === 'system' ? messages [ 0 ] . content ?. [ 0 ] ?. text : undefined ;
269286 const messagesToIterate = system ? messages . slice ( 1 ) : messages ;
270- const anthropicMsgs : Anthropic . Beta . Tools . ToolsBetaMessageParam [ ] = [ ] ;
287+ const anthropicMsgs : MessageParam [ ] = [ ] ;
271288 for ( const message of messagesToIterate ) {
272- const msg = new Message ( message ) ;
289+ const msg = new GenkitMessage ( message ) ;
273290 const content = msg . content . map ( toAnthropicMessageContent ) ;
274291 const toolMessageType = content . find (
275292 ( c ) => c . type === 'tool_use' || c . type === 'tool_result'
276- ) as
277- | Anthropic . Beta . Tools . ToolUseBlockParam
278- | Anthropic . Beta . Tools . ToolResultBlockParam ;
293+ ) as ToolUseBlockParam | ToolResultBlockParam ;
279294 const role = toAnthropicRole ( message . role , toolMessageType ?. type ) ;
280295 anthropicMsgs . push ( {
281296 role : role ,
@@ -290,19 +305,16 @@ export function toAnthropicMessages(messages: MessageData[]): {
290305 * @param tool The Genkit ToolDefinition to convert.
291306 * @returns The converted Anthropic Tool object.
292307 */
293- export function toAnthropicTool (
294- tool : ToolDefinition
295- ) : Anthropic . Beta . Tools . Tool {
308+ export function toAnthropicTool ( tool : ToolDefinition ) : Tool {
296309 return {
297310 name : tool . name ,
298311 description : tool . description ,
299- input_schema :
300- tool . inputSchema as Anthropic . Beta . Tools . Messages . Tool . InputSchema ,
312+ input_schema : tool . inputSchema as Tool . InputSchema ,
301313 } ;
302314}
303315
304316const finishReasonMap : Record <
305- NonNullable < Anthropic . Beta . Tools . ToolsBetaMessage [ 'stop_reason' ] > ,
317+ NonNullable < Message [ 'stop_reason' ] > ,
306318 CandidateData [ 'finishReason' ]
307319> = {
308320 end_turn : 'stop' ,
@@ -312,76 +324,88 @@ const finishReasonMap: Record<
312324} ;
313325
314326/**
315- * Converts an Anthropic content block to a Genkit CandidateData object.
316- * @param choice The Anthropic content block to convert.
317- * @param index The index of the content block.
318- * @param stopReason The reason the content block generation stopped.
319- * @returns The converted Genkit CandidateData object.
327+ * Converts an Anthropic content block to a Genkit Part object.
328+ * @param contentBlock The Anthropic content block to convert.
329+ * @returns The converted Genkit Part object.
320330 */
321- function fromAnthropicContentBlock (
322- choice : Anthropic . Beta . Tools . Messages . ToolsBetaContentBlock ,
323- index : number ,
324- stopReason : Anthropic . Beta . Tools . Messages . ToolsBetaMessage [ 'stop_reason' ]
325- ) : CandidateData {
326- return {
327- index,
328- finishReason : ( stopReason && finishReasonMap [ stopReason ] ) || 'other' ,
329- message :
330- choice . type === 'text'
331- ? {
332- role : 'model' ,
333- content : [ { text : choice . text } ] ,
334- }
335- : {
336- role : 'tool' ,
337- content : [
338- {
339- toolRequest : {
340- ref : choice . id ,
341- name : choice . name ,
342- input : choice . input ,
343- } ,
344- } ,
345- ] ,
346- } ,
347- } ;
331+ function fromAnthropicContentBlock ( contentBlock : ContentBlock ) : Part {
332+ return contentBlock . type === 'tool_use'
333+ ? {
334+ toolRequest : {
335+ ref : contentBlock . id ,
336+ name : contentBlock . name ,
337+ input : contentBlock . input ,
338+ } ,
339+ }
340+ : { text : contentBlock . text } ;
348341}
349342
350343/**
351- * Converts an Anthropic message stream event to a Genkit CandidateData object.
352- * @param choice The Anthropic message stream event to convert.
353- * @returns The converted Genkit CandidateData object if the event is a content block start or delta, otherwise undefined.
344+ * Converts an Anthropic message stream event to a Genkit Part object.
345+ * @param event The Anthropic message stream event to convert.
346+ * @returns The converted Genkit Part object if the event is a content block
347+ * start or delta, otherwise undefined.
354348 */
355349function fromAnthropicContentBlockChunk (
356- choice : Anthropic . Beta . Tools . Messages . ToolsBetaMessageStreamEvent
357- ) : CandidateData | undefined {
350+ event : MessageStreamEvent
351+ ) : Part | undefined {
358352 if (
359- choice . type !== 'content_block_start' &&
360- choice . type !== 'content_block_delta'
353+ event . type !== 'content_block_start' &&
354+ event . type !== 'content_block_delta'
361355 ) {
362356 return ;
363357 }
364- const choiceField =
365- choice . type === 'content_block_start' ? 'content_block' : 'delta' ;
358+ const eventField =
359+ event . type === 'content_block_start' ? 'content_block' : 'delta' ;
360+ return event [ eventField ] . type === 'text'
361+ ? {
362+ text : event [ eventField ] . text ,
363+ }
364+ : {
365+ toolRequest : {
366+ ref : event [ eventField ] . id ,
367+ name : event [ eventField ] . name ,
368+ input : event [ eventField ] . input ,
369+ } ,
370+ } ;
371+ }
372+
373+ function fromAnthropicStopReason (
374+ reason : Message [ 'stop_reason' ]
375+ ) : CandidateData [ 'finishReason' ] {
376+ switch ( reason ) {
377+ case 'max_tokens' :
378+ return 'length' ;
379+ case 'end_turn' :
380+ // fall through
381+ case 'stop_sequence' :
382+ // fall through
383+ case 'tool_use' :
384+ return 'stop' ;
385+ case null :
386+ return 'unknown' ;
387+ default :
388+ return 'other' ;
389+ }
390+ }
391+
392+ export function fromAnthropicResponse ( response : Message ) : GenerateResponseData {
366393 return {
367- index : choice . index ,
368- finishReason : 'unknown' ,
369- message : {
370- role : 'model' ,
371- content : [
372- choice [ choiceField ] . type === 'text'
373- ? {
374- text : choice [ choiceField ] . text ,
375- }
376- : {
377- toolRequest : {
378- ref : choice [ choiceField ] . id ,
379- name : choice [ choiceField ] . name ,
380- input : choice [ choiceField ] . input ,
381- } ,
382- } ,
383- ] ,
394+ candidates : [
395+ {
396+ index : 0 ,
397+ finishReason : fromAnthropicStopReason ( response . stop_reason ) ,
398+ message : {
399+ role : 'model' ,
400+ content : response . content . map ( fromAnthropicContentBlock ) ,
401+ } ,
402+ } ,
403+ ] ,
404+ usage : {
405+ inputTokens : response . usage . input_tokens ,
406+ outputTokens : response . usage . output_tokens ,
384407 } ,
408+ custom : response ,
385409 } ;
386410}
387411
@@ -397,12 +421,12 @@ export function toAnthropicRequestBody(
397421 modelName : string ,
398422 request : GenerateRequest < typeof AnthropicConfigSchema > ,
399423 stream ?: boolean
400- ) : Anthropic . Beta . Tools . Messages . MessageCreateParams {
424+ ) : MessageCreateParams {
401425 const model = SUPPORTED_CLAUDE_MODELS [ modelName ] ;
402426 if ( ! model ) throw new Error ( `Unsupported model: ${ modelName } ` ) ;
403427 const { system, messages } = toAnthropicMessages ( request . messages ) ;
404428 const mappedModelName = request . config ?. version || model . version || modelName ;
405- const body : Anthropic . Beta . Tools . MessageCreateParams = {
429+ const body : MessageCreateParams = {
406430 system,
407431 messages,
408432 tools : request . tools ?. map ( toAnthropicTool ) ,
@@ -451,35 +475,24 @@ export function claudeModel(
451475 configSchema : model . configSchema ,
452476 } ,
453477 async ( request , streamingCallback ) => {
454- let response : Anthropic . Beta . Tools . ToolsBetaMessage ;
478+ let response : Message ;
455479 const body = toAnthropicRequestBody ( name , request , ! ! streamingCallback ) ;
456480 if ( streamingCallback ) {
457- const stream = client . beta . tools . messages . stream ( body ) ;
481+ const stream = client . messages . stream ( body ) ;
458482 for await ( const chunk of stream ) {
459483 const c = fromAnthropicContentBlockChunk ( chunk ) ;
460484 if ( c ) {
461485 streamingCallback ( {
462- index : c . index ,
463- content : c . message . content ,
486+ index : 0 ,
487+ content : [ c ] ,
464488 } ) ;
465489 }
466490 }
467491 response = await stream . finalMessage ( ) ;
468492 } else {
469- response = ( await client . beta . tools . messages . create (
470- body
471- ) ) as Anthropic . Beta . Tools . ToolsBetaMessage ;
493+ response = ( await client . messages . create ( body ) ) as Message ;
472494 }
473- return {
474- candidates : response . content . map ( ( content , index ) =>
475- fromAnthropicContentBlock ( content , index , response . stop_reason )
476- ) ,
477- usage : {
478- inputTokens : response . usage . input_tokens ,
479- outputTokens : response . usage . output_tokens ,
480- } ,
481- custom : response ,
482- } ;
495+ return fromAnthropicResponse ( response ) ;
483496 }
484497 ) ;
485498}
0 commit comments