@@ -14,6 +14,7 @@ import { parseJsonObjectPreservingUnsafeIntegers } from "./json-unsafe-integers.
1414import { resolveProviderEndpoint } from "./provider-attribution.js" ;
1515import { buildGuardedModelFetch } from "./provider-transport-fetch.js" ;
1616import type { StreamFn } from "./runtime/index.js" ;
17+ import { projectRuntimeToolInputSchema } from "./tool-schema-projection.js" ;
1718import { transformTransportMessages } from "./transport-message-transform.js" ;
1819import {
1920 coerceTransportToolCallArguments ,
@@ -241,13 +242,22 @@ function toClaudeCodeName(name: string): string {
241242}
242243
243244function fromClaudeCodeName ( name : string , tools : Context [ "tools" ] | undefined ) : string {
244- if ( tools && tools . length > 0 ) {
245- const lowerName = normalizeLowercaseStringOrEmpty ( name ) ;
246- const matchedTool = tools . find (
247- ( tool ) => normalizeLowercaseStringOrEmpty ( tool . name ) === lowerName ,
248- ) ;
249- if ( matchedTool ) {
250- return matchedTool . name ;
245+ if ( ! tools ) {
246+ return name ;
247+ }
248+ const toolCount = readAnthropicToolCount ( tools ) ;
249+ if ( ! toolCount ) {
250+ return name ;
251+ }
252+ const lowerName = normalizeLowercaseStringOrEmpty ( name ) ;
253+ for ( let toolIndex = 0 ; toolIndex < toolCount ; toolIndex += 1 ) {
254+ const tool = readAnthropicToolAt ( tools , toolIndex ) ;
255+ if ( ! tool ) {
256+ continue ;
257+ }
258+ const toolName = readAnthropicToolField ( tool , "name" ) ;
259+ if ( typeof toolName === "string" && normalizeLowercaseStringOrEmpty ( toolName ) === lowerName ) {
260+ return toolName ;
251261 }
252262 }
253263 return name ;
@@ -474,10 +484,48 @@ function ensureNonEmptyAnthropicMessages(messages: Array<Record<string, unknown>
474484 : [ { role : "user" , content : EMPTY_ANTHROPIC_MESSAGES_FALLBACK_TEXT } ] ;
475485}
476486
487+ function readAnthropicToolField < TField extends keyof NonNullable < Context [ "tools" ] > [ number ] > (
488+ tool : NonNullable < Context [ "tools" ] > [ number ] ,
489+ field : TField ,
490+ ) : NonNullable < Context [ "tools" ] > [ number ] [ TField ] | undefined {
491+ try {
492+ return tool [ field ] ;
493+ } catch {
494+ return undefined ;
495+ }
496+ }
497+
498+ function isAnthropicObjectSchema ( value : unknown ) : value is Record < string , unknown > {
499+ return Boolean ( value && typeof value === "object" && ! Array . isArray ( value ) ) ;
500+ }
501+
502+ function readAnthropicToolCount ( tools : NonNullable < Context [ "tools" ] > ) : number | undefined {
503+ try {
504+ return tools . length ;
505+ } catch {
506+ return undefined ;
507+ }
508+ }
509+
510+ function readAnthropicToolAt (
511+ tools : NonNullable < Context [ "tools" ] > ,
512+ index : number ,
513+ ) : NonNullable < Context [ "tools" ] > [ number ] | undefined {
514+ try {
515+ return tools [ index ] ;
516+ } catch {
517+ return undefined ;
518+ }
519+ }
520+
477521function convertAnthropicTools ( tools : Context [ "tools" ] , isOAuthToken : boolean ) {
478522 if ( ! tools ) {
479523 return [ ] ;
480524 }
525+ const toolCount = readAnthropicToolCount ( tools ) ;
526+ if ( toolCount === undefined ) {
527+ return [ ] ;
528+ }
481529 const converted : Array < {
482530 name : string ;
483531 description ?: string ;
@@ -487,24 +535,38 @@ function convertAnthropicTools(tools: Context["tools"], isOAuthToken: boolean) {
487535 required : unknown ;
488536 } ;
489537 } > = [ ] ;
490- for ( const tool of tools ) {
538+ for ( let toolIndex = 0 ; toolIndex < toolCount ; toolIndex += 1 ) {
491539 // Main quarantine happens when plugin tools materialize; this keeps Anthropic
492540 // safe for direct/custom tool arrays that bypass the plugin registry.
493- const parameters =
494- tool . parameters && typeof tool . parameters === "object" && ! Array . isArray ( tool . parameters )
495- ? ( tool . parameters as Record < string , unknown > )
496- : undefined ;
497- if ( ! parameters ) {
541+ const tool = readAnthropicToolAt ( tools , toolIndex ) ;
542+ if ( ! tool ) {
498543 continue ;
499544 }
500- converted . push ( {
501- name : isOAuthToken ? toClaudeCodeName ( tool . name ) : tool . name ,
502- description : tool . description ,
545+ const rawName = readAnthropicToolField ( tool , "name" ) ;
546+ const parameters = readAnthropicToolField ( tool , "parameters" ) ;
547+ const description = readAnthropicToolField ( tool , "description" ) ;
548+ if ( typeof rawName !== "string" || ! rawName ) {
549+ continue ;
550+ }
551+ const projectedSchema = projectRuntimeToolInputSchema ( parameters , `${ rawName } .parameters` ) ;
552+ if ( projectedSchema . violations . length > 0 ) {
553+ continue ;
554+ }
555+ if ( ! isAnthropicObjectSchema ( projectedSchema . schema ) ) {
556+ continue ;
557+ }
558+ const parametersRecord = projectedSchema . schema ;
559+ const convertedTool = {
560+ name : isOAuthToken ? toClaudeCodeName ( rawName ) : rawName ,
503561 input_schema : {
504- type : "object" ,
505- properties : parameters . properties || { } ,
506- required : parameters . required || [ ] ,
562+ type : "object" as const ,
563+ properties : parametersRecord . properties || { } ,
564+ required : parametersRecord . required || [ ] ,
507565 } ,
566+ } ;
567+ converted . push ( {
568+ ...convertedTool ,
569+ ...( typeof description === "string" ? { description } : { } ) ,
508570 } ) ;
509571 }
510572 return converted ;
0 commit comments