What Does Built-In Actually Mean?

Explain this chapter with AI

Copy this prompt into ChatGPT, Claude, Gemini, a local model, or another AI.

Apply this chapter with AI

Copy this prompt into ChatGPT, Claude, Gemini, a local model, or another AI.

The word built-in suggests permanence.

A built-in function is present when the program starts. A built-in browser feature is expected to work without an installer, account or deployment. If a web developer hears that AI is built into the browser, the obvious mental model is a global function waiting to be called.

const answer = await browserAI.prompt("Explain this page");

That is not the system we actually have.

The API may be absent. The device may be ineligible. The requested language or modality may be unsupported. A model may need to be downloaded. Downloading may require a user action. The bytes can arrive before extraction and loading finish. A session can be created and later exhaust its context. The same interface can be backed by a changed implementation after a browser update.

β€œBuilt-in” therefore describes management, not instantaneous readiness.

The browser manages the capability. The application still has to model its state.

This chapter turns that state into an explicit contract. By the end, we will have a small adapter that every later experiment can use and every event in Browser AI Observatory can understand.


1. One runtime, several kinds of API

Chrome’s built-in AI work includes a general Prompt API and a collection of task APIs.

The distinction is architectural.

General capability

The Prompt API exposes a language-model session. The application supplies instructions and input, receives generated output, and owns the task definition.

application defines task
        ↓
general language-model session
        ↓
generated response

This is flexible. The same mechanism can classify, extract, explain, transform or draft. Flexibility also means the application must design prompts, constrain outputs, evaluate behavior and handle variation.

Task capability

Task APIs expose narrower operations such as summarization, writing, rewriting, proofreading, translation and language detection.

application selects task and options
        ↓
task-specific browser API
        ↓
task-shaped result

The browser has more semantic information. It knows that the application wants a summary rather than an arbitrary completion. It can expose options that fit that task and may use a specialized model or adapter.

Neither category is universally better.

Need Prefer a task API when available Prefer the Prompt API
Standard summarization or translation Yes Sometimes
Novel task with custom instructions No Yes
Narrow, predictable options Yes No
Maximum behavioral flexibility No Yes
Reduced prompt design Yes No
One shared abstraction for many experiments No Yes

We begin with the Prompt API because it exposes the general lifecycle most clearly. Later we will compare specialized APIs against equivalent prompts rather than assuming either route is superior.


2. Capability detection has two levels

There are two different questions hidden inside β€œDoes this browser support the Prompt API?”

The first is structural:

const exposed = "LanguageModel" in globalThis;

This asks whether the current JavaScript context exposes the interface.

The second is operational:

const options = {
  expectedInputs: [{ type: "text", languages: ["en"] }],
  expectedOutputs: [{ type: "text", languages: ["en"] }],
};

const availability = await LanguageModel.availability(options);

This asks whether the browser can create a session compatible with a particular request.

Those checks are not interchangeable.

An API can exist while the requested capability is unavailable. A browser can support English text prompting while rejecting a different language or modality. A test performed without the intended options can report a state that does not apply to the session the application later creates.

Chrome’s guidance is therefore important: pass corresponding capability options to availability() and create(). The availability check is a preflight for a particular session shape, not a global badge attached to the browser.

We can encode this with a shared constant:

export const ENGLISH_TEXT_SESSION = Object.freeze({
  expectedInputs: [{ type: "text", languages: ["en"] }],
  expectedOutputs: [{ type: "text", languages: ["en"] }],
});

Every call uses the same object:

const state = await LanguageModel.availability(ENGLISH_TEXT_SESSION);

const session = await LanguageModel.create({
  ...ENGLISH_TEXT_SESSION,
  monitor(monitor) {
    monitor.addEventListener("downloadprogress", onProgress);
  },
});

This looks like a small code-quality choice. It is actually a correctness constraint. If the preflight and creation options drift apart, the interface can promise one capability and attempt another.


3. Availability is a state machine

At the time of writing, the common built-in AI lifecycle uses four availability values:

