> ## Documentation Index
> Fetch the complete documentation index at: https://docs2.openclaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Các trình trợ giúp thời gian chạy Plugin

Tham chiếu cho đối tượng `api.runtime` được chèn vào mọi plugin trong quá trình đăng ký. Dùng các helper này thay vì nhập trực tiếp các phần nội bộ của host.

<CardGroup cols={2}>
  <Card title="Channel plugins" href="/vi/plugins/sdk-channel-plugins">
    Hướng dẫn từng bước sử dụng các helper này trong ngữ cảnh cho plugin kênh.
  </Card>

  <Card title="Provider plugins" href="/vi/plugins/sdk-provider-plugins">
    Hướng dẫn từng bước sử dụng các helper này trong ngữ cảnh cho plugin nhà cung cấp.
  </Card>
</CardGroup>

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
register(api) {
  const runtime = api.runtime;
}
```

## Tải và ghi cấu hình

Ưu tiên cấu hình đã được truyền vào đường dẫn gọi đang hoạt động, ví dụ `api.config` trong khi đăng ký hoặc đối số `cfg` trong callback của kênh/nhà cung cấp. Cách này giữ cho một snapshot tiến trình duy nhất chảy xuyên suốt công việc thay vì phân tích lại cấu hình trên các đường dẫn nóng.

Chỉ dùng `api.runtime.config.current()` khi một handler tồn tại lâu cần snapshot tiến trình hiện tại và không có cấu hình nào được truyền vào hàm đó. Giá trị trả về là chỉ đọc; hãy clone hoặc dùng helper mutation trước khi chỉnh sửa.

Factory công cụ nhận `ctx.runtimeConfig` cùng với `ctx.getRuntimeConfig()`. Dùng getter bên trong callback `execute` của một công cụ tồn tại lâu khi cấu hình có thể thay đổi sau khi định nghĩa công cụ đã được tạo.

Lưu thay đổi bằng `api.runtime.config.mutateConfigFile(...)` hoặc `api.runtime.config.replaceConfigFile(...)`. Mỗi lần ghi phải chọn một chính sách `afterWrite` rõ ràng:

* `afterWrite: { mode: "auto" }` để trình quyết định tải lại Gateway xử lý.
* `afterWrite: { mode: "restart", reason: "..." }` buộc khởi động lại sạch khi bên ghi biết tải lại nóng là không an toàn.
* `afterWrite: { mode: "none", reason: "..." }` chỉ chặn tải lại/khởi động lại tự động khi bên gọi sở hữu bước tiếp theo.

Các helper mutation trả về `afterWrite` cùng với bản tóm tắt `followUp` có kiểu để bên gọi có thể ghi log hoặc kiểm thử xem họ đã yêu cầu khởi động lại hay chưa. Gateway vẫn sở hữu thời điểm việc khởi động lại đó thực sự diễn ra.

`api.runtime.config.loadConfig()` và `api.runtime.config.writeConfigFile(...)` là các helper tương thích đã không còn được khuyến nghị dưới `runtime-config-load-write`. Chúng cảnh báo một lần trong runtime, và vẫn có sẵn cho plugin bên ngoài cũ trong giai đoạn chuyển đổi. Plugin tích hợp sẵn không được dùng chúng; các guard biên cấu hình sẽ thất bại nếu mã plugin gọi chúng hoặc nhập các helper đó từ các subpath của plugin SDK.

Với các import SDK trực tiếp, hãy dùng các subpath cấu hình tập trung thay vì barrel tương thích rộng
`openclaw/plugin-sdk/config-runtime`: `config-contracts` cho
kiểu, `plugin-config-runtime` cho các assertion cấu hình đã tải và tra cứu entry
plugin, `runtime-config-snapshot` cho snapshot tiến trình hiện tại, và
`config-mutation` cho thao tác ghi. Kiểm thử plugin tích hợp sẵn nên mock trực tiếp các
subpath tập trung này thay vì mock barrel tương thích rộng.

Mã runtime nội bộ của OpenClaw có cùng định hướng: tải cấu hình một lần tại biên CLI, Gateway, hoặc tiến trình, rồi truyền giá trị đó xuyên suốt. Các lần ghi mutation thành công sẽ làm mới snapshot runtime của tiến trình và tăng revision nội bộ của nó; cache tồn tại lâu nên dựa trên cache key do runtime sở hữu thay vì tự serialize cấu hình cục bộ. Các module runtime tồn tại lâu có scanner không khoan nhượng với các lệnh gọi `loadConfig()` xung quanh; dùng một `cfg` được truyền vào, một `context.getRuntimeConfig()` của request, hoặc `getRuntimeConfig()` tại một biên tiến trình rõ ràng.

Đường dẫn thực thi nhà cung cấp và kênh phải dùng snapshot cấu hình runtime đang hoạt động, không phải snapshot tệp được trả về để đọc lại hoặc chỉnh sửa cấu hình. Snapshot tệp giữ nguyên các giá trị nguồn như marker SecretRef cho UI và thao tác ghi; callback nhà cung cấp cần góc nhìn runtime đã được phân giải. Khi một helper có thể được gọi với snapshot nguồn đang hoạt động hoặc snapshot runtime đang hoạt động, hãy đi qua `selectApplicableRuntimeConfig()` trước khi đọc thông tin xác thực.

## Tiện ích runtime tái sử dụng

Dùng các dữ kiện `botLoopProtection` đầu vào cho tin nhắn đầu vào do bot tạo. Core áp dụng guard cửa sổ trượt trong bộ nhớ dùng chung trước khi ghi session và dispatch, mà không gắn chính sách vào một kênh duy nhất. Guard theo dõi các khóa `(scopeId, conversationId, participant pair)`, đếm cả hai chiều của một cặp cùng nhau, áp dụng cooldown sau khi vượt quá ngân sách cửa sổ, và opportunistically dọn các entry không hoạt động.

Plugin kênh hiển thị hành vi này cho operator nên ưu tiên shape `channels.defaults.botLoopProtection` dùng chung cho ngân sách baseline, sau đó chồng các override riêng cho kênh/nhà cung cấp lên trên. Cấu hình dùng chung sử dụng giây vì đây là phần hướng tới người dùng:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type ChannelBotLoopProtectionConfig = {
  enabled?: boolean;
  maxEventsPerWindow?: number;
  windowSeconds?: number;
  cooldownSeconds?: number;
};
```

