KsADK

Attachments And Multimodal Input

KsADK normalizes text, image, file, and uploaded attachment inputs before they reach a framework runner. Local UI, Responses API, Chat Completions, and ADK-style parts all share one execution path.

Application code should use the normalized runner payload instead of branching on which public protocol the client used. See Consume Structured Attachment Fields.

  • Route image analysis to a vision-capable model (see Default Multimodal Model).
  • Do not paste large files directly into the prompt; extract a summary or structured content first.
  • When a tool needs a file, use the workspace or an attachment reference; do not guess browser upload paths.
  • Do not write attachment bytes into long-term memory.

Input Shapes

Common protocol shapes:

input_text
{"type": "input_text", "text": "..."}
input_image
{"type": "input_image", "image_url": "data:image/png;base64,..."}
input_file
{"type": "input_file", "filename": "note.txt", "file_data": "data:text/plain;base64,..."}

File metadata and workspace references such as ksadk-upload://<file_id>.

Business agents should handle missing, expired, or unsupported attachment references and return a clear error.

Normalization Flow

Attachment normalization flow

The runner receives both canonical content (input_content, input_messages) and structured attachment results (attachment_results, current_attachment_results).

Protocol Mapping

KsADK accepts several client representations, then maps them into the same internal attachment path:

Client surfaceExample fieldCanonical meaning
Responsesinput[].content[].type=input_imageimage input block
Responsesinput[].content[].type=input_filefile input block
Chat Completionsmessages[].content[].type=image_urlimage input block
ADK-style partinlineDatainline bytes with MIME type
ADK-style partfileDatauploaded or referenced file
Local Web UIksadk-upload://...runtime-local upload reference
Hosted UIae-upload://...hosted upload reference (0.6.6 unified resolution)

Application code should use the normalized runner payload, not branch on which public protocol the client used.

Unified URI Resolution

New in 0.6.6

The runtime resolves local ksadk-upload:// and hosted ae-upload:// URIs through one unified path. Application code no longer needs to branch on the URI prefix.

URI schemeSourceResolution behavior
ksadk-upload://<file_id>local Web UI upload, written to the local files/ directory and mirrored to KS3read local cache first, fall back to KS3 via metadata when missing
ae-upload://<file_id>hosted runtime upload, object storage on the platform sidedownload the byte stream via the KOP Action AttachmentContent, then materialize it to the local cache (.meta.json)
paths without the ae-upload:// prefixworkspace-relative pathsresolved against the workspace root

Benefits of unified resolution:

  • After a session switch or browser refresh, the runner and workspace preview can still read real file content through file_uri.
  • Hosted attachments are materialized to the local files/ directory on first resolution; subsequent reads use the local cache instead of re-downloading.
  • Application code does not need to distinguish local from hosted: hand the file_uri to the runtime for resolution.

Public surface

ksadk-upload:// and ae-upload:// are not publicly addressable URLs; they are only valid inside the runtime that created them. Do not present them as durable links in public docs and examples.

Responses Examples

Text plus image:

{
  "model": "my-agent",
  "input": [
    {
      "role": "user",
      "content": [
        {"type": "input_text", "text": "Describe this image."},
        {
          "type": "input_image",
          "image_url": "data:image/png;base64,..."
        }
      ]
    }
  ],
  "stream": false
}

Text plus file:

{
  "model": "my-agent",
  "input": [
    {
      "role": "user",
      "content": [
        {"type": "input_text", "text": "Summarize this note."},
        {
          "type": "input_file",
          "filename": "note.txt",
          "file_data": "data:text/plain;base64,..."
        }
      ]
    }
  ]
}

Upload Then Reference

The local Web UI can upload a file and receive a URI:

{
  "FileData": {
    "fileUri": "ksadk-upload://abc123",
    "displayName": "contract.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 102400
  }
}

A later request can reference it:

{
  "type": "input_file",
  "filename": "contract.pdf",
  "file_url": "ksadk-upload://abc123"
}

Attachment Results

Each extracted attachment result can include:

FieldMeaning
display_namefile name shown to users
mime_typedetected or provided MIME type
transportinline or reference
file_urireference URI when available
size_bytesfile size
kindtext, document, image, archive, or binary
statusok, partial, or failed
warningsextraction warnings
extraction_methodparser or OCR path used
textextracted text, when available
text_excerptshort display/search excerpt

Text and document content may be truncated. Business code should handle missing or partial extraction results.

Inline Data Versus References

KsADK supports two attachment transport styles:

TransportExampleUse when
inlinefile_data: "data:text/plain;base64,..."small local examples, tests, direct API calls
referencefile_url: "ksadk-upload://..."Web UI upload flows and larger local files

Inline data is self-contained but increases request size. References keep the request small, but they are only meaningful to the runtime that created the upload reference. Public docs should not present ksadk-upload://... as a durable URL.

AttachmentContent Endpoint

The hosted runtime downloads ae-upload:// attachment bytes through the KOP Action endpoint:

GET request
GET /agentengine/api/v1/AttachmentContent?FileUri=<uri>
ParameterMeaning
FileUriattachment URI, typically ae-upload://<file_id>; paths without the ae-upload:// prefix are resolved as workspace-relative paths

The response is a byte stream. Content-Type reflects the real MIME type and Content-Disposition carries the original file name.

Local cache materialization

When resolving an ae-upload:// URI, the runtime materializes the downloaded bytes to the local files/ directory and writes a <file_id>.meta.json recording backend=hosted, display_name, mime_type, size_bytes, and local_path. After a session switch or browser refresh, the runner and workspace preview read real file content from the local cache without re-downloading.

Consume Structured Attachment Fields

Business agents should not guess upload paths or parse file_uri themselves. Consume the structured attachment fields from the runner payload instead:

