@anna-ai/app-runtime

中文文档
0.27.0发布于 2026/10/30MITES Module零依赖近 30 天下载 2149

Anna App browser runtime SDK. Loaded by every Anna App iframe; provides typed proxy over the postMessage RPC bridge to the host (Anna dashboard / anna-app dev harness).

原始使用文档
文档目录12 节
  1. Use inside an Anna App bundle
  2. Wire envelope
  3. RPC timeouts
  4. Where the script comes from
  5. Versioning
  6. Contents
  7. Agent run output cap & truncation (v0.26.0)
  8. Host context: theme & language (v0.24.0)
  9. Media generation (v0.20.0)
  10. Music generation (v0.23.0)
  11. Image async jobs & re-signing (v0.25.0)
  12. Model-aware options (v0.22.0)

@anna-ai/app-runtime

Browser runtime SDK for Anna Apps. A native ES module imported by every Anna App iframe; exports AnnaAppRuntime, which proxies RPC calls over postMessage to the host page (Anna dashboard in production, the anna-app dev harness in local development).

0.3.1+: the SDK is a pure ES module. Load it with <script type="module"> + import { AnnaAppRuntime } from "…". The old classic <script src> + window.AnnaAppRuntime global is removed.

Use inside an Anna App bundle

Make your entry script a module and import the SDK directly:

<!-- index.html -->
<script src="./app.js" type="module"></script>
// app.js
import { AnnaAppRuntime } from "/static/anna-apps/_sdk/latest/index.js";

const anna = await AnnaAppRuntime.connect();

await anna.window.set_title({ title: "My App" });

const out = await anna.tools.invoke({
  tool_id: "tool-twitter-handle-twitter-tool-abcd1234",
  method: "post_tweet",
  args: { text: "hi" },
});

anna.on("entry_payload", (p) => console.log(p));

Don't use inline <script>. The bundle CSP is script-src 'self' <sdk-origin> with no unsafe-inline/nonce, so any inline script (including an inline module that bridges the SDK to a global) is blocked. Always make your entry an external module (<script src="app.js" type="module">) and import the SDK inside it.

Wire envelope

{ kind: "req"|"res"|"event", id, ns, method, args | result | error }

The host validates every call against the app's manifest ACL before forwarding to the backend.

RPC timeouts

Each call has a per-namespace default; apps can override per call:

// Use the namespace default (image: 180s)
const out = await anna.image.generate({ prompt: "..." });

// Override for one call (e.g. high-res / many images)
const out = await anna.image.generate(
  { prompt: "...", n: 4, size: "2048x2048" },
  { timeoutMs: 240000 }
);
NamespaceDefaultRationale
image.*180 sMatches host's upstream provider timeout (Seedream / Qwen / native)
upload.*120 sLarge multipart / negotiate+confirm
agent.*300 ssession.run loops model + tools many turns
llm.*180 sLong completions / large context
everything else30 sstorage / chat / window / prefs / fs / tools / artifact

The timeout is a client-side deadline. The host bridge fetch has no abort signal — the backend always runs to completion, but a timed-out promise rejects with Error("RPC <ns>.<method> timed out after Nms") and the eventual response is discarded.

Where the script comes from

EnvironmentSource
Production (Anna dashboard)matrix-nexus static server, path /static/anna-apps/_sdk/<version>/index.js
Local dev (anna-app dev)@anna-ai/cli harness server resolves this package from its node_modules and serves the same path

The bundle author writes the same import { AnnaAppRuntime } from "/static/anna-apps/_sdk/latest/index.js" either way; the URL is part of the host contract.

Versioning

This package's version tracks dispatcher_version (wire protocol). See packages/VERSIONS.md for the full pin matrix and bump procedure.

Contents

Single file dist/index.js. Native ES module (ends in export { AnnaAppRuntime, ... }), no build step, runs directly in any modern browser via <script type="module"> / import.

Agent run output cap & truncation (v0.26.0)

Host ≥ 1.1.0-beta.200 / dispatcher 0.30.0 (forum #403). The per-call output cap of agent.session.run is min(model output cap, 65 536) clamped to the model's context-budget reserve (10 % of context, ≤ 65 536) — e.g. 65 536 on 1M-context models, 20 000 on 200K-context models. It is disclosed per run and truncation of the final message is now signalled:

const sess = await anna.agent.session({ submode: "auto" });
for await (const ev of sess.run({ content: prompt, maxTokens: 16384 })) {
  if (ev.event === "run_meta") console.log("cap:", ev.max_output_tokens);
  const tc = ev.choices?.[0]?.delta?.task_complete;
  if (tc?.truncated) {
    // status is still "completed": the graph ended normally, but the final
    // assistant message hit the per-call cap. Continue in the same session:
    // sess.run({ content: "continue exactly from: " + tail })
  }
}
  • maxTokens? (1..65536) can only lower the cap — never above platform ∩ model. Omit to use the model's own output cap.
  • task_complete.stop_reason ∈ endTurn | maxTokens | contentFilter | toolUse (same set as llm.complete's stopReason); truncated === (stop_reason === "maxTokens").
  • Older hosts omit max_output_tokens / stop_reason / truncated.

Host context: theme & language (v0.24.0)

The dashboard tells every App which theme and UI language the user chose, so Apps no longer guess from prefers-color-scheme / navigator.language. No manifest grant is involved — these are one-way host→app broadcasts (like auth.refresh), never callable RPC methods.

const anna = await AnnaAppRuntime.connect();

anna.theme;      // 'light' | 'dark'           — already correct before the first paint
anna.themeMode;  // 'light' | 'dark' | 'system' — what the user picked
anna.locale;     // 'en' | 'zh-CN' | …          — dashboard UI language

// Subscribe; both replay the current value once, then fire on every change.
anna.onThemeChange(({ theme }) => { document.documentElement.dataset.theme = theme; });
anna.onLocaleChange(({ lang }) => i18n.setLocale(lang));

Wire details: initial values ride on the iframe URL (…?wid=&t=&theme=light&themeMode=system&lang=zh-CN) and are read synchronously in connect(); live changes arrive as {kind:"event", event:"theme.changed", payload:{theme, mode}} and {event:"locale.changed", payload:{lang}}. Hosts older than this release send neither — the SDK falls back to prefers-color-scheme and navigator.language, so keep a CSS fallback for standalone previews:

:root[data-theme="light"] { /* light tokens */ }
:root[data-theme="dark"]  { /* dark tokens  */ }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) { /* dark tokens */ } }

Media generation (v0.20.0)

Async video jobs + sync audio, gated by the per-app media_grant (manifest must declare ui.host_api.video / .audio):

// One-shot sugar: submit + media_job events + getJob polling fallback
const job = await anna.video.generateAwait(
  { prompt: "Aerial drone shot over a misty pine forest", durationSec: 5,
    resolution: "768p", clientTag: "scene-3-broll" },
  { onProgress: (p) => bar.update(p), signal: abortController.signal }
);
video.src = job.result.url;         // presigned GET, ~30 min TTL

// URL expired (403)? Re-sign via getJob:
const fresh = await anna.video.getJob({ jobId: job.jobId });

// Raw job API: generate / getJob / cancelJob / listJobs
const { jobId, estimatedCostCU } = await anna.video.generate({ prompt: "..." });

// Sync TTS / STT
const speech = await anna.audio.speak({ text: "Welcome!", voice: "Cherry" });
const { text } = await anna.audio.transcribe({ audioUrl: "https://..." });

// Capability discovery: models, parameter space, CU pricing, remaining quota
const catalog = await anna.llm.catalog({ serviceType: "video_gen" });

Music generation (v0.23.0)

Async music jobs (mjob_…) with the exact same job API, media_job events and pre-hold → settle → refund billing as video. Requires ui.host_api.music + per-app media_grant.music.

// Capability discovery first — models differ (lyrics vs prompt-embedded lyrics,
// fixed vs range length, mp3 vs wav, reference image …). Nothing is silently
// downgraded: an unsupported field fails with MUSIC_FEATURE_UNSUPPORTED (-32165)
// and error.data.alternatives lists models that do support it.
const { models } = await anna.llm.catalog({ serviceType: "music_gen" });
const m = models.find(x => x.isPlatformDefault);   // fal-ai/minimax-music/v2.6

const song = await anna.music.generateAwait(
  { prompt: "melancholic cinematic piano, slow ~70 BPM, rainy night",
    lyrics: "[Verse 1]\n…\n[Chorus]\n…",       // only when m.capabilities.lyrics
    model: m.modelName,
    clientTag: "scene-3-score",
    idempotencyKey: "canvas-node-8f3a-v2" },     // 24h replay → same job, no re-charge
  { onProgress: (p) => bar.update(p), signal: abortController.signal }
);
audio.src = song.result.url;     // presigned GET, ~30 min TTL; song.result.lyrics when returned
song.idempotentReplay;           // true when the key hit an existing job

// Range-length models (ElevenLabs / Stable Audio / ACE-Step) take durationSec;
// fixed-length models (MiniMax / Lyria) reject it. Instrumental-only models
// (Stable Audio) reject lyrics and instrumental:false.
await anna.music.generate({ prompt: "ambient pad", model: "fal-ai/ace-step", durationSec: 30, instrumental: true });

Image async jobs & re-signing (v0.25.0)

anna.image.generate / anna.image.edit stay synchronous and unchanged. For 2K/4K or quality: "high" renders (60–120 s) opt into the same async-job shape as video / music — ijob_… ids, media_job events, pre-hold → settle (on the actual number of images) → refund — and re-sign any already-paid artifact for 7 days instead of losing it after the 30-min URL TTL. Requires ui.host_api.image listing the new method names + the existing image_grant (allowGenerate / allowEdit; no new grant).