Truyền các dữ kiện cặp bot đã chuẩn hóa cùng với turn đã phân giải. Core phân giải mặc định, chuyển đổi đơn vị, và ngữ nghĩa `enabled`:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
return {
  channel: "example",
  routeSessionKey,
  storePath,
  ctxPayload,
  recordInboundSession,
  runDispatch,
  botLoopProtection: {
    scopeId: "account-1",
    conversationId: "channel-1",
    senderId: "bot-a",
    receiverId: "bot-b",
    config: channelConfig.botLoopProtection,
    defaultsConfig: runtimeConfig.channels?.defaults?.botLoopProtection,
    defaultEnabled: allowBotsMode !== "off",
  },
};
```

Chỉ dùng trực tiếp `openclaw/plugin-sdk/pair-loop-guard-runtime` cho các vòng lặp sự kiện
hai bên tùy chỉnh không đi qua runner phản hồi đầu vào dùng chung.

## Namespace runtime

<AccordionGroup>
  <Accordion title="api.runtime.agent">
    Danh tính agent, thư mục, và quản lý session.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    // Resolve the agent's working directory
    const agentDir = api.runtime.agent.resolveAgentDir(cfg);

    // Resolve agent workspace
    const workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(cfg);

    // Get agent identity
    const identity = api.runtime.agent.resolveAgentIdentity(cfg);

    // Get default thinking level
    const thinking = api.runtime.agent.resolveThinkingDefault({
      cfg,
      provider,
      model,
    });

    // Validate a user-provided thinking level against the active provider profile
    const policy = api.runtime.agent.resolveThinkingPolicy({ provider, model });
    const level = api.runtime.agent.normalizeThinkingLevel("extra high");
    if (level && policy.levels.some((entry) => entry.id === level)) {
      // pass level to an embedded run
    }

    // Get agent timeout
    const timeoutMs = api.runtime.agent.resolveAgentTimeoutMs(cfg);

    // Ensure workspace exists
    await api.runtime.agent.ensureAgentWorkspace(cfg);

    // Run an embedded agent turn
    const result = await api.runtime.agent.runEmbeddedAgent({
      sessionId: "my-plugin:task-1",
      runId: crypto.randomUUID(),
      workspaceDir: api.runtime.agent.resolveAgentWorkspaceDir(cfg),
      prompt: "Summarize the latest changes",
      timeoutMs: api.runtime.agent.resolveAgentTimeoutMs(cfg),
    });
    ```

    `runEmbeddedAgent(...)` là helper trung lập để bắt đầu một turn agent OpenClaw bình thường từ mã plugin. Nó dùng cùng cơ chế phân giải nhà cung cấp/model và lựa chọn agent-harness như các phản hồi được kích hoạt bởi kênh.

    `runEmbeddedPiAgent(...)` vẫn là alias tương thích đã không còn được khuyến nghị cho các plugin hiện có. Mã mới nên dùng `runEmbeddedAgent(...)`.

    `resolveThinkingPolicy(...)` trả về các mức thinking được nhà cung cấp/model hỗ trợ và mặc định tùy chọn. Plugin nhà cung cấp sở hữu profile riêng theo model thông qua các hook thinking của chúng, vì vậy plugin công cụ nên gọi helper runtime này thay vì nhập hoặc sao chép danh sách nhà cung cấp.

    `normalizeThinkingLevel(...)` chuyển đổi văn bản người dùng như `on`, `x-high`, hoặc `extra high` thành mức lưu trữ chuẩn trước khi kiểm tra nó với policy đã phân giải.

    **Helper kho session** nằm dưới `api.runtime.agent.session`:

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const entry = api.runtime.agent.session.getSessionEntry({ agentId, sessionKey });
    for (const { sessionKey, entry } of api.runtime.agent.session.listSessionEntries({ agentId })) {
      // Iterate session rows without depending on the legacy sessions.json shape.
    }
    await api.runtime.agent.session.patchSessionEntry({
      agentId,
      sessionKey,
      update: (entry) => ({ thinkingLevel: "high" }),
    });

    const storePath = api.runtime.agent.session.resolveStorePath(cfg.session?.store, { agentId });
    await api.runtime.agent.session.runWithWorkAdmission(
      { storePath, sessionKey },
      async (signal) => {
        // Create or update the session, then pass signal to the admitted agent run.
      },
    );
    ```

    Ưu tiên `getSessionEntry(...)`, `listSessionEntries(...)`, `patchSessionEntry(...)`, hoặc `upsertSessionEntry(...)` cho các workflow session. Các helper này định địa chỉ session bằng danh tính agent/session để plugin không phụ thuộc vào shape lưu trữ `sessions.json` kế thừa. Dùng `preserveActivity: true` cho các bản vá chỉ metadata không nên làm mới hoạt động session, và chỉ dùng `replaceEntry: true` khi callback trả về một entry hoàn chỉnh và các trường đã xóa phải tiếp tục bị xóa.

    Dùng `runWithWorkAdmission(...)` khi plugin bắt đầu công việc trên một session đã được lưu bền vững. Callback từ chối session đã lưu trữ hoặc bị thay thế đồng thời, giữ các mutation lưu trữ/reset/xóa được phối hợp xuyên suốt đến khi hoàn tất, và nhận một `AbortSignal` phải được chuyển tiếp đến agent run.

    Để đọc và ghi transcript, nhập `openclaw/plugin-sdk/session-transcript-runtime` và dùng `resolveSessionTranscriptIdentity(...)`, `resolveSessionTranscriptTarget(...)`, `readSessionTranscriptEvents(...)`, `appendSessionTranscriptMessageByIdentity(...)`, `publishSessionTranscriptUpdateByIdentity(...)`, hoặc `withSessionTranscriptWriteLock(...)` với `{ agentId, sessionKey, sessionId }`. Các API này cho phép plugin xác định một transcript, đọc các sự kiện của nó, thêm tin nhắn, phát hành cập nhật, và chạy các thao tác liên quan dưới cùng một write lock transcript. Việc truyền `sessionFile`, dùng `resolveSessionTranscriptLegacyFileTarget(...)`, hoặc nhập các API cấp thấp `appendSessionTranscriptMessage(...)` / `emitSessionTranscriptUpdate(...)` từ `openclaw/plugin-sdk/agent-harness-runtime` đã không còn được khuyến nghị; các đường dẫn đó chỉ tồn tại cho mã kế thừa vốn đã nhận một artifact transcript đang hoạt động.

    `loadSessionStore(...)`, `saveSessionStore(...)`, `updateSessionStore(...)`, `resolveSessionFilePath(...)`, và `resolveAndPersistSessionFile(...)` là các helper tương thích đã không còn được khuyến nghị cho plugin vẫn cố ý phụ thuộc vào shape whole-store hoặc transcript-file kế thừa. Mã plugin mới không được dùng các helper đó, và các caller hiện có nên di chuyển sang helper entry và helper danh tính transcript.
  </Accordion>

  <Accordion title="api.runtime.agent.defaults">
    Hằng số model và nhà cung cấp mặc định:

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const model = api.runtime.agent.defaults.model; // e.g. "anthropic/claude-sonnet-4-6"
    const provider = api.runtime.agent.defaults.provider; // e.g. "anthropic"
    ```
  </Accordion>

  <Accordion title="api.runtime.llm">
    Chạy một completion văn bản do host sở hữu mà không cần nhập phần nội bộ của nhà cung cấp hoặc
    sao chép việc chuẩn bị model/auth/base URL của OpenClaw.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const result = await api.runtime.llm.complete({
      messages: [{ role: "user", content: "Summarize this transcript." }],
      purpose: "my-plugin.summary",
      maxTokens: 512,
      temperature: 0.2,
    });
    ```

    Helper dùng cùng đường dẫn chuẩn bị simple-completion như runtime
    tích hợp sẵn của OpenClaw và snapshot cấu hình runtime do host sở hữu. Context engine
    nhận capability `llm.complete` gắn với session, vì vậy các lệnh gọi model dùng agent của
    session đang hoạt động và không âm thầm fallback về agent mặc định. Kết quả
    bao gồm attribution nhà cung cấp/model/agent cùng với token,
    cache, và mức sử dụng chi phí ước tính đã chuẩn hóa khi có sẵn.

    <Warning>
      Ghi đè mô hình yêu cầu người vận hành chọn tham gia qua `plugins.entries.<id>.llm.allowModelOverride: true` trong cấu hình. Dùng `plugins.entries.<id>.llm.allowedModels` để giới hạn các plugin đáng tin cậy vào những mục tiêu `provider/model` chuẩn cụ thể. Hoàn tất giữa các tác tử yêu cầu `plugins.entries.<id>.llm.allowAgentIdOverride: true`.
    </Warning>
  </Accordion>

  <Accordion title="api.runtime.subagent">
    Khởi chạy và quản lý các lượt chạy subagent nền.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    // Start a subagent run
    const { runId } = await api.runtime.subagent.run({
      sessionKey: "agent:main:subagent:search-helper",
      message: "Expand this query into focused follow-up searches.",
      provider: "openai", // optional override
      model: "gpt-4.1-mini", // optional override
      deliver: false,
    });

    // Wait for completion
    const result = await api.runtime.subagent.waitForRun({ runId, timeoutMs: 30000 });

    // Read session messages
    const { messages } = await api.runtime.subagent.getSessionMessages({
      sessionKey: "agent:main:subagent:search-helper",
      limit: 10,
    });

    // Delete a session
    await api.runtime.subagent.deleteSession({
      sessionKey: "agent:main:subagent:search-helper",
    });
    ```

    <Warning>
      Ghi đè mô hình (`provider`/`model`) yêu cầu người vận hành chọn tham gia qua `plugins.entries.<id>.subagent.allowModelOverride: true` trong cấu hình. Plugin không đáng tin cậy vẫn có thể chạy subagent, nhưng các yêu cầu ghi đè sẽ bị từ chối.
    </Warning>

    `deleteSession(...)` có thể xóa các phiên do cùng plugin tạo qua `api.runtime.subagent.run(...)`. Xóa phiên tùy ý của người dùng hoặc người vận hành vẫn yêu cầu một yêu cầu Gateway có phạm vi quản trị.
  </Accordion>

  <Accordion title="api.runtime.nodes">
    Liệt kê các nút đã kết nối và gọi một lệnh trên máy chủ nút từ mã plugin được Gateway tải hoặc từ các lệnh CLI của plugin. Dùng mục này khi plugin sở hữu công việc cục bộ trên một thiết bị đã ghép đôi, chẳng hạn cầu nối trình duyệt hoặc âm thanh trên một máy Mac khác.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const { nodes } = await api.runtime.nodes.list({ connected: true });

    const result = await api.runtime.nodes.invoke({
      nodeId: "mac-studio",
      command: "my-plugin.command",
      params: { action: "start" },
      timeoutMs: 30000,
    });
    ```

    Bên trong Gateway, runtime này chạy trong cùng tiến trình. Trong các lệnh CLI của plugin, nó gọi Gateway đã cấu hình qua RPC, vì vậy các lệnh như `openclaw googlemeet recover-tab` có thể kiểm tra các nút đã ghép đôi từ terminal. Lệnh nút vẫn đi qua quy trình ghép đôi nút Gateway thông thường, danh sách lệnh cho phép, chính sách gọi nút của plugin và xử lý lệnh cục bộ trên nút.

    Các plugin phơi bày lệnh nguy hiểm trên máy chủ nút nên đăng ký chính sách gọi nút bằng `api.registerNodeInvokePolicy(...)`. Chính sách chạy trong Gateway sau các bước kiểm tra danh sách lệnh cho phép và trước khi lệnh được chuyển tiếp đến nút, vì vậy các lệnh gọi trực tiếp `node.invoke` và công cụ plugin cấp cao hơn dùng chung cùng một đường thực thi cưỡng chế.

    <Warning>
      Trường tùy chọn `scopes` yêu cầu phạm vi người vận hành Gateway cho lượt gọi. OpenClaw chỉ tôn trọng trường này đối với plugin đóng gói sẵn và các bản cài đặt plugin chính thức đáng tin cậy; yêu cầu từ plugin khác sẽ không nâng quyền cho lệnh gọi. Chỉ dùng trường này khi một plugin đáng tin cậy phải gọi lệnh nút với phạm vi Gateway chặt chẽ hơn, chẳng hạn `operator.admin`.
    </Warning>
  </Accordion>

  <Accordion title="api.runtime.tasks.managedFlows">
    Liên kết runtime Luồng tác vụ với một khóa phiên OpenClaw hiện có hoặc ngữ cảnh công cụ đáng tin cậy, rồi tạo và quản lý Luồng tác vụ mà không cần truyền chủ sở hữu trong mọi lệnh gọi.

    Luồng tác vụ theo dõi trạng thái quy trình làm việc nhiều bước bền vững. Nó không phải bộ lập lịch:
    dùng Cron hoặc `api.session.workflow.scheduleSessionTurn(...)` cho các lần đánh thức trong tương lai, rồi dùng `managedFlows` từ lượt đã lập lịch khi công việc đó
    cần trạng thái luồng, tác vụ con, chờ hoặc hủy.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const taskFlow = api.runtime.tasks.managedFlows.fromToolContext(ctx);

    const created = taskFlow.createManaged({
      controllerId: "my-plugin/review-batch",
      goal: "Review new pull requests",
    });

    const child = taskFlow.runTask({
      flowId: created.flowId,
      runtime: "acp",
      childSessionKey: "agent:main:subagent:reviewer",
      task: "Review PR #123",
      status: "running",
      startedAt: Date.now(),
    });

    const waiting = taskFlow.setWaiting({
      flowId: created.flowId,
      expectedRevision: created.revision,
      currentStep: "await-human-reply",
      waitJson: { kind: "reply", channel: "telegram" },
    });
    ```

    Dùng `bindSession({ sessionKey, requesterOrigin })` khi bạn đã có khóa phiên OpenClaw đáng tin cậy từ lớp liên kết riêng của mình. Không liên kết từ dữ liệu nhập thô của người dùng.
  </Accordion>

  <Accordion title="api.runtime.tts">
    Tổng hợp văn bản thành giọng nói.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    // Standard TTS
    const clip = await api.runtime.tts.textToSpeech({
      text: "Hello from OpenClaw",
      cfg: api.config,
    });

    // Telephony-optimized TTS
    const telephonyClip = await api.runtime.tts.textToSpeechTelephony({
      text: "Hello from OpenClaw",
      cfg: api.config,
    });

    // List available voices
    const voices = await api.runtime.tts.listVoices({
      provider: "elevenlabs",
      cfg: api.config,
    });
    ```

    Dùng cấu hình lõi `messages.tts` và lựa chọn nhà cung cấp. Trả về bộ đệm âm thanh PCM + tốc độ lấy mẫu.
  </Accordion>

  <Accordion title="api.runtime.mediaUnderstanding">
    Phân tích hình ảnh, âm thanh và video.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    // Describe an image
    const image = await api.runtime.mediaUnderstanding.describeImageFile({
      filePath: "/tmp/inbound-photo.jpg",
      cfg: api.config,
      agentDir: "/tmp/agent",
    });

    // Transcribe audio
    const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
      filePath: "/tmp/inbound-audio.ogg",
      cfg: api.config,
      mime: "audio/ogg", // optional, for when MIME cannot be inferred
    });

    // Describe a video
    const video = await api.runtime.mediaUnderstanding.describeVideoFile({
      filePath: "/tmp/inbound-video.mp4",
      cfg: api.config,
    });

    // Generic file analysis
    const result = await api.runtime.mediaUnderstanding.runFile({
      filePath: "/tmp/inbound-file.pdf",
      cfg: api.config,
    });

    // Structured image extraction through a specific provider/model.
    // Include at least one image; text inputs are supplemental context.
    const evidence = await api.runtime.mediaUnderstanding.extractStructuredWithModel({
      provider: "codex",
      model: "gpt-5.5",
      input: [
        {
          type: "image",
          buffer: receiptImageBuffer,
          fileName: "receipt.png",
          mime: "image/png",
        },
        { type: "text", text: "Prefer the printed total over handwritten notes." },
      ],
      instructions: "Extract vendor, total, and searchable tags.",
      schemaName: "receipt.evidence",
      jsonSchema: {
        type: "object",
        properties: {
          vendor: { type: "string" },
          total: { type: "number" },
          tags: { type: "array", items: { type: "string" } },
        },
        required: ["vendor", "total"],
      },
      cfg: api.config,
    });
    ```

    Trả về `{ text: undefined }` khi không có đầu ra nào được tạo ra (ví dụ: dữ liệu nhập bị bỏ qua).

    <Info>
      `api.runtime.stt.transcribeAudioFile(...)` vẫn là bí danh tương thích cho `api.runtime.mediaUnderstanding.transcribeAudioFile(...)`.
    </Info>
  </Accordion>

  <Accordion title="api.runtime.imageGeneration">
    Tạo hình ảnh.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const result = await api.runtime.imageGeneration.generate({
      prompt: "A robot painting a sunset",
      cfg: api.config,
    });

    const providers = api.runtime.imageGeneration.listProviders({ cfg: api.config });
    ```
  </Accordion>

  <Accordion title="api.runtime.webSearch">
    Tìm kiếm trên web.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const providers = api.runtime.webSearch.listProviders({ config: api.config });

    const result = await api.runtime.webSearch.search({
      config: api.config,
      args: { query: "OpenClaw plugin SDK", count: 5 },
    });
    ```
  </Accordion>

  <Accordion title="api.runtime.media">
    Tiện ích media cấp thấp.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const webMedia = await api.runtime.media.loadWebMedia(url);
    const mime = await api.runtime.media.detectMime(buffer);
    const kind = api.runtime.media.mediaKindFromMime("image/jpeg"); // "image"
    const isVoice = api.runtime.media.isVoiceCompatibleAudio(filePath);
    const metadata = await api.runtime.media.getImageMetadata(filePath);
    const resized = await api.runtime.media.resizeToJpeg(buffer, { maxWidth: 800 });
    const terminalQr = await api.runtime.media.renderQrTerminal("https://openclaw.ai");
    const pngQr = await api.runtime.media.renderQrPngBase64("https://openclaw.ai", {
      scale: 6, // 1-12
      marginModules: 4, // 0-16
    });
    const pngQrDataUrl = await api.runtime.media.renderQrPngDataUrl("https://openclaw.ai");
    const tmpRoot = resolvePreferredOpenClawTmpDir();
    const pngQrFile = await api.runtime.media.writeQrPngTempFile("https://openclaw.ai", {
      tmpRoot,
      dirPrefix: "my-plugin-qr-",
      fileName: "qr.png",
    });
    ```
  </Accordion>

  <Accordion title="api.runtime.config">
    Ảnh chụp cấu hình runtime hiện tại và ghi cấu hình theo giao dịch. Ưu tiên
    cấu hình đã được truyền vào đường gọi đang hoạt động; chỉ dùng
    `current()` khi trình xử lý cần trực tiếp ảnh chụp tiến trình.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const cfg = api.runtime.config.current();
    await api.runtime.config.mutateConfigFile({
      afterWrite: { mode: "auto" },
      mutate(draft) {
        draft.plugins ??= {};
      },
    });
    ```

    `mutateConfigFile(...)` và `replaceConfigFile(...)` trả về một giá trị `followUp`,
    ví dụ `{ mode: "restart", requiresRestart: true, reason }`,
    ghi lại ý định của bên ghi mà không lấy quyền điều khiển khởi động lại khỏi
    Gateway.
  </Accordion>

  <Accordion title="api.runtime.system">
    Tiện ích cấp hệ thống.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    await api.runtime.system.enqueueSystemEvent(event);
    api.runtime.system.requestHeartbeat({
      source: "other",
      intent: "event",
      reason: "plugin-event",
    });
    api.runtime.system.requestHeartbeatNow({ reason: "plugin-event" }); // Deprecated compatibility alias.
    const output = await api.runtime.system.runCommandWithTimeout(cmd, args, opts);
    const hint = api.runtime.system.formatNativeDependencyHint(pkg);
    ```

    `runCommandWithTimeout(...)` trả về `stdout` và `stderr` đã thu thập, số lượng
    cắt bớt tùy chọn, `code`, `signal`, `killed`, `termination`, và
    `noOutputTimedOut`. Kết quả hết thời gian chờ và hết thời gian chờ do không có đầu ra báo cáo `code: 124`
    khi tiến trình con không cung cấp mã thoát khác không. Các lần thoát bằng tín hiệu
    không do hết thời gian chờ vẫn có thể trả về `code: null`, vì vậy hãy dùng `termination` và
    `noOutputTimedOut` để phân biệt lý do hết thời gian chờ.
  </Accordion>

  <Accordion title="api.runtime.events">
    Đăng ký sự kiện.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    api.runtime.events.onAgentEvent((event) => {
      /* ... */
    });
    api.runtime.events.onSessionTranscriptUpdate((update) => {
      /* ... */
    });
    ```
  </Accordion>

  <Accordion title="api.runtime.logging">
    Ghi log.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const verbose = api.runtime.logging.shouldLogVerbose();
    const childLogger = api.runtime.logging.getChildLogger({ plugin: "my-plugin" }, { level: "debug" });
    ```
  </Accordion>

  <Accordion title="api.runtime.modelAuth">
    Phân giải xác thực mô hình và nhà cung cấp.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const auth = await api.runtime.modelAuth.getApiKeyForModel({ model, cfg });
    const providerAuth = await api.runtime.modelAuth.resolveApiKeyForProvider({
      provider: "openai",
      cfg,
    });
    ```
  </Accordion>

  <Accordion title="api.runtime.state">
    Phân giải thư mục trạng thái và kho lưu trữ dạng khóa được hỗ trợ bởi SQLite.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const stateDir = api.runtime.state.resolveStateDir(process.env);
    const store = api.runtime.state.openKeyedStore<MyRecord>({
      namespace: "my-feature",
      maxEntries: 200,
      defaultTtlMs: 15 * 60_000,
    });

    await store.register("key-1", { value: "hello" });
    const claimed = await store.registerIfAbsent("dedupe-key", { value: "first" });
    const value = await store.lookup("key-1");
    await store.consume("key-1");
    await store.clear();
    ```

    Các kho lưu trữ dạng khóa vẫn tồn tại sau khi khởi động lại và được cô lập theo plugin id gắn với runtime. Dùng `registerIfAbsent(...)` cho các yêu cầu chống trùng lặp nguyên tử: hàm trả về `true` khi khóa bị thiếu hoặc đã hết hạn và được đăng ký, hoặc `false` khi một giá trị còn hiệu lực đã tồn tại mà không ghi đè giá trị, thời điểm tạo hoặc TTL của nó. Giới hạn: `maxEntries` trên mỗi namespace, 6.000 hàng còn hiệu lực trên mỗi Plugin, giá trị JSON dưới 64KB, và tùy chọn hết hạn TTL. Khi một lần ghi vượt quá giới hạn số hàng của Plugin, runtime có thể loại bỏ các hàng còn hiệu lực cũ nhất khỏi namespace đang được ghi; các namespace cùng cấp không bị loại bỏ cho lần ghi đó, và lần ghi vẫn thất bại nếu namespace không thể giải phóng đủ số hàng.

    <Warning>
      Chỉ các plugin được đóng gói sẵn trong bản phát hành này.
    </Warning>
  </Accordion>

  <Accordion title="api.runtime.tools">
    Các factory cho công cụ bộ nhớ và CLI.

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const getTool = api.runtime.tools.createMemoryGetTool(/* ... */);
    const searchTool = api.runtime.tools.createMemorySearchTool(/* ... */);
    api.runtime.tools.registerMemoryCli(/* ... */);
    ```
  </Accordion>

  <Accordion title="api.runtime.channel">
    Các helper runtime dành riêng cho kênh (có sẵn khi một plugin kênh được tải).

    `api.runtime.channel.media` là bề mặt được ưu tiên cho việc tải xuống và lưu trữ phương tiện của kênh:

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const saved = await api.runtime.channel.media.saveRemoteMedia({
      url,
      subdir: "inbound",
      maxBytes,
      filePathHint: fileName,
    });
    ```

    Dùng `saveRemoteMedia(...)` khi một URL từ xa cần trở thành phương tiện của OpenClaw. Dùng `saveResponseMedia(...)` khi Plugin đã lấy một `Response` với auth, xử lý redirect hoặc allowlist do Plugin sở hữu. Chỉ dùng `readRemoteMediaBuffer(...)` khi Plugin cần byte thô để kiểm tra, chuyển đổi, giải mã hoặc tải lên lại. `fetchRemoteMedia(...)` vẫn là alias tương thích đã bị ngừng khuyến nghị cho `readRemoteMediaBuffer(...)`.

    `api.runtime.channel.mentions` là bề mặt chính sách nhắc đến đầu vào dùng chung cho các plugin kênh được đóng gói sẵn có sử dụng runtime injection:

    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    const mentionMatch = api.runtime.channel.mentions.matchesMentionWithExplicit(text, {
      mentionRegexes,
      mentionPatterns,
    });

    const decision = api.runtime.channel.mentions.resolveInboundMentionDecision({
      facts: {
        canDetectMention: true,
        wasMentioned: mentionMatch.matched,
        implicitMentionKinds: api.runtime.channel.mentions.implicitMentionKindWhen(
          "reply_to_bot",
          isReplyToBot,
        ),
      },
      policy: {
        isGroup,
        requireMention,
        allowTextCommands,
        hasControlCommand,
        commandAuthorized,
      },
    });
    ```

    Các helper nhắc đến có sẵn:

    * `buildMentionRegexes`
    * `matchesMentionPatterns`
    * `matchesMentionWithExplicit`
    * `implicitMentionKindWhen`
    * `resolveInboundMentionDecision`

    `api.runtime.channel.mentions` cố ý không phơi bày các helper tương thích `resolveMentionGating*` cũ hơn. Ưu tiên đường dẫn chuẩn hóa `{ facts, policy }`.
  </Accordion>