State Meaning for the application Appropriate response
unavailable The requested capability cannot be created in this environment Explain, disable, or offer an explicit fallback
downloadable The browser can support it after required assets are downloaded Ask the user to start; show expected transition
downloading A required download is already in progress Show progress or waiting state
available A compatible session can be created without another model download Enable creation or prompt flow

Notice what the values do not say.

available does not mean a session already exists.

downloadable does not mean the application should silently begin a large download.

downloading does not necessarily mean the feature is ready when the progress event reaches its final numeric value; the model may still require preparation and loading.

unavailable does not identify one universal cause. Unsupported hardware, insufficient storage, an unsupported option, browser configuration or rollout state can converge on the same application-visible result.

We should preserve the raw state and derive UI state from it:

function availabilityView(state) {
  switch (state) {
    case "available":
      return { level: "ready", action: "Create session" };
    case "downloadable":
      return { level: "setup", action: "Download and enable" };
    case "downloading":
      return { level: "waiting", action: null };
    case "unavailable":
      return { level: "blocked", action: null };
    default:
      return { level: "error", action: null };
  }
}

Do not convert the values directly to a boolean. A boolean erases exactly the lifecycle information the interface needs.


4. Run the state machine in the chapter

The state table becomes useful only when we can compare it with a browser’s actual path.

The Run with Browser AI action on this chapter opens a chapter-specific experience at:

/tools/ai/browser-ai-from-first-principles/02-chapter/

The page uses the same shared Browser AI runtime as Chapter 01, but Chapter 02 supplies a different experiment contract. It asks the runtime to expose a lifecycle view and declares the session options that must be used for both inspection and creation.

{
  "view": "capability-lifecycle",
  "options": {
    "samplingMode": "most-predictable",
    "expectedInputs": [{ "type": "text", "languages": ["en"] }],
    "expectedOutputs": [{ "type": "text", "languages": ["en"] }]
  }
}

The deterministic sampling mode is deliberate. It is compatible with browser builds that use speculative decoding and Multi-Token Prediction. More importantly, the exact same options reach LanguageModel.availability() and LanguageModel.create(). The interface does not preflight one session and attempt to create another.

The experience begins at not-inspected. A click on Inspect this browser performs the structural and operational checks. It then marks only the state returned by that browser:

not-inspected
    β”œβ”€β”€ unsupported
    └── unavailable | downloadable | downloading | available
                                ↓
                         session-ready
                                ↓
                             running
                         β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
                     completed       failed

This is not a decorative progress indicator. There is no rule saying every run must visit every state. A browser that reports available can move directly to session creation. A browser without an exposed LanguageModel stops at unsupported. A download-progress event adds downloading to the observed route. The strip is evidence about what happened, not an animation of what the application hoped would happen.

The chapter bundle contains three different forms of knowledge:

02-chapter/context.md          chapter-specific concepts
02-chapter/experiment.json     executable options and prompt
02-chapter/recorded-trace.json reviewed evidence from an earlier run

The context is supplied only when the user runs the prompt. It is not smuggled into capability detection. The experiment config defines behavior. The trace remains evidence with its original provenance.

If live execution is unavailable, Replay captured trace reconstructs the lifecycle from the events in the reviewed Chrome run. The replay may show available, download-progress events and session-ready because those are the events the browser actually emitted. It must not add downloadable merely to make the picture look orderly. Recorded mode is labelled throughout so another machine’s evidence cannot be mistaken for the current browser’s state.

This changes the role of the chapter. The prose defines the contract; the live page lets the reader inspect an implementation of it; the exported trace makes the observation portable. We can now disagree with a diagram by running the system.


5. Download is part of the product

The first create() call can trigger model acquisition when the capability is downloadable. Chrome performs eligibility checks, downloads the required model assets, prepares them and makes them available to built-in AI APIs that share the model.

This has several consequences.

The download belongs to a user-visible action

A web page should not make a user wonder why a browser process suddenly consumes bandwidth and disk. When user activation is required, it also cannot reliably initiate the process from an arbitrary background callback.

A useful interaction is explicit:

Local AI is supported but not installed.

Download the browser-managed model to enable this feature.
The browser controls storage and future updates.

[Enable local AI]

The exact copy will depend on the product, but the action should communicate that setup is happening.

Progress is not merely decoration