// Same request body as image.generate, plus clientTag / idempotencyKey.
const job = await anna.image.generateAwait(
  { prompt: "A bold art-deco poster of the planet Mars", n: 2,
    model: "fal-ai/nano-banana-2", aspect_ratio: "9:16", params: { resolution: "4K" },
    clientTag: "poster-v3", idempotencyKey: `poster-v3:${attempt}` },   // 24h replay → same job, no re-charge
  { onProgress: (p) => status.textContent = `${p.state} · queue ${p.queuePosition ?? "-"}`,
    signal: abortController.signal }                                       // → image.cancelJob
);
job.result.images;        // [{url, r2Key, mimeType, width, height, expiresIn}] — url re-signed by getJob
job.billedCostCU;         // settled on actual output (≤ estimatedCostCU)

// Edit (optionally mask-based) as a job:
await anna.image.editAwait({ image_url, prompt: "replace the sky with aurora", mask_url, model: "openai/gpt-image-2" });

// Reload recovery / correlation — same four-pack as video / music:
const { jobs } = await anna.image.listJobs({ state: "running", clientTag: "poster-v3" });
const fresh = await anna.image.getJob({ jobId });      // every result.images[].url re-signed (~30 min)

// Re-sign already-paid artifacts (sync `images[].r2_key` or job `r2Key`), up to 7 days:
const { urls } = await anna.image.resign({ r2Keys: [img.images[0].r2_key, job.result.images[0].r2Key] });
// One key that is not yours → whole call fails with IMAGE_ASSET_NOT_FOUND (details.data.index).

Errors surface as HostRpcError names: IMAGE_JOB_NOT_FOUND, IMAGE_JOB_NOT_CANCELLABLE, IMAGE_CONTENT_REJECTED (also replaces the sync path's former APP_PROVIDER_ERROR for provider safety rejections), IMAGE_JOB_EXPIRED, IMAGE_ASSET_NOT_FOUND; error.details.data now carries allowed / alternatives / index. anna.llm.catalog image entries report capabilities.jobs and, for image_edit, the real options.n.max.

Model-aware options (v0.22.0)

Every TTS / image model describes its knobs in capabilities.options (schema v2: {type, min, max, step, values, default, scope, …}). Pass any of them in params; keys a model does not declare are dropped and echoed in ignoredParams, never surcharged.

const { models } = await anna.llm.catalog({ serviceType: "tts", voiceLanguage: "zh" });
const tts = models.find(m => m.isUserPreferred) ?? models.find(m => m.isPlatformDefault);
const out = await anna.audio.speak({
  text: "Welcome!",
  model: tts.modelName,
  voice: tts.capabilities.defaultVoice,
  params: { stability: 0.3, similarity_boost: 0.8 },   // keys from capabilities.options
});
out.params;         // what reached the provider
out.ignoredParams;  // e.g. ["similarity_boost"] on a model that only exposes stability

// Image: explicit model + size policy. `fit: "strict"` fails with
// IMAGE_SIZE_UNSUPPORTED (+ data.allowed) instead of snapping to the nearest size.
const img = await anna.image.generate({
  prompt: "A bold art-deco poster of the planet Mars",
  model: "fal-ai/nano-banana-2",
  aspect_ratio: "9:16",
  params: { resolution: "2K" },
  fit: "strict",
});
img.size; img.sizeAdjusted; img.images[0].width;   // real decoded dimensions

Render capabilities.options as a form with the shared ModelOptionsForm component — the same file the platform uses on /llm and in the Developer Console Playground.

Costs are pre-charged from the estimate shown in estimatedCostCU and fully refunded on failure/cancel/expiry. Aborting generateAwait cancels the job (queued = full refund; running cancel is best-effort).

版本与发布记录

@anna-ai/app-runtime 共发布 22 个版本,首次发布于 2026/04/30;过去 12 个月发布了 22 个版本。

版本发布日期距今
0.27.0latest2026/10/308 天前
0.26.02026/10/2315 天前
0.25.02026/10/2117 天前
0.23.02026/10/2018 天前
0.21.02026/10/1622 天前
0.20.02026/10/0929 天前

关于 @anna-ai/app-runtime 的常见问题

@anna-ai/app-runtime 是什么?

Anna App browser runtime SDK. Loaded by every Anna App iframe; provides typed proxy over the postMessage RPC bridge to the host (Anna dashboard / anna-app dev harness).

如何安装 @anna-ai/app-runtime?

在项目目录运行 npm install @anna-ai/app-runtime,或使用 pnpm add / yarn add / bun add。

@anna-ai/app-runtime 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/anna-ai__app-runtime 是否存在。

@anna-ai/app-runtime 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@anna-ai/app-runtime 可以商用吗?

@anna-ai/app-runtime 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@anna-ai/app-runtime 的下载量有多少?

根据 npm 官方下载统计,@anna-ai/app-runtime 在 2026/10/06 – 2026/11/04 这 30 天内被下载 2,149 次,日均约 72 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@anna-ai/app-runtime 还在维护吗?

最新版本 0.27.0 发布于 2026/10/30(8 天前);至今共发布 22 个版本,其中过去 12 个月发布了 22 个;首次发布于 2026/04/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。