TypeScriptADK-TS

Runner Configuration

Wire an artifact service to your ADK-TS Runner and enable automatic saving of user-uploaded blobs.

Before any callback or tool can save or load an artifact, the Runner needs an artifactService. This page covers how to wire one up and how to enable auto-saving of binary uploads sent by the user.

Wiring the service

Without an artifactService, every saveArtifact and loadArtifact call will throw at runtime. The recommended way is to pass it through AgentBuilder using .withArtifactService():

import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";

const { runner } = await AgentBuilder.create("my_agent")
  .withModel("gemini-2.5-flash")
  .withArtifactService(new InMemoryArtifactService())
  .build();

If you're constructing the Runner directly, pass artifactService in the options:

import {
  Runner,
  LlmAgent,
  InMemorySessionService,
  InMemoryArtifactService,
} from "@iqai/adk";

const runner = new Runner({
  appName: "my_app",
  agent: new LlmAgent({
    name: "my_agent",
    model: "gemini-2.5-flash",
    description: "An agent that does things",
  }),
  sessionService: new InMemorySessionService(),
  artifactService: new InMemoryArtifactService(),
});

Use InMemoryArtifactService during development and switch to GcsArtifactService for production. Both implement the same interface, so no other code needs to change.

Auto-save user uploads

Normally, binary content sent by the user (images, files) arrives as inlineData parts inside the message and the agent has to process them itself. When saveInputBlobsAsArtifacts is enabled, the runner automatically saves each inlineData part as an artifact before the agent sees the message. The original part is replaced with a short text placeholder so the agent receives a clean, text-only message.

Each saved artifact is named artifact_<invocationId>_<index>, where index is the position of the part in the message.

To enable this for every run, set it at build time via withRunConfig:

import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";

const { runner } = await AgentBuilder.create("upload_agent")
  .withModel("gemini-2.5-flash")
  .withArtifactService(new InMemoryArtifactService())
  .withRunConfig({ saveInputBlobsAsArtifacts: true })
  .build();

To enable it only for specific runs, pass a RunConfig to runAsync instead:

import { RunConfig } from "@iqai/adk";

const message = {};

for await (const event of runner.runAsync({
  userId: "u1",
  sessionId: "s1",
  newMessage: message,
  runConfig: new RunConfig({ saveInputBlobsAsArtifacts: true }),
})) {
  // handle events
}

Sending a file upload

When auto-save is enabled, any message you send with inlineData parts will trigger it. Encode the raw bytes as base64 and set the correct mimeType:

import { readFileSync } from "node:fs";
import type { Content } from "@google/genai";

const message: Content = {
  role: "user",
  parts: [
    {
      inlineData: {
        data: readFileSync("./upload.png").toString("base64"),
        mimeType: "image/png",
      },
    },
  ],
};

for await (const event of runner.runAsync({
  userId: "u1",
  sessionId: "s1",
  newMessage: message,
})) {
  // The image is auto-saved as "artifact_<invocationId>_0"
  // The agent receives a text placeholder instead of the raw bytes
}

The artifact name follows the pattern artifact_<invocationId>_<index>. If the user sends multiple files in one message, they are saved as _0, _1, _2, and so on. Your callbacks and tools can then load them by name.

Next steps