The creation API accepts a monitor callback. The monitor emits downloadprogress events whose loaded value moves toward 1.

const session = await LanguageModel.create({
  ...ENGLISH_TEXT_SESSION,
  monitor(monitor) {
    monitor.addEventListener("downloadprogress", (event) => {
      renderProgress(Math.round(event.loaded * 100));
    });
  },
});

We will record these events rather than only painting a progress bar. The event sequence lets us measure:

  • time from user action to first progress;
  • download duration;
  • time from final progress to resolved session creation;
  • failures and stalls;
  • differences between cold and warm creation.

The application does not own persistence

Browser-managed models can be updated or purged. Chrome’s management rules include device eligibility and available storage. An application should not treat one successful session on Monday as proof that the model will remain ready on Friday.

Capability checks belong at the point of use, with sensible caching only where the platform contract makes that safe.


6. A session is a stateful resource

After creation, the Prompt API returns a session. The session can accept a complete prompt or produce a stream.

const response = await session.prompt("Explain event delegation.");

or:

const stream = session.promptStreaming(
  "Explain event delegation with one minimal example."
);

for await (const chunk of stream) {
  appendOutput(chunk);
}

The second form matters for interactive software. It reduces perceived waiting time and exposes time to first output, stream cadence and interruption behavior.

The session also owns conversation context. Later prompts can be influenced by earlier turns until the context capacity is exhausted. This makes a session more than a function reference.

It is a resource with:

  • identity;
  • creation options;
  • creation time;
  • accumulated context;
  • active requests;
  • cancellation behavior;
  • terminal destruction or failure.

The observatory will assign its own session identifier because a platform object is not a durable trace key:

function createSessionRecord(apiSession, options) {
  return {
    id: crypto.randomUUID(),
    apiSession,
    options,
    createdAt: performance.timeOrigin + performance.now(),
    prompts: 0,
    status: "ready",
  };
}

We keep runtime objects and serializable records separate. The apiSession cannot be assumed to survive extension messaging or storage. The record can.


7. Streaming changes what we can measure

A non-streaming call produces two obvious timestamps:

request start ───────────────── response complete

A streaming call exposes a more useful structure:

request start
      ↓
first chunk
      ↓
chunk cadence
      ↓
last chunk
      ↓
stream complete

From those events we can derive:

$$ T_{first} = t_{first\ chunk} - t_{request} $$
and:
$$ T_{generation} = t_{complete} - t_{first\ chunk} $$
If token counts are available or estimated consistently, we can approximate throughput:
$$ R = \frac{N_{output}}{T_{generation}} $$
The estimate must be labelled honestly. JavaScript string length is not token count. A tokenizer for the wrong model is also not exact. For the first observatory version we will store bytes, Unicode code points and timing, then add token estimates only behind a named estimator.

The distinction prevents a polished dashboard from reporting invented precision.


8. Cancellation is part of correctness

Generation can be slow, the user can change their mind, and a page can navigate away. Both complete and streaming prompt operations can accept an abort signal.

const controller = new AbortController();

stopButton.addEventListener("click", () => controller.abort());

try {
  const stream = session.promptStreaming(prompt, {
    signal: controller.signal,
  });

  for await (const chunk of stream) {
    appendOutput(chunk);
  }
} catch (error) {
  if (error.name === "AbortError") {
    showStatus("Stopped");
  } else {
    throw error;
  }
}

An abort is not the same as a model failure. The observatory event must preserve that distinction:

{
  "type": "prompt.finished",
  "outcome": "aborted",
  "error": null
}

If cancellation is collapsed into a generic error count, the dashboard will make a responsive user control look like a reliability regression.


9. Model the lifecycle once

We now have enough behavior to define a reusable adapter.

export class BrowserLanguageModel {
  constructor(options, emit = () => {}) {
    this.options = options;
    this.emit = emit;
    this.session = null;
    this.sessionId = null;
  }

  async inspect() {
    if (!("LanguageModel" in globalThis)) {
      return { exposed: false, availability: "unavailable" };
    }

    const availability = await LanguageModel.availability(this.options);
    return { exposed: true, availability };
  }

