{"record":{"id":"26768662a9bfef2a","repo":"vercel/ai","slug":"open-responses-extension-id-extension-id-must-u","errorCode":null,"errorMessage":"Open Responses extension ID ${extension.id} must use <implementor>.<extension> format.","messagePattern":"Open Responses extension ID (.+?) must use <implementor>\\.<extension> format\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/open-responses/src/open-responses-extension.ts","lineNumber":163,"sourceCode":"  eventTypes: readonly OpenResponsesNamespacedType[];\n  decodeEvent: NonNullable<OpenResponsesExtension['decodeEvent']>;\n};\n\nexport function createOpenResponsesExtensionRegistry(\n  extensions?: readonly OpenResponsesExtension[],\n): OpenResponsesExtensionRegistry {\n  const registry: OpenResponsesExtensionRegistry = {\n    byEventType: new Map(),\n    byExtensionId: new Map(),\n    byItemType: new Map(),\n    byProviderToolId: new Map(),\n    byToolType: new Map(),\n  };\n\n  for (const extension of extensions ?? []) {\n    const namespaceSeparatorIndex = extension.id.indexOf('.');\n    if (namespaceSeparatorIndex <= 0) {\n      throw new Error(\n        `Open Responses extension ID ${extension.id} must use <implementor>.<extension> format.`,\n      );\n    }\n    const namespace = extension.id.slice(0, namespaceSeparatorIndex);\n\n    registerUnique({\n      map: registry.byExtensionId,\n      key: extension.id,\n      extension,\n      field: 'id',\n    });\n\n    const hasToolType = extension.toolType != null;\n    const hasToolEncoder = extension.encodeTool != null;\n    if (hasToolType !== hasToolEncoder) {\n      throw new Error(\n        `Open Responses extension ${extension.id} must provide toolType and encodeTool together.`,\n      );","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/open-responses/src/open-responses-extension.ts#L145-L181","documentation":"Open Responses extensions must be namespaced with an ID of the form '<implementor>.<extension>' so tool/item types cannot collide between implementors. createOpenResponsesExtensionRegistry throws when an extension id lacks a '.' separator (or starts with one), because no namespace can be derived.","triggerScenarios":"Passing an extension to createOpenResponses / the model constructor with id like 'myext', 'my-ext', or '.hidden' — i.e. indexOf('.') <= 0.","commonSituations":"Copy-pasting an extension example and renaming the id without keeping the dot; building a custom extension for internal use with a single-word id; typos like 'acme-pdf' intending a dot but using a dash.","solutions":["Change the id to include a dot: '<implementor>.<extension>' (e.g. 'acme.pdfAnnotation').","Verify the id contains at least one '.' that is not the first character.","Ensure the implementor prefix is stable/unique for your organization."],"exampleFix":"// before\nconst ext = { id: 'pdfannotation', toolType: 'pdf', encodeTool: ... }\n// after\nconst ext = { id: 'acme.pdfannotation', toolType: 'pdf', encodeTool: ... }","handlingStrategy":"validation","validationCode":"function hasNamespacedId(ext: { id: string }): boolean {\n  return ext.id.includes('.') && ext.id.indexOf('.') > 0;\n}\nextensions.forEach(e => { if (!hasNamespacedId(e)) throw new Error(`extension id '${e.id}' must be <implementor>.<extension>`); });","typeGuard":"function isNamespacedExtensionId(id: string): id is `${string}.${string}` {\n  return id.indexOf('.') > 0;\n}","tryCatchPattern":"try {\n  const registry = createOpenResponsesExtensionRegistry(extensions);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('must use <implementor>.<extension> format')) {\n    // fix the offending extension id before re-creating the registry\n  }\n  throw e;\n}","preventionTips":["Always prefix extension ids with your implementor/org name followed by a dot.","Use a template-literal type `${Org}.${string}` for extension ids.","Validate extension configs in a shared factory function.","Keep a registry of existing namespaces to avoid collisions."],"tags":["open-responses","extension","naming","validation"],"backgroundTag":"invalid-identifier-format","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}