</AccordionGroup>

## Lưu trữ tham chiếu runtime

Dùng `createPluginRuntimeStore` để lưu tham chiếu runtime nhằm sử dụng bên ngoài callback `register`:

<Steps>
  <Step title="Create the store">
    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
    import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";

    const store = createPluginRuntimeStore<PluginRuntime>({
      pluginId: "my-plugin",
      errorMessage: "my-plugin runtime not initialized",
    });
    ```
  </Step>

  <Step title="Wire into the entry point">
    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    export default defineChannelPluginEntry({
      id: "my-plugin",
      name: "My Plugin",
      description: "Example",
      plugin: myPlugin,
      setRuntime: store.setRuntime,
    });
    ```
  </Step>

  <Step title="Access from other files">
    ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
    export function getRuntime() {
      return store.getRuntime(); // throws if not initialized
    }

    export function tryGetRuntime() {
      return store.tryGetRuntime(); // returns null if not initialized
    }
    ```
  </Step>
</Steps>

<Note>
  Ưu tiên `pluginId` cho danh tính runtime-store. Dạng `key` cấp thấp hơn dành cho các trường hợp ít gặp khi một Plugin chủ ý cần nhiều hơn một slot runtime.
</Note>

## Các trường `api` cấp cao nhất khác

Ngoài `api.runtime`, đối tượng API cũng cung cấp:

<ParamField path="api.id" type="string">
  Plugin id.
</ParamField>

<ParamField path="api.name" type="string">
  Tên hiển thị của Plugin.
</ParamField>

<ParamField path="api.config" type="OpenClawConfig">
  Ảnh chụp config hiện tại (ảnh chụp runtime trong bộ nhớ đang hoạt động khi có sẵn).
</ParamField>

<ParamField path="api.pluginConfig" type="Record<string, unknown>">
  Config dành riêng cho Plugin từ `plugins.entries.<id>.config`.
</ParamField>

<ParamField path="api.logger" type="PluginLogger">
  Logger theo phạm vi (`debug`, `info`, `warn`, `error`).
</ParamField>

<ParamField path="api.registrationMode" type="PluginRegistrationMode">
  Chế độ tải hiện tại; `"setup-runtime"` là cửa sổ khởi động/thiết lập nhẹ trước khi vào entry đầy đủ.
</ParamField>

<ParamField path="api.resolvePath(input)" type="(string) => string">
  Phân giải một đường dẫn tương đối với gốc Plugin.
</ParamField>

## Liên quan

* [Nội bộ Plugin](/vi/plugins/architecture) — mô hình capability và registry
* [Điểm vào SDK](/vi/plugins/sdk-entrypoints) — tùy chọn `definePluginEntry`
* [Tổng quan SDK](/vi/plugins/sdk-overview) — tham chiếu subpath