FieldUse when
current_attachmentsprocessing only files/images uploaded in the current user turn
current_attachment_resultsprocessing only the extraction results (text/OCR/metadata) of the current turn
attachmentsthe effective file context for this turn (including session carryover)
attachment_resultsfollow-up questions that should keep working with the prior file
agent.py
def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
    return {
        "messages": payload.get("input_messages", []),
        # only files uploaded in the current turn
        "files": payload.get("current_attachment_results", []),
        # historical attachment context still reachable on follow-ups
        "file_context": payload.get("attachment_results", []),
    }
  • Use current_attachment_results for workflows such as "process the file I just uploaded".
  • Use attachment_results for follow-ups such as "now summarize the second section"; current_attachment_results is expected to be empty on a text-only follow-up.
  • When a tool needs a file handle, take the file_uri from current_attachments / attachments and hand it to the runtime for unified resolution.

Do not assemble host-absolute paths yourself or parse file_uri manually. Hand file_uri to the runtime for unified resolution.

Display Content Versus Runner Content

The runtime keeps separate views of an uploaded file:

ViewPurpose
display_contentshort user-visible message text and attachment names
prompt attachment textextracted or summarized text added to the runner prompt
attachment_resultsstructured metadata, extraction status, warnings, and text excerpts
current_attachment_resultsstructured results only for files in the current user turn

This separation avoids flooding the UI transcript with extracted document text while still giving the runner enough context to answer file-related questions. If your agent needs exact structured file metadata, read attachment_results instead of parsing the display text.

Supported Extraction Paths

AttachmentTypical handling
text filesdecode text using common encodings
PDFnative text extraction, with OCR fallback when available
DOCX/PPTX/XLSX/HTMLdocument-specific text extraction
imagesOCR when OCR dependencies are available
ZIPsafe enumeration with path and size restrictions
binary filesmetadata and warnings only

ZIP handling is intentionally conservative: path traversal, nested archives, executable entries, large entries, and excessive total extracted size are blocked.

ZIP And Archive Boundaries

Archive handling is optimized for safe inspection, not arbitrary extraction. The runtime may list or extract text-like entries, but it can reject:

  • path traversal entries.
  • nested archives.
  • executable files.
  • very large entries.
  • archives whose total expanded size exceeds local limits.

Treat archive results as partial unless status is ok and no warnings are present. Agents should ask the user for a narrower file or a direct text export when archive extraction is incomplete.

Runner Payload Fields

Framework adapters receive these file-related fields:

FieldUse when
input_contentpreserving Responses-style multimodal blocks
input_messagespassing message-native state into LangGraph or a model client
attachmentsusing the effective file context for this turn
attachment_resultsreading extracted text, OCR, or document metadata
current_attachmentschecking files uploaded in the current user turn only
current_attachment_resultsprocessing only newly uploaded files
has_current_filesdeciding whether this turn should override prior file context

For follow-up questions, attachment_results can include the most recent session attachment context even when the current turn has no new file. Use current_attachment_results when the workflow must react only to newly uploaded files.

Framework Adapter Notes

Framework adapters receive the same normalized attachment payload, but they may feed it to the framework differently:

AdapterTypical behavior
ADKconverts text and supported inline bytes into ADK Content / Part values
LangGraphincludes normalized messages, attachment context, and optional image blocks in state
LangChainincludes attachment context in prepared input or the replay prompt
custom hooksreceive structured payload fields and can choose the exact state shape

If an application uses a custom hook, include the attachment fields you actually need:

def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
    return {
        "messages": payload.get("input_messages", []),
        "files": payload.get("current_attachment_results", []),
        "file_context": payload.get("attachment_results", []),
    }

Prefer current_attachment_results for workflows such as "process the file I just uploaded". Prefer attachment_results for follow-up questions such as "now summarize the second section".

Session Carryover

If the current user turn includes attachments, those attachments become the effective context. If a later turn has no new attachment, the runtime can restore the most recent attachment context from the same session.

Session attachment carryover

Use current_attachment_results when the user just uploaded a file. Use attachment_results when follow-up questions should keep working with the prior file.

Resume After Refresh

Resume after refresh

As long as the file_uri is still resolvable (local cache or a reachable ae-upload:// backend), the runner and workspace preview keep reading real file content after a refresh or session resume. Application code needs no special handling.

Default Multimodal Model

New in 0.6.6+

Hosted deployments inject a shared model policy through AGENTENGINE_MODEL_POLICY_JSON. Policy v1 defaults to glm-5.2 as the primary model, kimi-k2.7-code as the multimodal model, and deepseek-v4-pro as the fallback. Multimodal capability (vision / image) is routed to kimi-k2.7-code by default.

SettingPurpose
AGENTENGINE_MODEL_POLICY_JSONplatform-injected shared policy declaring primary / multimodal / fallback
OPENCLAW_IMAGE_MODELexplicit OpenClaw multimodal / image model override, takes precedence over the policy default
HERMES_DEFAULT_MODEL / OPENCLAW_FALLBACK_MODELexplicit environment-variable overrides still take precedence over policy defaults

Precedence: explicit request-level model options > explicit environment variables > policy defaults.

To switch the image / vision model, set it explicitly in request model_options, or set OPENCLAW_IMAGE_MODEL to override the default multimodal model.

Safety Rules

  • Validate file type and size before business processing.
  • Treat uploaded file content as untrusted user input.
  • Do not publish uploaded customer files, OCR text, or local file paths.
  • Prefer placeholder data in docs and tests.
  • Keep large binary fixtures out of the public repository unless reviewed.
  • Prefer file_data or ksadk-upload:// references for local processing; do not assume the runtime will fetch arbitrary remote file_url values.
  • Avoid logging extracted attachment text in CI or public issue templates.

On this page