How to Send Images as Attachments to Copilot SDK Sessions: Blob vs File Methods

You can send images to Copilot SDK sessions using either blob attachments for base64-encoded data or file attachments for local image paths, with the latter requiring the vision: true capability flag.

The GitHub Copilot SDK provides native support for multimodal conversations by allowing you to attach images directly to session.send or session.sendAndWait calls. Whether you need to transmit dynamically generated pixels or reference existing files on disk, the SDK handles the encoding and transmission automatically according to the type definitions in nodejs/src/types.ts.

Supported Attachment Types for Images

The SDK recognizes two distinct attachment strategies for image data. Your choice depends on whether the image exists as a file or as raw data in memory.

Blob Attachments

blob attachments embed base64-encoded image data directly in the request payload. According to the type definitions in nodejs/src/types.ts (lines 58-89), a blob attachment requires data (the base64 string), mimeType (such as image/png or image/jpeg), and an optional displayName for UI rendering.

This approach is optimal for small images generated programmatically or when you want to avoid filesystem I/O. The SDK passes the MIME type to the transport layer, which encodes the data as an image_url part for the model.

File Attachments with Vision

file attachments reference images by their filesystem path. To use this method, you must enable vision capabilities when creating the session by setting modelCapabilities: { supports: { vision: true } } in the session configuration.

When vision is enabled, the SDK reads the specified file, base64-encodes it automatically, and injects an image_url content block into the conversation. This method is ideal for large files that benefit from the SDK's streaming efficiency.

Architectural Flow: How Images Reach the Model

Understanding the internal processing helps debug attachment issues. The flow follows four distinct stages implemented in the github/copilot-sdk repository:

  1. Type Definition – The MessageOptions.attachments field is defined in nodejs/src/types.ts (lines 58-89), specifying the blob shape with data, mimeType, and optional displayName.

  2. Session Send – When session.send receives an attachment, the SDK normalizes it into an internal Attachment object. The wire protocol schema resides in nodejs/src/generated/session-events.ts (lines 2750-2790).

  3. Vision Handling – If the session's model capabilities include vision: true, the runtime generates an image_url content block containing a data URL formatted as data:<mime>;base64,<data>.

  4. Model Reception – The LLM receives the image as part of the prompt (e.g., a ChatCompletionMessageContentPartImageUrl in OpenAI format), and the SDK returns the response as usual.

Code Examples

Sending a Base64-Encoded Image as a Blob

Use this pattern when you have image data already loaded in memory or generated on-the-fly:

import { CopilotClient, approveAll } from "copilot-sdk";

const client = new CopilotClient();
const session = await client.createSession({ onPermissionRequest: approveAll });

await session.sendAndWait({
  prompt: "What color is this pixel? Reply in one word.",
  attachments: [
    {
      type: "blob",
      data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
      mimeType: "image/png",
      displayName: "pixel.png",
    },
  ],
});
await session.disconnect();

This example corresponds to the blob attachment test in nodejs/test/e2e/session_config.e2e.test.ts (lines 65-82).

Attaching a Local Image File with Vision Enabled

Use this approach when referencing existing files on disk:

import { CopilotClient, approveAll } from "copilot-sdk";
import { writeFile, join } from "fs/promises";

const client = new CopilotClient();
const workDir = "/tmp/myproject";
await writeFile(join(workDir, "photo.png"), Buffer.from(
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
  "base64"
));

const session = await client.createSession({
  onPermissionRequest: approveAll,
  modelCapabilities: { supports: { vision: true } },
});

await session.sendAndWait({
  prompt: "Describe the image content",
  attachments: [{ type: "file", path: join(workDir, "photo.png") }],
});
await session.disconnect();

This implementation is verified in nodejs/test/e2e/session_config.e2e.test.ts (lines 117-136).

Dynamically Toggling Vision Capabilities

You can change vision support mid-session using setModel:

// Start with vision disabled
let session = await client.createSession({
  onPermissionRequest: approveAll,
  modelCapabilities: { supports: { vision: false } },
});
await session.sendAndWait({ prompt: "Explain this text." });

// Enable vision later
await session.setModel("claude-sonnet-4.5", {
  modelCapabilities: { supports: { vision: true } },
});
await session.sendAndWait({
  prompt: "Look at the attached picture and tell me what you see",
  attachments: [{ type: "file", path: "./cat.png" }],
});

This pattern is demonstrated in nodejs/test/e2e/session_config.e2e.test.ts (lines 31-45).

Key Implementation Files

  • nodejs/src/types.ts – Defines the public API shape for MessageOptions.attachments, including the blob interface with data, mimeType, and displayName fields.

  • nodejs/src/generated/session-events.ts – Contains the internal Attachment schema that dictates how the SDK serializes attachments for the wire protocol.

  • nodejs/test/e2e/session_config.e2e.test.ts – Provides verified usage patterns for both blob and vision-enabled file attachments, ensuring images correctly forward to the model.

  • nodejs/src/session.ts – Implements the core session logic that handles send, sendAndWait, setModel, and the conditional injection of image_url content blocks based on vision flags.

Summary

  • Blob attachments require base64-encoded data and a MIME type, making them ideal for in-memory images.
  • File attachments require the vision: true capability and automatically handle file reading and encoding.
  • The SDK converts both attachment types into image_url content blocks that models receive as part of the conversation context.
  • Vision capabilities can be toggled dynamically using session.setModel after session initialization.
  • Reference nodejs/src/types.ts for type definitions and nodejs/test/e2e/session_config.e2e.test.ts for working examples.

Frequently Asked Questions

What MIME types are supported for blob attachments?

The SDK accepts any standard image MIME type (such as image/png, image/jpeg, or image/webp) in the mimeType field of a blob attachment. The transport layer passes this value directly to the model provider when constructing the data URL, so compatibility depends on the specific LLM's supported formats.

Do I need to base64-encode images manually when using file attachments?

No. When using type: "file" attachments with vision enabled, the SDK automatically reads the file from disk and base64-encodes it before transmission. You only need to provide the filesystem path; the encoding happens internally in the session implementation.

Can I switch vision capabilities after creating a session?

Yes. You can dynamically enable or disable vision by calling session.setModel() with updated modelCapabilities. This allows you to start with text-only processing and enable image analysis later without creating a new session instance, as shown in the test cases at nodejs/test/e2e/session_config.e2e.test.ts.

What is the maximum image size supported?

While the SDK itself does not enforce strict size limits in nodejs/src/types.ts, practical limits depend on the underlying model provider's API constraints and the transport layer's payload size restrictions. For large images, use file attachments rather than blob to leverage the SDK's streaming capabilities and avoid memory overhead from base64 string expansion.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →