Skip to main content
When you run a prompt in the AgentMark platform, it sends a prompt-run event to your webhook endpoint. This event contains the prompt configuration and any test settings you’ve defined. Prompt Run Button Highlighted

Event Format

{
  "event": {
    "type": "prompt-run",
    "data": {
      "prompt": {
        ...A prompt in Agentmark format
      }
    }
  }
}

Handling Different Generation Types

The webhook handler supports four types of generation using the Vercel AI SDK. First, import the necessary functions:
import { NextRequest, NextResponse } from "next/server";
import { AgentMarkSDK } from "@agentmark/sdk";
import {
  createAgentMarkClient,
  VercelAIModelRegistry,
} from "@agentmark/vercel-ai-v4-adapter";
import { openai } from "@ai-sdk/openai";
import { getFrontMatter } from "@agentmark/templatedx";
import {
  generateText,
  generateObject,
  experimental_generateImage as generateImage,
  experimental_generateSpeech as generateSpeech,
} from "ai";

1. Text Generation

if (frontmatter.text_config) {
  const prompt = await agentmark.loadTextPrompt(data.prompt);
  const vercelInput = await prompt.formatWithTestProps({});
  const { text, finishReason, usage, steps } = await generateText(vercelInput);

  const toolCalls = steps.map((step) => step.toolCalls).flat();
  const toolResults = steps.map((step) => step.toolResults).flat();

  return NextResponse.json({
    type: "text",
    result: text,
    usage: usage,
    finishReason: finishReason,
    toolCalls: toolCalls,
    toolResults: toolResults,
  });
}

Response Format

{
  "type": "text",
  "result": "Generated text content",
  "usage": {
    "promptTokens": 0,
    "completionTokens": 0,
    "totalTokens": 0
  },
  "finishReason": "stop",
  "toolCalls": [
    {
      "toolName": "string",
      "args": {},
      "type": "string",
      "toolCallId": "string"
    }
  ],
  "toolResults": [
    {
      "toolCallId": "string",
      "result": "string or object",
      "type": "string",
      "toolName": "string",
      "args": {}
    }
  ]
}

2. Object Generation

if (frontmatter.object_config) {
  const prompt = await agentmark.loadObjectPrompt(data.prompt);
  const vercelInput = await prompt.formatWithTestProps({});
  const { object, finishReason, usage } = await generateObject(vercelInput);

  return NextResponse.json({
    type: "object",
    result: object,
    usage: usage,
    finishReason: finishReason,
  });
}

Response Format

{
  "type": "object",
  "result": {
    // Structured object response
  },
  "usage": {
    "promptTokens": 0,
    "completionTokens": 0,
    "totalTokens": 0
  },
  "finishReason": "stop"
}

3. Image Generation

if (frontmatter.image_config) {
  const prompt = await agentmark.loadImagePrompt(data.prompt);
  const vercelInput = await prompt.formatWithTestProps({});
  const { images } = await generateImage(vercelInput);
  
  return NextResponse.json({
    type: "image",
    result: images.map((image) => ({
      base64: image.base64,
      mimeType: image.mimeType,
    })),
  });
}

Response Format

{
  "type": "image",
  "result": [
    {
      "base64": "base64_encoded_image",
      "mimeType": "image/png"
    }
  ]
}

4. Speech Generation

if (frontmatter.speech_config) {
  const prompt = await agentmark.loadSpeechPrompt(data.prompt);
  const vercelInput = await prompt.formatWithTestProps({});
  const { audio } = await generateSpeech(vercelInput);
  
  return NextResponse.json({
    type: "speech",
    result: {
      base64: audio.base64,
      format: audio.format,
      mimeType: audio.mimeType,
    },
  });
}

Response Format

{
  "type": "speech",
  "result": {
    "base64": "base64_encoded_audio",
    "format": "mp3",
    "mimeType": "audio/mpeg"
  }
}

Streaming Responses

Agentmark supports streaming responses for text and object generation back to platform. To enable streaming responses, set the AgentMark-Streaming header to true in your response.

Text Generation Streaming

if (frontmatter.text_config) {
  const prompt = await agentmark.loadTextPrompt(data.prompt);
  const vercelInput = await prompt.formatWithTestProps({});
  const { fullStream } = streamText(vercelInput);

  const stream = new ReadableStream({
    async start(controller) {
      const encoder = new TextEncoder();
      for await (const chunk of fullStream) {
        if (chunk.type === "error") {
          const err = chunk.error as any;
          let message =
            err?.message ??
            err?.data?.error?.message ??
            err?.lastError?.data?.error?.message ??
            "Something went wrong during inference";
          controller.enqueue(
            encoder.encode(
              JSON.stringify({
                error: message,
                type: "error",
              }) + "\n"
            )
          );
          controller.close();
          return;
        }

        if (chunk.type === "text-delta") {
          const chunkData = encoder.encode(
            JSON.stringify({
              result: chunk.textDelta,
              type: "text",
            }) + "\n"
          );
          controller.enqueue(chunkData);
        }

        if (chunk.type === "tool-call") {
          const chunkData = encoder.encode(
            JSON.stringify({
              toolCall: {
                args: chunk.args,
                toolCallId: chunk.toolCallId,
                toolName: chunk.toolName,
              },
              type: "text",
            }) + "\n"
          );
          controller.enqueue(chunkData);
        }

        if (chunk.type === "tool-result") {
          const chunkData = encoder.encode(
            JSON.stringify({
              toolResult: {
                args: chunk.args,
                toolCallId: chunk.toolCallId,
                toolName: chunk.toolName,
                result: chunk.result,
              },
              type: "text",
            }) + "\n"
          );
          controller.enqueue(chunkData);
        }

        if (chunk.type === "finish") {
          const chunkData = encoder.encode(
            JSON.stringify({
              finishReason: chunk.finishReason,
              usage: chunk.usage,
              type: "text",
            }) + "\n"
          );
          controller.enqueue(chunkData);
        }
      }

      controller.close();
    },
  });

  return new NextResponse(stream, {
    headers: {
      "Content-Type": "application/json",
      "Transfer-Encoding": "chunked",
      "AgentMark-Streaming": "true",
    },
  });
}
The stream sends data in chunks, with each chunk being a JSON object followed by a newline:
{"type":"text","result":"First chunk"}
{"type":"text","result":"Second chunk"}
{"type":"text","toolCall":{"toolName":"get_weather","toolCallId":"call_123","args":{"city":"New York"}}}
{"type":"text","toolResult":{"toolName":"get_weather","toolCallId":"call_123","args":{"city":"New York"},"result":"Sunny, 22°C"}}
{"type":"text","result":"Final response chunk"}
{"type":"text","finishReason":"stop","usage":{"promptTokens":10,"completionTokens":20,"totalTokens":30}}

Object Generation Streaming

if (frontmatter.object_config) {
  const prompt = await agentmark.loadObjectPrompt(data.prompt);
  const vercelInput = await prompt.formatWithTestProps({});
  const { usage, fullStream } = streamObject(vercelInput);
  
  const stream = new ReadableStream({
    async start(controller) {
      const encoder = new TextEncoder();
      for await (const chunk of fullStream) {
        if (chunk.type === "error") {
          const err = chunk.error as any;
          let message =
            err?.message ??
            err?.data?.error?.message ??
            err?.lastError?.data?.error?.message ??
            "Something went wrong during inference";
          controller.enqueue(
            encoder.encode(
              JSON.stringify({
                error: message,
                type: "error",
              }) + "\n"
            )
          );
          controller.close();
          return;
        }

        if (chunk.type === "object") {
          const chunkData = encoder.encode(
            JSON.stringify({
              result: chunk.object,
              type: "object",
            }) + "\n"
          );
          controller.enqueue(chunkData);
        }
      }
      
      const usageData = await usage;
      const metadata = {
        usage: usageData,
        type: "object",
      };
      const metadataChunk = JSON.stringify(metadata);
      const metadataChunkData = encoder.encode(metadataChunk);
      controller.enqueue(metadataChunkData);
      controller.close();
    },
  });

  return new NextResponse(stream, {
    headers: {
      "Content-Type": "application/json",
      "Transfer-Encoding": "chunked",
      "AgentMark-Streaming": "true",
    },
  });
}
The stream sends data in chunks, with each chunk being a JSON object followed by a newline:
{"type":"object","result":{"partial":"object"}}
{"type":"object","result":{"more":"complete","partial":"object"}}
{"type":"object","result":{"final":"complete","object":"data"}}
{"type":"object","usage":{"promptTokens":10,"completionTokens":20,"totalTokens":30}}

Error Handling

Handle errors appropriately in your webhook:
try {
  // Process prompt run
} catch (error) {
  console.error("Prompt run error:", error);
  return NextResponse.json(
    { message: "Error processing prompt run" },
    { status: 500 }
  );
}
The webhook implementation uses the Vercel AI SDK for generation. Make sure to install the required dependencies: bash npm install ai @ai-sdk/openai

Have Questions?

We’re here to help! Choose the best way to reach us: