multi-content-proxy
Pi agent extension that parses image/audio/video inputs and forwards them to an OpenAI-compatible multimodal proxy (e.g. dots.ai).
Package details
Install multi-content-proxy from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:multi-content-proxy- Package
multi-content-proxy- Version
1.0.6- Published
- Aug 23, 2026
- Downloads
- 1,169/mo · 12/wk
- Author
- villji
- License
- MIT
- Types
- extension
- Size
- 81.3 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
multi-content-proxy
A Pi agent extension that parses image, audio, and video inputs and forwards them to an OpenAI-compatible multimodal proxy so the agent can "see", "hear", and "watch" content that the underlying model wouldn't otherwise ingest natively.
It is the generalized sibling of the image-only vision-proxy: the same interception pattern
(<input> hook → describe media through a proxy → inject text into the prompt) is extended to audio and
video, with per-modality provider configuration so you can point each modality at the model that actually
supports it.
Why
Different multimodal models accept different input modalities. Some do images only, some add audio, some add
video. The dots.ai platform exposes an OpenAI-compatible chat-completions endpoint
(described in its #tool-calling-response-example docs) that accepts a mix of text + image + video + audio
content parts. This extension maps each detected media file to the right content-part format and sends it to
the right endpoint, so a non-multimodal coding model can still reason about screenshots, voice memos, and
screen recordings.
Install / enable
From npm (recommended for users)
# global install (applies to all your pi projects)
pi install npm:multi-content-proxy
# or project-local install (scoped to the current project, adds to .pi/settings.json)
pi install npm:multi-content-proxy -l
This downloads the package into pi's managed npm directory and registers it in your
settings.json packages list. Restart (or start a new) pi session and the analyze_media
tool + /multi-content-proxy command become available.
From a local folder (for development)
pi install /absolute/path/to/multi-content-proxy
pi treats an absolute path as a local: source — no network, no copy; it just records the
path in settings.json and loads index.ts via jiti. Great for iterating on the source.
Manual
You can also drop the folder into ~/.pi/agent/extensions/ (auto-discovered) or add the
path to settings.json → packages yourself.
typebox is the only runtime dependency (Pi already provides it; the dependency is declared
for standalone installs).
Concepts
Providers (per-modality)
Because modality support is uneven across models, the extension keeps a base image provider plus optional dedicated audio and video providers.
imageis the base and is always required.audioandvideoare optional. When they are not configured they transparently fall back to the image provider (itsbaseUrl/apiKey/model). This is exactly what you want for an endpoint like dots.ai's that handles all three with one model — set it once onimageand everything works.- To use a different model for audio or video, configure that modality's provider and only the fields you
want; the rest inherits from
image. Clear it again (audio-model clear) to revert to fallback.
Each provider is an OpenAI-compatible chat-completions endpoint:
| Field | Meaning |
|---|---|
baseUrl |
OpenAI-compatible base URL, e.g. https://host/v1 (code appends /chat/completions) |
apiKey |
bearer token (optional for some endpoints) |
model |
multimodal model id understood by the endpoint |
Modes
fallback(default): only send media to the proxy when the active model lacks that modality. Images attached by Pi are still passed through if the model supportsimage. Audio/video are always proxied (Pi models can't ingest them natively).always: proxy every media input regardless of model capabilities (useful to force descriptions).off: disable the extension.
Strategies
- Video
native(default): send the whole file as a singlevideo_urlpart (only works with endpoints that accept video natively, e.g. dots.ai). If the provider rejects the payload (dots.ai returns HTTP 400 for oversized video), the extension automatically retries withframes— so you get the best quality when it works and a graceful fallback when it doesn't.frames:ffmpegextracts up to N preview frames (PNG) + the audio track (WAV). Frames are sent asimage_urlparts and the audio is sent as anaudio_urldata URI (or transcribed ifaudio-strategy=transcribe).
- Audio
describe(default): send the audio inline as anaudio_urldata URI. Note: dots.ai (and this extension's default content-part format) useaudio_url, not OpenAI'sinput_audioshape, which dots.ai rejects.transcribe: POST to the endpoint'saudio/transcriptionsroute (OpenAI Whisper-compatible) and inject the transcript text.
Usage
When enabled, any image/audio/video file you reference in a message is detected and handled automatically.
References can be a local path, an http(s) URL, or Pi's file-mention syntax @file (e.g. @pic.jpg).
For example:
explain what this diagram shows: ./arch.png
summarize this meeting recording: ./standup.mp3
what is happening in this clip: ./demo.mov
You can also drive it deliberately with the built-in tool and command (see below).
The analyze_media tool
The agent can call analyze_media to inspect specific files or URLs on demand:
media— array of 1–20 references: local file paths orhttp(s)URLs (image/audio/video)question— what to ask about the media (required)frames— number of video frames to sample (frames strategy)crop— per-image crop{ image_index, region | box }(requires ffmpeg)
The /multi-content-proxy command
/multi-content-proxy # show current config
/multi-content-proxy fallback|always|off # set mode
# per-modality provider (audio/video fall back to image when unset)
/multi-content-proxy image-model <id>
/multi-content-proxy image-base-url <url>
/multi-content-proxy image-api-key <key>
/multi-content-proxy audio-model <id> # or `audio-model clear`
/multi-content-proxy audio-base-url <url>
/multi-content-proxy audio-api-key <key>
/multi-content-proxy video-model <id> # or `video-model clear`
/multi-content-proxy video-base-url <url>
/multi-content-proxy video-api-key <key>
# shortcuts: model / base-url / api-key == image-*
/multi-content-proxy video-strategy native|frames
/multi-content-proxy audio-strategy describe|transcribe
/multi-content-proxy max-frames <n>
/multi-content-proxy max-bytes <n|Nmb>
/multi-content-proxy ffmpeg <path>
/multi-content-proxy status on|off
/multi-content-proxy consent yes|no|ask
/multi-content-proxy folders add|remove|clear <path>
/multi-content-proxy test <path|url> # call the proxy once and print the result
/multi-content-proxy reset-consent
Configuration
Configure by just telling the assistant (recommended)
You don't need to edit files or run commands. After installing the extension, simply
tell the assistant in natural language and it configures itself via the
configure_multi_content_proxy tool:
帮我配置拓展 multi-content-proxy 的 baseurl 为 https://note3-prev-api.askdiandian.com/v1 模型名为 dots3-note-prev APIKEY为 ak_xxxxxxxx
The English equivalent works too:
configure multi-content-proxy with baseurl https://note3-prev-api.askdiandian.com/v1, model dots3-note-prev, apikey ak_xxx
The tool writes baseUrl, model (image + video) and apiKey to
~/.pi/agent/multi-content-proxy.json. A trailing /chat/completions is stripped
automatically, and audio/video inherit from image unless already set. Optional
consent / mode can also be supplied.
Precedence (highest first): environment variables → persisted JSON
(~/.pi/agent/multi-content-proxy.json) → built-in defaults.
Environment variables
| Variable | Maps to |
|---|---|
MULTI_CONTENT_PROXY_MODE |
mode |
MULTI_CONTENT_PROXY_IMAGE_BASE_URL |
image.baseUrl (falls back to MULTI_CONTENT_PROXY_BASE_URL) |
MULTI_CONTENT_PROXY_IMAGE_API_KEY |
image.apiKey (falls back to MULTI_CONTENT_PROXY_API_KEY) |
MULTI_CONTENT_PROXY_IMAGE_MODEL |
image.model (falls back to MULTI_CONTENT_PROXY_MODEL) |
MULTI_CONTENT_PROXY_AUDIO_BASE_URL/API_KEY/MODEL |
audio provider (inherits image when omitted) |
MULTI_CONTENT_PROXY_VIDEO_BASE_URL/API_KEY/MODEL |
video provider (inherits image when omitted) |
MULTI_CONTENT_PROXY_VIDEO_STRATEGY |
native | frames |
MULTI_CONTENT_PROXY_AUDIO_STRATEGY |
describe | transcribe |
MULTI_CONTENT_PROXY_MAX_BYTES |
size guard (bytes) |
MULTI_CONTENT_PROXY_MAX_FRAMES |
video frame count |
MULTI_CONTENT_PROXY_FFMPEG |
ffmpeg executable path |
MULTI_CONTENT_PROXY_CONSENT |
yes | no | ask |
MULTI_CONTENT_PROXY_STATUS_LINE |
on | off |
MULTI_CONTENT_PROXY_INCLUDE_CONTEXT |
true | false |
MULTI_CONTENT_PROXY_ENABLE_IMAGE/AUDIO/VIDEO |
per-kind enable toggles |
Defaults: mode fallback, image base URL https://note3-prev-api.askdiandian.com/v1 (code appends /chat/completions),
image model dots3-note-prev.
Privacy & consent
Media is base64-encoded and sent to the configured endpoint. Before the first upload you'll be asked for
consent (consent config: ask default, or set yes/no). Local file access is restricted to the cwd and
(optionally) allowedFolders; per-file size is capped by maxBytes. The proxy result is injected into the
prompt inside a clearly delimited <multi_content_proxy> block, and the original media is removed from the
message when the model can't use it natively.
How it works (architecture)
src/types.ts MediaKind / MediaFile / MediaPart / ProxyResult
src/config.ts resolveConfig (env > persisted > defaults), per-modality providers, providerFor()
src/media.ts path/URL/attachment extraction, size+folder guards, ffmpeg frame/audio extraction,
long-audio chunking, buildParts() → OpenAI multimodal content parts
(image_url / video_url / audio_url)
src/proxy.ts callMultimodalProxy() (chat/completions) + callStt() (audio/transcriptions)
index.ts <input> hook (auto media), analyze_media + configure_multi_content_proxy tools, /multi-content-proxy command,
before_agent_start / session_start hooks, consent + rate-limit + LRU cache
Media is mapped to the canonical OpenAI-compatible multimodal content-part format (data-URI parts):
- image →
{ type: "image_url", image_url: { url: "data:..." } } - video (native) →
{ type: "video_url", video_url: { url: "data:..." } } - video (frames) → N
image_urlparts + oneaudio_urlpart - audio →
{ type: "audio_url", audio_url: { url: "data:..." } }(or a transcription)
dots.ai notes
The reference endpoint is dots.ai's platform API. Working configuration (also the default image provider):
baseUrl:https://note3-prev-api.askdiandian.com/v1model:dots3-note-prevapiKey: a dots-ai API key (bearer token)
dots.ai-specific behaviours this extension handles for you:
audio_url, notinput_audio. dots.ai's chat-completions endpoint accepts audio as{ type: "audio_url", audio_url: { url: "data:..." } }. The OpenAIinput_audioshape is rejected.- Long-audio chunking. dots.ai returns an empty response for a single huge audio payload, so audio longer than ~110s is automatically split into segments, analyzed per segment, then merged.
- Video native → frames fallback. Very large
video_urlpayloads are rejected (HTTP 400); the extension retries with theframesstrategy automatically. - Higher token budget. dots.ai is a reasoning model; audio/video calls use
max_tokens: 8192or it spends the whole budget thinking and returns emptycontent.
License
MIT.