1+ import { AnthropicHandler } from '../anthropic' ;
2+ import { ApiHandlerOptions } from '../../../shared/api' ;
3+ import { ApiStream } from '../../transform/stream' ;
4+ import { Anthropic } from '@anthropic-ai/sdk' ;
5+
6+ // Mock Anthropic client
7+ const mockBetaCreate = jest . fn ( ) ;
8+ const mockCreate = jest . fn ( ) ;
9+ jest . mock ( '@anthropic-ai/sdk' , ( ) => {
10+ return {
11+ Anthropic : jest . fn ( ) . mockImplementation ( ( ) => ( {
12+ beta : {
13+ promptCaching : {
14+ messages : {
15+ create : mockBetaCreate . mockImplementation ( async ( ) => ( {
16+ async * [ Symbol . asyncIterator ] ( ) {
17+ yield {
18+ type : 'message_start' ,
19+ message : {
20+ usage : {
21+ input_tokens : 100 ,
22+ output_tokens : 50 ,
23+ cache_creation_input_tokens : 20 ,
24+ cache_read_input_tokens : 10
25+ }
26+ }
27+ } ;
28+ yield {
29+ type : 'content_block_start' ,
30+ index : 0 ,
31+ content_block : {
32+ type : 'text' ,
33+ text : 'Hello'
34+ }
35+ } ;
36+ yield {
37+ type : 'content_block_delta' ,
38+ delta : {
39+ type : 'text_delta' ,
40+ text : ' world'
41+ }
42+ } ;
43+ }
44+ } ) )
45+ }
46+ }
47+ } ,
48+ messages : {
49+ create : mockCreate
50+ }
51+ } ) )
52+ } ;
53+ } ) ;
54+
55+ describe ( 'AnthropicHandler' , ( ) => {
56+ let handler : AnthropicHandler ;
57+ let mockOptions : ApiHandlerOptions ;
58+
59+ beforeEach ( ( ) => {
60+ mockOptions = {
61+ apiKey : 'test-api-key' ,
62+ apiModelId : 'claude-3-5-sonnet-20241022'
63+ } ;
64+ handler = new AnthropicHandler ( mockOptions ) ;
65+ mockBetaCreate . mockClear ( ) ;
66+ mockCreate . mockClear ( ) ;
67+ } ) ;
68+
69+ describe ( 'constructor' , ( ) => {
70+ it ( 'should initialize with provided options' , ( ) => {
71+ expect ( handler ) . toBeInstanceOf ( AnthropicHandler ) ;
72+ expect ( handler . getModel ( ) . id ) . toBe ( mockOptions . apiModelId ) ;
73+ } ) ;
74+
75+ it ( 'should initialize with undefined API key' , ( ) => {
76+ // The SDK will handle API key validation, so we just verify it initializes
77+ const handlerWithoutKey = new AnthropicHandler ( {
78+ ...mockOptions ,
79+ apiKey : undefined
80+ } ) ;
81+ expect ( handlerWithoutKey ) . toBeInstanceOf ( AnthropicHandler ) ;
82+ } ) ;
83+
84+ it ( 'should use custom base URL if provided' , ( ) => {
85+ const customBaseUrl = 'https://custom.anthropic.com' ;
86+ const handlerWithCustomUrl = new AnthropicHandler ( {
87+ ...mockOptions ,
88+ anthropicBaseUrl : customBaseUrl
89+ } ) ;
90+ expect ( handlerWithCustomUrl ) . toBeInstanceOf ( AnthropicHandler ) ;
91+ } ) ;
92+ } ) ;
93+
94+ describe ( 'createMessage' , ( ) => {
95+ const systemPrompt = 'You are a helpful assistant.' ;
96+ const messages : Anthropic . Messages . MessageParam [ ] = [
97+ {
98+ role : 'user' ,
99+ content : [ {
100+ type : 'text' as const ,
101+ text : 'Hello!'
102+ } ]
103+ }
104+ ] ;
105+
106+ it ( 'should handle prompt caching for supported models' , async ( ) => {
107+ const stream = handler . createMessage ( systemPrompt , [
108+ {
109+ role : 'user' ,
110+ content : [ { type : 'text' as const , text : 'First message' } ]
111+ } ,
112+ {
113+ role : 'assistant' ,
114+ content : [ { type : 'text' as const , text : 'Response' } ]
115+ } ,
116+ {
117+ role : 'user' ,
118+ content : [ { type : 'text' as const , text : 'Second message' } ]
119+ }
120+ ] ) ;
121+
122+ const chunks : any [ ] = [ ] ;
123+ for await ( const chunk of stream ) {
124+ chunks . push ( chunk ) ;
125+ }
126+
127+ // Verify usage information
128+ const usageChunk = chunks . find ( chunk => chunk . type === 'usage' ) ;
129+ expect ( usageChunk ) . toBeDefined ( ) ;
130+ expect ( usageChunk ?. inputTokens ) . toBe ( 100 ) ;
131+ expect ( usageChunk ?. outputTokens ) . toBe ( 50 ) ;
132+ expect ( usageChunk ?. cacheWriteTokens ) . toBe ( 20 ) ;
133+ expect ( usageChunk ?. cacheReadTokens ) . toBe ( 10 ) ;
134+
135+ // Verify text content
136+ const textChunks = chunks . filter ( chunk => chunk . type === 'text' ) ;
137+ expect ( textChunks ) . toHaveLength ( 2 ) ;
138+ expect ( textChunks [ 0 ] . text ) . toBe ( 'Hello' ) ;
139+ expect ( textChunks [ 1 ] . text ) . toBe ( ' world' ) ;
140+
141+ // Verify beta API was used
142+ expect ( mockBetaCreate ) . toHaveBeenCalled ( ) ;
143+ expect ( mockCreate ) . not . toHaveBeenCalled ( ) ;
144+ } ) ;
145+ } ) ;
146+
147+ describe ( 'getModel' , ( ) => {
148+ it ( 'should return default model if no model ID is provided' , ( ) => {
149+ const handlerWithoutModel = new AnthropicHandler ( {
150+ ...mockOptions ,
151+ apiModelId : undefined
152+ } ) ;
153+ const model = handlerWithoutModel . getModel ( ) ;
154+ expect ( model . id ) . toBeDefined ( ) ;
155+ expect ( model . info ) . toBeDefined ( ) ;
156+ } ) ;
157+
158+ it ( 'should return specified model if valid model ID is provided' , ( ) => {
159+ const model = handler . getModel ( ) ;
160+ expect ( model . id ) . toBe ( mockOptions . apiModelId ) ;
161+ expect ( model . info ) . toBeDefined ( ) ;
162+ expect ( model . info . maxTokens ) . toBe ( 8192 ) ;
163+ expect ( model . info . contextWindow ) . toBe ( 200_000 ) ;
164+ expect ( model . info . supportsImages ) . toBe ( true ) ;
165+ expect ( model . info . supportsPromptCache ) . toBe ( true ) ;
166+ } ) ;
167+ } ) ;
168+ } ) ;
0 commit comments