{"record":{"id":"d4f6ac371f59612d","repo":"paperclipai/paperclip","slug":"opendm-is-unavailable","errorCode":null,"errorMessage":"openDM is unavailable","messagePattern":"openDM is unavailable","errorType":"exception","errorClass":"TeamsAdapterCompatibilityError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-sdk-runtime.ts","lineNumber":1019,"sourceCode":" * outbound call to a fresh API client rooted at that route. Legacy thread ids\n * that embedded a URL remain readable, but a newer persisted route wins. A\n * context-local getter keeps simultaneous conversations isolated without\n * forcing unrelated Teams threads through a single network queue.\n */\nexport function scopeMicrosoftTeamsEgress(\n  adapter: Adapter,\n  configuredApiUrl?: string,\n  enableFileConsent = false,\n): Adapter {\n  const teams = adapter as unknown as TeamsAdapterInternals;\n  if (!teams.app?.api) {\n    throw new TeamsAdapterCompatibilityError(\"app.api is unavailable\");\n  }\n  if (typeof teams.decodeThreadId !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"decodeThreadId is unavailable\");\n  }\n  if (typeof teams.openDM !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"openDM is unavailable\");\n  }\n  if (typeof teams.cacheUserContext !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"cacheUserContext is unavailable\");\n  }\n  if (typeof teams.getIncomingUser !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"getIncomingUser is unavailable\");\n  }\n  if (typeof teams.getUser !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"getUser is unavailable\");\n  }\n  for (const methodName of TEAMS_THREAD_SCOPED_METHODS) {\n    if (typeof teams[methodName] !== \"function\") {\n      throw new TeamsAdapterCompatibilityError(`${methodName} is unavailable`);\n    }\n  }\n  if (\n    typeof teams.app.api.constructor !== \"function\" ||\n    !(\"http\" in teams.app.api)","sourceCodeStart":1001,"sourceCodeEnd":1037,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-sdk-runtime.ts#L1001-L1037","documentation":"Same compatibility gate as error 537: after verifying decodeThreadId, the runtime checks that adapter.openDM is a function and throws TeamsAdapterCompatibilityError('openDM is unavailable') when it is not. The adapter therefore cannot open direct-message threads and is rejected.","triggerScenarios":"Wiring a Teams adapter whose internals include app.api and decodeThreadId but whose openDM property is missing or not a function (older adapter version, incomplete custom adapter, partial mock).","commonSituations":"Adapter package drift after a dependency update; a custom adapter implemented before openDM was added to the contract; mocks that stub only the methods a specific test touched.","solutions":["Update the Teams adapter package to the version matching the runtime's expected internals.","Add/implement openDM on a custom adapter per the runtime contract.","Extend the mock with openDM: vi.fn().","Confirm no wrapper/proxy around the adapter deletes or renames openDM."],"exampleFix":"// before\nconst adapter = { app: { api: {} }, decodeThreadId: vi.fn() };\n// after\nconst adapter = { app: { api: {} }, decodeThreadId: vi.fn(), openDM: vi.fn() };","handlingStrategy":"type-guard","validationCode":"const teams = adapter as unknown as TeamsAdapterInternals;\nif (typeof teams.openDM !== \"function\") throw new Error(\"adapter missing openDM\");","typeGuard":"function supportsOpenDM(a: unknown): a is TeamsAdapterInternals {\n  return typeof (a as TeamsAdapterInternals)?.openDM === \"function\";\n}","tryCatchPattern":"try {\n  wireTeamsAdapter(adapter);\n} catch (e) {\n  if (e instanceof TeamsAdapterCompatibilityError && e.message.includes(\"openDM\")) {\n    // implement or upgrade so openDM exists\n  }\n}","preventionTips":["Feature-check the full required method set, not just one method, before wiring.","Upgrade the adapter package when the runtime adds new internal requirements.","Extend mocks whenever a new compatibility error names a missing method.","Add a unit test asserting every internal method is a function on the adapter."],"tags":["teams","adapter-compatibility","api-contract"],"backgroundTag":"method-not-implemented","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}