> ## Documentation Index
> Fetch the complete documentation index at: https://agent.minimax.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Page Client API

> The host injects window.mcode into the Mini App's main page through preload. Check that a method exists before calling it.

No npm SDK, session ID, or plugin ID is needed. Older hosts, ordinary browser tabs, and external browsers don't have this object, so always feature-detect. You can copy `client-api.ts` from the Skill into your project for type checking; don't load it at runtime.

See [Client API reference](https://agent.minimax.io/docs/miniapp/reference/client-api.md) for complete signatures, parameters, return types, and capability names. Session methods are in [Agent API reference](https://agent.minimax.io/docs/miniapp/reference/agent-api.md).

## Feature detection

```javascript theme={null}
const miniapp = window.mcode?.miniapp;

if (!miniapp?.message?.append) {
  showHint('This host cannot hand content to the message input.');
}
if (miniapp?.window) {
  // Pop-out window support is available
}
if (miniapp?.agent) {
  // The host provides Agent APIs; calls still require the declaration and permission switch
}
```

## message.append(text)

Call it from a button the user clicked. The host shows its own confirmation dialog with the plugin, the target conversation, and the text. After confirmation, the text is appended to that conversation's message input. The user can edit it and must press Send to start a turn.

```javascript theme={null}
async function handToAgent(text, button, status) {
  const message = window.mcode?.miniapp?.message;
  if (!message?.append) {
    status.textContent = 'This host cannot add text to the message input.';
    return;
  }
  button.disabled = true;
  try {
    await message.append(text);
    status.textContent = 'Added to the message input. Review it there and press Send.';
  } catch (error) {
    const code = error?.message;
    if (code === 'user_cancelled') status.textContent = 'Cancelled. Nothing was added.';
    else if (code === 'result_unknown') status.textContent = 'Check the message input first; the text may already be there.';
    else status.textContent = 'Could not add the text. Check the conversation and try again.';
  } finally {
    button.disabled = false;
  }
}
```

* `text` is plain text, up to 100,000 UTF-16 code units per call. HTML is treated as text; add newlines yourself if you need them.
* Every call needs confirmation. There is no remembered choice and no automatic sending. Multiple calls are confirmed one at a time, in order.
* Existing draft text, references, and attachments are preserved. The `message` namespace grants no access to chat history and cannot change sent messages; reading user sessions requires a separate `session.read` declaration. See [Agent API](https://agent.minimax.io/docs/miniapp/authors/agent.md#agent-api).
* On failure, `message` is `user_cancelled`, `invalid_request`, `text_too_large`, `not_allowed`, `target_unavailable`, or `result_unknown`. For `result_unknown`, ask the user to check the message input first; never repeat the append automatically.

Make the destination clear on the button, for example "Ask Agent". A resolved promise only means the text reached the input, not that the Agent has started working.

## miniapp.window

A handle to the Mini App's own window: whether the page sits in the file panel (`inline`) or is popped out (`window`), plus position, size, and a few window flags while popped out. The interface may still change, so always feature-detect first.

```javascript theme={null}
const handle = window.mcode?.miniapp?.window;
if (handle) {
  const state = await handle.getState(); // { mode: 'inline' } or { mode: 'window', window: {...} }

  // Save required state in advance; call a mode change inside a synchronous user gesture
  popOutButton.onclick = () => handle.update({ mode: 'window', width: 900, height: 650 });

  const subscription = handle.onDidChangeState((next) => render(next.mode));
  addEventListener('pagehide', () => subscription.dispose());
}
```

* In official releases from v3.1.9, `getState()` may return `modeChangeReloadsPage: true`. This means changing between `inline` and `window` rebuilds the page. The logical window ID and Node service survive, but the current document, memory, media streams, subscriptions, and pending Promises do not. Check the runtime result rather than assuming every window transfer reloads the page.
* Save required state before allowing the user to click the transfer button. Waiting for an asynchronous save just before the call can lose the user gesture. Do not put required work after `await update({ mode })` or depend on the old document's listener. Read `getState()`, restore saved content, and subscribe again when the destination page starts.
* From v3.1.10, transfers that rebuild the page require host confirmation, including closing the separate window or pressing Cmd/Ctrl+W to return it. Cancellation preserves the original page and requests, and `update()` resolves with the current state, whose `mode` may differ from the request. Inspect `mode` in a successful reply or state event. See [Page rebuilds and confirmation](https://agent.minimax.io/docs/miniapp/reference/client-api.md#window-reload) for the full rules.
* Changing `mode` or calling `focus()` must come from a user gesture; otherwise it rejects with `not_permitted`.
* `frame`, `transparent`, `minWidth`, and `minHeight` can only be declared in the manifest's `surface.window` and can't change at runtime.
* `alwaysOnTop: true` and `clickThrough: true` can be turned on at runtime only if the manifest declares them as `true`.
* Multiple `update` calls in the same frame are merged into one; a patch with `mode` is sent immediately so it keeps the user gesture.
* Errors: `inline_mode`, `not_permitted`, `busy`, `result_unknown`, `invalid_request`.

### Pop-out window pitfalls

| Setting | Watch out for |
| - | - |
| `frame: false` | Removes the OS title bar and its drag region; provide your own with CSS `-webkit-app-region: drag` |
| `transparent: true` | Only removes the window's own background; the page `body` must be transparent too. The host turns off the native shadow for transparent windows, so draw one with CSS if you need it |
| Window height | The window doesn't grow with content. Use `html, body { height: 100%; overflow: hidden }` and let the main area scroll |
| `clickThrough: true` | Turn it on only for states that should be visible without catching clicks; otherwise nothing in the window, including its own buttons, is clickable |
| `visible: false` | Hiding does not itself end the page; scripts, timers, and network requests keep running |

## miniapp.agent

Start Agent sessions from the page, render tool approvals and questionnaires yourself, or hand a session to the host sidebar with `open`. Requires `agentAccess` in the manifest; see [Agent API](https://agent.minimax.io/docs/miniapp/authors/agent.md#agent-api).

## Devices and system permissions

Camera and system notifications require MiniMax Code v3.1.9 or later. Declare that minimum in `engines.mcode` when using either capability; see [Version requirements](https://agent.minimax.io/docs/miniapp/reference/manifest.md#compat).

Device access must be declared in `deviceAccess` in `miniapp.json` and left enabled in the plugin detail page's permission panel. Each new request checks the corresponding switch. Turning off microphone access blocks new recording requests and transcriptions; the page must still stop audio streams it already holds. Unavailable system permissions, such as location and screen capture, are denied by default.

### Microphone and speech-to-text

```json miniapp/miniapp.json theme={null}
{
  "deviceAccess": ["microphone"]
}
```

Record on the page with `navigator.mediaDevices.getUserMedia({ audio: true })`, then call `window.mcode.miniapp.speech.transcribe` after recording ends. The host only transcribes. The page decides when to stop, shows its own recording indicator, and stops every track in the audio stream.

```javascript theme={null}
// audio must already contain 16 kHz, mono, s16 little-endian PCM samples.
async function transcribeRecordedAudio(audio) {
  const speech = window.mcode?.miniapp?.speech;
  if (!speech) throw new Error('speech_unavailable');
  return speech.transcribe({
    audio,
    format: { sampleRate: 16000, channelCount: 1, sampleFormat: 's16' },
  }); // Promise<{ text }>
}
```

* `audio` accepts an `Int16Array`, `ArrayBuffer`, or view with at least one sample and no more than 16,000,000 bytes (500 seconds). Convert recorded audio to the PCM format above first; never pass `MediaRecorder` output or a WAV, WebM, Ogg, FLAC, or MP3 file directly.
* An optional second argument, `{ onPartial: (text) => ... }`, receives transcript updates while the host processes the audio. These are not live captions during recording.
* Only one transcription per Mini App can run at a time; another call returns `busy`. Missing declarations or a disabled permission return `not_permitted`; a signed-out host or disabled speech recognition returns `runtime_unavailable`. Closing or reloading the page ends a pending transcription.

### Camera

Declare `deviceAccess: ["camera"]` in the manifest and enable the corresponding permission. The page then obtains camera video through the standard Web API:

```javascript theme={null}
async function startCamera(video) {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
  return () => {
    stream.getTracks().forEach((track) => track.stop());
    video.srcObject = null;
  };
}
```

The page must show capture status and call the returned stop function when the user finishes recording or previewing. On first use, the operating system may ask whether to allow camera access for the host application. A combined audio/video request requires both `microphone` and `camera` to be declared and enabled; if either is off, the entire request fails with `NotAllowedError`. Turning off camera permission blocks new requests; the page must still stop video streams it already holds.

### System notifications

Declare `deviceAccess: ["notifications"]` in the manifest and enable the corresponding permission. The page sends notifications with the standard `Notification` API under the host application's identity. Use `Notification.permission` to check the current notification permission. `requestPermission()` does not show a separate host confirmation dialog; access still depends on the declaration and permission switch.

```javascript theme={null}
const notice = new Notification('Done', { body: 'Return to the Mini App to see the results.' });
notice.addEventListener('click', () => {
  const handle = window.mcode?.miniapp?.window;
  if (handle) void handle.focus().catch(() => {});
});
```

A notification click handler can request `focus()` while the page that created it is still alive. A separate window is restored and focused; an inline page requests host focus and selection of its own tab. Do not rely on this action to expand a collapsed file panel. After the creating page closes or reloads, an old notification cannot reopen it or open another page. This API does not provide background notifications after the page closes.

## Page conventions

* Use relative or same-origin URLs, with business APIs served by the Node service. Credentials, Connector clients, and host transport never enter the page.
* Use semantic HTML: landmarks, headings, tables, forms, `role="status"`, and so on. That is how the Agent understands the page when it checks it in the browser.
* Keep third-party scripts out of the main document. Code there, including a same-origin iframe that can reach its parent, shares the page's trust.
* Don't load remote resources: use local files or inline SVG for fonts, icons, and images, never a remote CDN. Pages then work offline, and third-party scripts have less chance to reach local services under the page's identity.
* Without a specific style request, follow the MiniMax Code visual baseline in the Skill.