  async create() {
    const startedAt = performance.now();
    this.emit({ type: "session.create.started", startedAt });

    this.session = await LanguageModel.create({
      ...this.options,
      monitor: (monitor) => {
        monitor.addEventListener("downloadprogress", (event) => {
          this.emit({
            type: "model.download.progress",
            loaded: event.loaded,
            at: performance.now(),
          });
        });
      },
    });

    this.sessionId = crypto.randomUUID();
    this.emit({
      type: "session.create.finished",
      sessionId: this.sessionId,
      durationMs: performance.now() - startedAt,
    });

    return this.session;
  }

  async *stream(prompt, signal) {
    if (!this.session) {
      throw new Error("Create a session before prompting.");
    }

    const traceId = crypto.randomUUID();
    const startedAt = performance.now();
    let firstChunkAt = null;
    let outputChars = 0;

    this.emit({
      type: "prompt.started",
      traceId,
      sessionId: this.sessionId,
      inputChars: [...prompt].length,
      startedAt,
    });

    try {
      const response = this.session.promptStreaming(prompt, { signal });

      for await (const chunk of response) {
        const at = performance.now();
        firstChunkAt ??= at;
        outputChars += [...chunk].length;
        this.emit({ type: "prompt.chunk", traceId, at, chars: [...chunk].length });
        yield chunk;
      }

      this.emit({
        type: "prompt.finished",
        traceId,
        outcome: "completed",
        totalMs: performance.now() - startedAt,
        timeToFirstChunkMs:
          firstChunkAt === null ? null : firstChunkAt - startedAt,
        outputChars,
      });
    } catch (error) {
      this.emit({
        type: "prompt.finished",
        traceId,
        outcome: error.name === "AbortError" ? "aborted" : "failed",
        totalMs: performance.now() - startedAt,
        errorName: error.name,
      });
      throw error;
    }
  }
}

This is not a production wrapper yet. It does something more important for the current stage: it makes our assumptions visible.

It separates capability inspection from session creation. It uses one option contract. It treats download progress as an event. It assigns session and trace identity. It distinguishes completion, abort and failure. It records sizes without pretending they are tokens. It streams output without retaining prompt content in the event by default.

That is enough to become the core of the first extension.


10. Failure must be classified before it is handled

A browser-AI feature can fail at several layers:

Layer Example Response
Exposure LanguageModel absent Explain browser requirement or choose fallback
Capability Requested language unavailable Change options or disable this route
Acquisition Download cannot start or complete Show actionable setup state
Session Creation fails Record environment and error; allow retry where safe
Request Prompt rejected or context exhausted Revise input, rotate session, or compact context
User control Request aborted Stop cleanly; do not report as system failure
Behavior Fluent but wrong output Evaluate and reject; API success is not task success

The last row is the most important.

A resolved promise proves that inference completed. It does not prove that the result is correct, useful, safe or compatible with the feature contract.

Operational observability and behavioral evaluation must meet, but they should not be confused.


Conclusion

β€œBuilt-in” is not a binary property. It is a lifecycle controlled partly by the browser and experienced through the application.

The application must distinguish interface exposure, capability availability, model acquisition, session readiness, active generation, cancellation, operational failure and behavioral failure. Flattening those states into supported: true creates a brittle feature and an unhelpful debugger.

Chapter 02 now makes that distinction executable. The live experience shows only the route observed in the current browser; replay mode shows only the route preserved by the recorded events. Both feed the same event vocabulary, so the conceptual model, the reader interface and the Observatory can evolve together.

We now have the contract the rest of the opening group needs:

inspect capability
      ↓
explain or initiate acquisition
      ↓
create and identify session
      ↓
prompt with cancellation
      ↓
stream and measure
      ↓
classify outcome

The next chapter puts this contract inside Chrome DevTools. We will build the smallest Browser AI Observatory: a Manifest V3 extension with its own panel, capability probe, download display, streaming prompt runner and event trace.


Sources and further reading

  1. Chrome for Developers, Get started with built-in AI.
  2. Chrome for Developers, The Prompt API.
  3. Chrome for Developers, Inform users of model download.
  4. Chrome for Developers, Understand built-in model management in Chrome.
  5. Chrome for Developers, Built-in AI.