@@ -197,6 +197,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons
197197 () -> this .anthropicApi .chatCompletionEntity (request , this .getAdditionalHttpHeaders (prompt )));
198198
199199 AnthropicApi .ChatCompletionResponse completionResponse = completionEntity .getBody ();
200+
200201 AnthropicApi .Usage usage = completionResponse .usage ();
201202
202203 Usage currentChatResponseUsage = usage != null ? this .getDefaultUsage (completionResponse .usage ())
@@ -381,7 +382,8 @@ private ChatResponse toChatResponse(ChatCompletionResponse chatCompletion, Usage
381382 .usage (usage )
382383 .keyValue ("stop-reason" , chatCompletion .stopReason ())
383384 .keyValue ("stop-sequence" , chatCompletion .stopSequence ())
384- .keyValue ("type" , chatCompletion .type ());
385+ .keyValue ("type" , chatCompletion .type ())
386+ .keyValue ("anthropic-response" , chatCompletion );
385387
386388 // Add citation metadata if citations were found
387389 if (citationContext .hasCitations ()) {
@@ -584,6 +586,14 @@ Prompt buildRequestPrompt(Prompt prompt) {
584586 else if (this .defaultOptions .getCitationDocuments () != null ) {
585587 requestOptions .setCitationDocuments (this .defaultOptions .getCitationDocuments ());
586588 }
589+
590+ // Merge skillContainer that is Json-ignored
591+ if (runtimeOptions .getSkillContainer () != null ) {
592+ requestOptions .setSkillContainer (runtimeOptions .getSkillContainer ());
593+ }
594+ else if (this .defaultOptions .getSkillContainer () != null ) {
595+ requestOptions .setSkillContainer (this .defaultOptions .getSkillContainer ());
596+ }
587597 }
588598 else {
589599 requestOptions .setHttpHeaders (this .defaultOptions .getHttpHeaders ());
@@ -592,6 +602,7 @@ else if (this.defaultOptions.getCitationDocuments() != null) {
592602 requestOptions .setToolCallbacks (this .defaultOptions .getToolCallbacks ());
593603 requestOptions .setToolContext (this .defaultOptions .getToolContext ());
594604 requestOptions .setCitationDocuments (this .defaultOptions .getCitationDocuments ());
605+ requestOptions .setSkillContainer (this .defaultOptions .getSkillContainer ());
595606 }
596607
597608 ToolCallingChatOptions .validateToolCallbacks (requestOptions .getToolCallbacks ());
@@ -629,7 +640,16 @@ ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
629640 ChatCompletionRequest request = new ChatCompletionRequest (this .defaultOptions .getModel (), userMessages ,
630641 systemContent , this .defaultOptions .getMaxTokens (), this .defaultOptions .getTemperature (), stream );
631642
632- request = ModelOptionsUtils .merge (requestOptions , request , ChatCompletionRequest .class );
643+ // Save toolChoice for later application (after code_execution tool is added)
644+ AnthropicApi .ToolChoice savedToolChoice = requestOptions != null ? requestOptions .getToolChoice () : null ;
645+ AnthropicChatOptions mergeOptions = requestOptions ;
646+ if (savedToolChoice != null && requestOptions != null ) {
647+ // Create a copy without toolChoice to avoid premature merge
648+ mergeOptions = requestOptions .copy ();
649+ mergeOptions .setToolChoice (null );
650+ }
651+
652+ request = ModelOptionsUtils .merge (mergeOptions , request , ChatCompletionRequest .class );
633653
634654 // Add the tool definitions with potential caching
635655 List <ToolDefinition > toolDefinitions = this .toolCallingManager .resolveToolDefinitions (requestOptions );
@@ -643,21 +663,112 @@ ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
643663 request = ChatCompletionRequest .from (request ).tools (tools ).build ();
644664 }
645665
646- // Add beta header for 1-hour TTL if needed
647- if (cacheOptions .getMessageTypeTtl ().containsValue (AnthropicCacheTtl .ONE_HOUR )) {
666+ // Add Skills container from options if present
667+ AnthropicApi .SkillContainer skillContainer = null ;
668+ if (requestOptions != null && requestOptions .getSkillContainer () != null ) {
669+ skillContainer = requestOptions .getSkillContainer ();
670+ }
671+ else if (this .defaultOptions .getSkillContainer () != null ) {
672+ skillContainer = this .defaultOptions .getSkillContainer ();
673+ }
674+
675+ if (skillContainer != null ) {
676+ request = ChatCompletionRequest .from (request ).container (skillContainer ).build ();
677+
678+ // Skills require the code_execution tool to be enabled
679+ // Add it if not already present
680+ List <AnthropicApi .Tool > existingTools = request .tools () != null ? new ArrayList <>(request .tools ())
681+ : new ArrayList <>();
682+ boolean hasCodeExecution = existingTools .stream ().anyMatch (tool -> "code_execution" .equals (tool .name ()));
683+
684+ if (!hasCodeExecution ) {
685+ existingTools
686+ .add (new AnthropicApi .Tool (AnthropicApi .CODE_EXECUTION_TOOL_TYPE , "code_execution" , null , null ));
687+ request = ChatCompletionRequest .from (request ).tools (existingTools ).build ();
688+ }
689+
690+ // Apply saved toolChoice now that code_execution tool has been added
691+ if (savedToolChoice != null ) {
692+ request = ChatCompletionRequest .from (request ).toolChoice (savedToolChoice ).build ();
693+ }
694+ }
695+ else if (savedToolChoice != null ) {
696+ // No Skills but toolChoice was set - apply it now
697+ request = ChatCompletionRequest .from (request ).toolChoice (savedToolChoice ).build ();
698+ }
699+
700+ // Add beta headers if needed
701+ if (requestOptions != null ) {
648702 Map <String , String > headers = new HashMap <>(requestOptions .getHttpHeaders ());
649- headers .put ("anthropic-beta" , AnthropicApi .BETA_EXTENDED_CACHE_TTL );
650- requestOptions .setHttpHeaders (headers );
703+ boolean needsUpdate = false ;
704+
705+ // Add Skills beta headers if Skills are present
706+ // Skills require three beta headers: skills, code-execution, and files-api
707+ if (skillContainer != null ) {
708+ String existingBeta = headers .get ("anthropic-beta" );
709+ String requiredBetas = AnthropicApi .BETA_SKILLS + "," + AnthropicApi .BETA_CODE_EXECUTION + ","
710+ + AnthropicApi .BETA_FILES_API ;
711+
712+ if (existingBeta != null ) {
713+ // Add missing beta headers
714+ if (!existingBeta .contains (AnthropicApi .BETA_SKILLS )) {
715+ existingBeta = existingBeta + "," + AnthropicApi .BETA_SKILLS ;
716+ }
717+ if (!existingBeta .contains (AnthropicApi .BETA_CODE_EXECUTION )) {
718+ existingBeta = existingBeta + "," + AnthropicApi .BETA_CODE_EXECUTION ;
719+ }
720+ if (!existingBeta .contains (AnthropicApi .BETA_FILES_API )) {
721+ existingBeta = existingBeta + "," + AnthropicApi .BETA_FILES_API ;
722+ }
723+ headers .put ("anthropic-beta" , existingBeta );
724+ }
725+ else {
726+ headers .put ("anthropic-beta" , requiredBetas );
727+ }
728+ needsUpdate = true ;
729+ }
730+
731+ // Add extended cache TTL beta header if needed
732+ if (cacheOptions .getMessageTypeTtl ().containsValue (AnthropicCacheTtl .ONE_HOUR )) {
733+ String existingBeta = headers .get ("anthropic-beta" );
734+ if (existingBeta != null && !existingBeta .contains (AnthropicApi .BETA_EXTENDED_CACHE_TTL )) {
735+ headers .put ("anthropic-beta" , existingBeta + "," + AnthropicApi .BETA_EXTENDED_CACHE_TTL );
736+ }
737+ else if (existingBeta == null ) {
738+ headers .put ("anthropic-beta" , AnthropicApi .BETA_EXTENDED_CACHE_TTL );
739+ }
740+ needsUpdate = true ;
741+ }
742+
743+ if (needsUpdate ) {
744+ requestOptions .setHttpHeaders (headers );
745+ }
651746 }
652747
653748 return request ;
654749 }
655750
751+ /**
752+ * Helper method to serialize content from ContentBlock. The content field can be
753+ * either a String or a complex object (for Skills responses).
754+ * @param content The content to serialize
755+ * @return String representation of the content, or null if content is null
756+ */
757+ private static String serializeContent (Object content ) {
758+ if (content == null ) {
759+ return null ;
760+ }
761+ if (content instanceof String s ) {
762+ return s ;
763+ }
764+ return JsonParser .toJson (content );
765+ }
766+
656767 private static ContentBlock cacheAwareContentBlock (ContentBlock contentBlock , MessageType messageType ,
657768 CacheEligibilityResolver cacheEligibilityResolver ) {
658769 String basisForLength = switch (contentBlock .type ()) {
659770 case TEXT , TEXT_DELTA -> contentBlock .text ();
660- case TOOL_RESULT -> contentBlock .content ();
771+ case TOOL_RESULT -> serializeContent ( contentBlock .content () );
661772 case TOOL_USE -> JsonParser .toJson (contentBlock .input ());
662773 case THINKING , THINKING_DELTA -> contentBlock .thinking ();
663774 case REDACTED_THINKING -> contentBlock .data ();
@@ -846,7 +957,8 @@ private List<AnthropicApi.Tool> addCacheToLastTool(List<AnthropicApi.Tool> tools
846957 AnthropicApi .Tool tool = tools .get (i );
847958 if (i == tools .size () - 1 ) {
848959 // Add cache control to last tool
849- tool = new AnthropicApi .Tool (tool .name (), tool .description (), tool .inputSchema (), cacheControl );
960+ tool = new AnthropicApi .Tool (tool .type (), tool .name (), tool .description (), tool .inputSchema (),
961+ cacheControl );
850962 cacheEligibilityResolver .useCacheBlock ();
851963 }
852964 modifiedTools .add (tool );
0 commit comments