TypeScriptADK-TS

Context Integration

Save and load artifacts inside ADK-TS agent callbacks and tools using CallbackContext and ToolContext.

When you write a callback or a tool, the context object passed to your function already has artifact methods built in — you do not need to import or pass the service manually.

  • CallbackContext — available in beforeAgentCallback, afterAgentCallback, beforeModelCallback, and afterModelCallback. Provides saveArtifact and loadArtifact.
  • ToolContext — available inside tools. Extends CallbackContext and adds listArtifacts(), which lets a tool discover what files are available in the current session.

Auto-saving user uploads

If you enable saveInputBlobsAsArtifacts: true in your run config, the runner automatically saves any inlineData parts from the user's message as artifacts before the agent runs — no callback code needed. See Runner Configuration for setup details.

Available methods

MethodAvailable onDescription
saveArtifact(filename, part)BothSaves a new version of the artifact. Returns the version number (starts at 0).
loadArtifact(filename, version?)BothLoads the latest version, or a specific version if you pass a number. Returns undefined if not found.
listArtifacts()ToolContext onlyReturns the filenames of all artifacts in the current session.

Deletion is not on contexts

deleteArtifact is intentionally not exposed through context objects — call it directly on the service instance. See Cleanup and Deletion.

Using artifacts in agent callbacks

CallbackContext is available in both beforeAgentCallback and afterAgentCallback. Use beforeAgent to load data the agent needs before it starts, and afterAgent to persist data once it finishes.

Load user preferences and conversation history from previous sessions, then write them into state so the agent can reference them:

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

const { runner } = await AgentBuilder.create("my_agent")
  .withModel("gemini-2.5-flash")
  .withArtifactService(new InMemoryArtifactService())
  .withBeforeAgentCallback(async (ctx: CallbackContext) => {
    // Load user preferences (user-scoped — persists across sessions)
    const prefsArtifact = await ctx.loadArtifact("user:preferences.json");
    if (prefsArtifact?.inlineData) {
      const prefs = JSON.parse(
        Buffer.from(prefsArtifact.inlineData.data, "base64").toString(),
      );
      ctx.state.set("theme", prefs.theme ?? "light");
      ctx.state.set("user_preferences", prefs);
    }

    // Load conversation history (session-scoped)
    const historyArtifact = await ctx.loadArtifact("conversation_history.json");
    if (historyArtifact?.inlineData) {
      const history = JSON.parse(
        Buffer.from(historyArtifact.inlineData.data, "base64").toString(),
      );
      ctx.state.set("conversation_count", history.length);
    }

    return undefined;
  })
  .build();

Persist the conversation history and a session summary after each turn so they're available next time:

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

const { runner } = await AgentBuilder.create("my_agent")
  .withModel("gemini-2.5-flash")
  .withArtifactService(new InMemoryArtifactService())
  .withAfterAgentCallback(async (ctx: CallbackContext) => {
    // Save conversation history
    const history = ctx.state.get("conversation_history") ?? [];
    await ctx.saveArtifact("conversation_history.json", {
      inlineData: {
        data: Buffer.from(JSON.stringify(history)).toString("base64"),
        mimeType: "application/json",
      },
    });

    // Save session summary if the agent produced one
    const summary = ctx.state.get("session_summary");
    if (summary) {
      await ctx.saveArtifact("session_summary.json", {
        inlineData: {
          data: Buffer.from(JSON.stringify(summary)).toString("base64"),
          mimeType: "application/json",
        },
      });
    }

    return undefined;
  })
  .build();

Using artifacts in model callbacks

beforeModelCallback fires before every LLM call and afterModelCallback fires after. Use them to inject context into state before the model runs, or to capture metadata from the response.

Load a stored model config and inject its settings into state before each LLM call:

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

const { runner } = await AgentBuilder.create("my_agent")
  .withModel("gemini-2.5-flash")
  .withArtifactService(new InMemoryArtifactService())
  .withBeforeModelCallback(async ({
    callbackContext,
  }: {
    callbackContext: CallbackContext;
    llmRequest: LlmRequest;
  }) => {
    const configArtifact = await callbackContext.loadArtifact("model_config.json");
    if (configArtifact?.inlineData) {
      const config = JSON.parse(
        Buffer.from(configArtifact.inlineData.data, "base64").toString(),
      );
      callbackContext.state.set("temperature", config.temperature ?? 0.7);
      callbackContext.state.set("max_tokens", config.maxTokens ?? 1000);
    }

    // Optionally load additional context to pass to the model
    const contextArtifact = await callbackContext.loadArtifact("context_data.txt");
    if (contextArtifact?.inlineData) {
      const contextText = Buffer.from(
        contextArtifact.inlineData.data,
        "base64",
      ).toString();
      callbackContext.state.set("additional_context", contextText);
    }

    return undefined;
  })
  .build();

Record metadata about each model response — useful for auditing, cost tracking, or debugging:

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

const { runner } = await AgentBuilder.create("my_agent")
  .withModel("gemini-2.5-flash")
  .withArtifactService(new InMemoryArtifactService())
  .withAfterModelCallback(async ({
    callbackContext,
  }: {
    callbackContext: CallbackContext;
    llmResponse: LlmResponse;
  }) => {
    const metadata = {
      timestamp: new Date().toISOString(),
      model: callbackContext.state.get("current_model"),
      tokensUsed: callbackContext.state.get("tokens_used"),
    };

    await callbackContext.saveArtifact("response_metadata.json", {
      inlineData: {
        data: Buffer.from(JSON.stringify(metadata)).toString("base64"),
        mimeType: "application/json",
      },
    });

    return undefined;
  })
  .build();

Using artifacts in tools

Inside a tool, ctx is a ToolContext. It has all the same methods as CallbackContext plus listArtifacts(), which returns the filenames of every artifact in the current session. Use it to check what's available before loading.

import { createTool } from "@iqai/adk";
import * as z from "zod";

export const inspectFile = createTool({
  name: "inspect_file",
  description: "Describe a specific artifact by filename",
  schema: z.object({ filename: z.string() }),
  fn: async ({ filename }, ctx) => {
    // Check what's available before loading
    const files = await ctx.listArtifacts();
    if (!files.includes(filename)) {
      return { error: "File not found", available: files };
    }

    const artifact = await ctx.loadArtifact(filename);

    if (artifact?.text) {
      return { filename, type: "text", sizeBytes: artifact.text.length };
    }

    if (artifact?.inlineData) {
      return {
        filename,
        mimeType: artifact.inlineData.mimeType,
        sizeBytes: Buffer.from(artifact.inlineData.data, "base64").length,
      };
    }

    return { error: "Could not read file" };
  },
});

Next steps