paperclipai/paperclip · error · TeamsAdapterCompatibilityError

${methodName} is unavailable

Error message

${methodName} is unavailable

What it means

scopeMicrosoftTeamsEgress iterates TEAMS_THREAD_SCOPED_METHODS (the adapter's per-thread operations) and asserts each is a function. Thread-scoped methods must all exist because the wrapper re-routes them through withThreadServiceUrl to scope outbound calls to the conversation's verified service URL. A missing method means the wrapper cannot safely proxy the full thread surface, so it throws.

Source

Thrown at server/src/services/chat-sdk-runtime.ts:1032

  }
  if (typeof teams.decodeThreadId !== "function") {
    throw new TeamsAdapterCompatibilityError("decodeThreadId is unavailable");
  }
  if (typeof teams.openDM !== "function") {
    throw new TeamsAdapterCompatibilityError("openDM is unavailable");
  }
  if (typeof teams.cacheUserContext !== "function") {
    throw new TeamsAdapterCompatibilityError("cacheUserContext is unavailable");
  }
  if (typeof teams.getIncomingUser !== "function") {
    throw new TeamsAdapterCompatibilityError("getIncomingUser is unavailable");
  }
  if (typeof teams.getUser !== "function") {
    throw new TeamsAdapterCompatibilityError("getUser is unavailable");
  }
  for (const methodName of TEAMS_THREAD_SCOPED_METHODS) {
    if (typeof teams[methodName] !== "function") {
      throw new TeamsAdapterCompatibilityError(`${methodName} is unavailable`);
    }
  }
  if (
    typeof teams.app.api.constructor !== "function" ||
    !("http" in teams.app.api)
  ) {
    throw new TeamsAdapterCompatibilityError(
      "the API client constructor or HTTP transport is unavailable",
    );
  }
  const apiDescriptor = Object.getOwnPropertyDescriptor(teams.app, "api");
  if (apiDescriptor && apiDescriptor.configurable === false) {
    throw new TeamsAdapterCompatibilityError(
      "app.api cannot be scoped per asynchronous conversation",
    );
  }
  const trustedConfiguredApiUrl = configuredApiUrl
    ? normalizedTeamsServiceUrl(configuredApiUrl)

View on GitHub (pinned to 01ad858492)

Solutions

  1. Read the thrown message to identify which method is missing and check TEAMS_THREAD_SCOPED_METHODS for the full required list.
  2. Use the official, current Teams adapter implementation rather than a partial reimplementation.
  3. Update your test mock to implement every thread-scoped method as a function.
  4. Check the adapter's prototype chain — methods defined only on a class prototype can be lost if the object was spread ({...adapter}).

Example fix

// before (spread drops prototype methods)
const partial = { ...createTeamsAdapter() };
scopeMicrosoftTeamsEgress(partial);
// after
const adapter = createTeamsAdapter(); // keep prototype intact
scopeMicrosoftTeamsEgress(adapter);
Defensive patterns

Strategy: validation

Validate before calling

const required = TEAMS_THREAD_SCOPED_METHODS;
const missing = required.filter((m) => typeof (adapter as any)[m] !== 'function');
if (missing.length) throw new Error(`Teams adapter missing thread-scoped methods: ${missing.join(', ')}`);

Try / catch

try {
  scoped = scopeMicrosoftTeamsEgress(adapter);
} catch (err) {
  if (err instanceof TeamsAdapterCompatibilityError && err.message.endsWith('is unavailable')) {
    logger.error(`Thread-scoped method missing on Teams adapter: ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: An adapter object passed to scopeMicrosoftTeamsEgress is missing at least one method listed in TEAMS_THREAD_SCOPED_METHODS; the thrown message names the specific method (e.g. 'postActivity is unavailable').

Common situations: Adapter version mismatch after an upgrade; incomplete test doubles; a custom adapter that implements most but not all thread-scoped methods; tree-shaking or build config dropping prototype methods.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/a8fd8087d6c34967. Report an issue: GitHub.