{"record":{"id":"622187e834fb5841","repo":"JuliusBrussee/caveman","slug":"duplicate-or-reserved-caveman-retrieve-tool-name","errorCode":null,"errorMessage":"Duplicate or reserved caveman_retrieve tool name","messagePattern":"Duplicate or reserved caveman_retrieve tool name","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/typescript/src/openai.ts","lineNumber":32,"sourceCode":"}\n\ntype Functions = Readonly<Record<string, (input: unknown) => unknown | Promise<unknown>>>;\nexport interface OpenAIToolLoop<T extends OpenAI, Tool> {\n  readonly client: T;\n  /** A fresh native definition list; changing it cannot change registration. */\n  readonly tools: Tool[];\n  /** Use this immutable table for every application-owned function dispatch. */\n  readonly functions: Functions;\n}\ntype ChatFunction = OpenAI.Chat.Completions.ChatCompletionFunctionTool;\ntype ResponseFunction = OpenAI.Responses.FunctionTool;\nexport function withCavemanOpenAITools<T extends OpenAI>(client:T,options:OpenAIOptions & {protocol:'openai-chat';tools:ChatFunction[];functions:Functions}):OpenAIToolLoop<T,ChatFunction>;\nexport function withCavemanOpenAITools<T extends OpenAI>(client:T,options:OpenAIOptions & {protocol:'openai-responses';tools:ResponseFunction[];functions:Functions}):OpenAIToolLoop<T,ResponseFunction>;\n/** Native application-owned Chat/Responses calls, with no added scheduler. */\nexport function withCavemanOpenAITools<T extends OpenAI>(client:T,options:OpenAIOptions & {protocol:'openai-chat'|'openai-responses';tools:(ChatFunction|ResponseFunction)[];functions:Functions}):OpenAIToolLoop<T,ChatFunction|ResponseFunction>{\n  const definitions=structuredClone(options.tools);\n  const names=definitions.map(tool=>'function' in tool?tool.function.name:tool.name);\n  if(!plain(options.functions)||names.some(name=>typeof name!=='string'||!name)||new Set(names).size!==names.length||names.includes('caveman_retrieve')||'caveman_retrieve' in options.functions)throw new TypeError('Duplicate or reserved caveman_retrieve tool name');\n  if(names.length!==Object.keys(options.functions).length||names.some(name=>typeof options.functions[name]!=='function'))throw new TypeError('Every native function definition needs exactly one executor');\n  let functions=Object.freeze({...options.functions});\n  let context:RecoveryContext|undefined;\n  if(options.runtime.mode==='compress'&&frameworkCompatible('openai',VERSION)){\n    const binding=options.runtime.recovery(options.scope);\n    const schema={name:binding.name,description:binding.description,parameters:binding.inputSchema};\n    const definition=options.protocol==='openai-chat'?{type:'function' as const,function:schema}:{type:'function' as const,...schema,strict:false};\n    definitions.push(definition);\n    const execute=(input:unknown)=>binding.execute(input as never);\n    functions=Object.freeze({...functions,[binding.name]:execute});\n    context={runtime:options.runtime,scope:options.scope,binding,overhead:JSON.stringify(definition),logicalCallId:crypto.randomUUID(),\n      isRegistered:()=>options.runtime.ownsBinding(binding,options.scope)&&functions[binding.name]===execute};\n  }\n  const serialized=JSON.stringify(definitions);\n  return Object.freeze({client:wrapOpenAI(client,options,context,options.protocol),functions,get tools(){return JSON.parse(serialized);}});\n}\n\n/** A native withOptions clone; APIPromise, parsers, streams and runners survive. */","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/typescript/src/openai.ts#L14-L50","documentation":"`withCavemanOpenAITools` wraps your OpenAI client and reserves the tool name `caveman_retrieve` for its own recovery tool. In packages/middleware/typescript/src/openai.ts:32 it throws a TypeError if your supplied tool definitions contain duplicate names, an empty/invalid name, or any tool (or function executor key) named `caveman_retrieve`, since that would collide with the reserved name.","triggerScenarios":"Calling `withCavemanOpenAITools(client, { protocol, tools, functions })` where: two tool definitions share a name; a tool has an empty or non-string name; a tool is named `caveman_retrieve`; or `options.functions` already has a `caveman_retrieve` key; or `options.functions` is not a plain object.","commonSituations":"An app that previously registered its own caveman retrieval tool and now wraps with this library; programmatic tool generation producing duplicates; merging tool lists from multiple sources.","solutions":["Remove or rename any user-supplied `caveman_retrieve` tool/function; let the wrapper provide it.","Deduplicate the tools array before wrapping (unique, non-empty names).","Verify `functions` is a plain object of name -> function with no extra keys."],"exampleFix":"// before\nconst tools = [...baseTools, { type: 'function', function: { name: 'caveman_retrieve', ... } }];\n// after\nconst tools = baseTools; // caveman_retrieve is injected by withCavemanOpenAITools","handlingStrategy":"validation","validationCode":"function assertNoReservedTools(tools: { name?: string; function?: { name?: string } }[], functions: Record<string, unknown>) {\n  const names = tools.map(t => 'function' in t ? t.function?.name : t.name);\n  if (names.some(n => typeof n !== 'string' || !n)) throw new Error('All tools need non-empty string names');\n  if (new Set(names).size !== names.length) throw new Error('Duplicate tool names');\n  if (names.includes('caveman_retrieve') || 'caveman_retrieve' in functions) throw new Error('caveman_retrieve is reserved');\n}","typeGuard":"const isPlainFunctions = (f: unknown): f is Record<string, (...a: unknown[]) => unknown> =>\n  typeof f === 'object' && f !== null && !Array.isArray(f) && Object.values(f).every(v => typeof v === 'function');","tryCatchPattern":"try {\n  const loop = withCavemanOpenAITools(client, options);\n} catch (e) {\n  if (e instanceof TypeError && /reserved caveman_retrieve/.test(e.message)) {\n    console.error('Remove your own caveman_retrieve tool; the wrapper injects it.');\n    throw e;\n  }\n  throw e;\n}","preventionTips":["Treat caveman_retrieve as a reserved namespace in your tool registry.","Deduplicate tools by name when merging lists from multiple sources.","Add a unit test that wraps your production tool list to catch collisions early."],"tags":["openai","tool-naming","collision","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}