paperclipai/paperclip · error

Telegram durable draft transport is unavailable

Error message

Telegram durable draft transport is unavailable

What it means

streamTelegramDraft only forwards a text stream to the Telegram adapter when three invariants hold: the runtime provider is 'telegram', the thread resolves to a private-chat draft destination, and the adapter declares paperclipDraftStopVersion === 1 (the durable stop protocol). If any check fails, the runtime refuses to start a draft stream it cannot durably control, so it throws this plain Error instead of silently degrading to a non-durable transport.

Source

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

  async streamTelegramDraft(
    threadId: string,
    textStream: AsyncIterable<string>,
    control: TelegramDraftControl,
  ): Promise<{ id: string } | TelegramDraftStopped> {
    const adapter = this.adapter as unknown as {
      paperclipDraftStopVersion?: number;
      stream(
        threadId: string,
        stream: AsyncIterable<string>,
        options: unknown,
      ): Promise<{ id: string } | TelegramDraftStopped>;
    };
    if (
      this.provider !== "telegram" ||
      !telegramPrivateDraftDestination(threadId) ||
      adapter.paperclipDraftStopVersion !== 1
    ) {
      throw new Error("Telegram durable draft transport is unavailable");
    }
    return adapter.stream(threadId, textStream, {
      paperclipDraftControl: control,
    });
  }

  /**
   * Post one Slack file-only publication while durably recording the accepted
   * upload IDs before the adapter performs its eventually-consistent share
   * lookup. This specialized receipt scope is deliberately unavailable for
   * cards, edits, and ordinary text sends; the send still uses Thread.post.
   */
  async postSlackFilePublication(
    threadId: string,
    message: Parameters<Thread["post"]>[0],
    onUploadAccepted: (
      receipt: SlackFileUploadAcceptedReceipt,
    ) => Promise<void>,

View on GitHub (pinned to 01ad858492)

Solutions

  1. Verify the endpoint runtime is bound to the telegram provider before calling streamTelegramDraft (check this.provider / endpoint config).
  2. Confirm the threadId denotes a private chat by running telegramPrivateDraftDestination(threadId) first; use the ordinary Thread.post path for group/channel threads.
  3. Upgrade or re-wire the Telegram adapter so it exposes paperclipDraftStopVersion === 1, or feature-detect it and fall back to a plain post.
  4. Wrap the call in try/catch and fall back to non-durable sending when the transport is unavailable.

Example fix

// before
await runtime.streamTelegramDraft(threadId, textStream, control);

// after
if (
  runtime.provider === "telegram" &&
  telegramPrivateDraftDestination(threadId) &&
  (runtime.getProviderAdapter() as { paperclipDraftStopVersion?: number })
      .paperclipDraftStopVersion === 1
) {
  await runtime.streamTelegramDraft(threadId, textStream, control);
} else {
  await runtime.thread(threadId).post(text);
}
Defensive patterns

Strategy: fallback

Validate before calling

function canStreamTelegramDraft(runtime: unknown, threadId: string): boolean {
  const r = runtime as { provider?: string; getProviderAdapter?: () => unknown };
  const adapter = r.getProviderAdapter?.() as { paperclipDraftStopVersion?: number } | undefined;
  return r.provider === "telegram" && telegramPrivateDraftDestination(threadId) && adapter?.paperclipDraftStopVersion === 1;
}

Type guard

function isDraftCapableTelegramAdapter(a: unknown): a is { paperclipDraftStopVersion: 1; stream: Function } {
  return typeof a === "object" && a !== null && (a as any).paperclipDraftStopVersion === 1 && typeof (a as any).stream === "function";
}

Try / catch

try {
  await runtime.streamTelegramDraft(threadId, textStream, control);
} catch (err) {
  if (err instanceof Error && err.message.includes("durable draft transport is unavailable")) {
    await runtime.thread(threadId).post(text);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling streamTelegramDraft(threadId, textStream, control) when: (1) the runtime was constructed for a provider other than 'telegram'; (2) telegramPrivateDraftDestination(threadId) returns falsy, i.e. the thread id is not a private-chat draft-capable destination (e.g. a group/channel thread); or (3) the wired Telegram adapter does not export paperclipDraftStopVersion === 1 (stale or third-party adapter build).

Common situations: A deployment where the chat endpoint config was switched to a different provider but old code paths still call the Telegram-only streaming API; a Telegram group/channel thread id passed in instead of a private chat; running an older Telegram adapter (or stubbed adapter in tests) that predates the draft-stop v1 contract.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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