Wallet provider
The window.aiwallet provider that AI Wallet places in every page, its discovery events, its four methods, the events a chat streams, errors with their statuses and link notices, written so another wallet or SDK can implement it.
A page talks to the wallet through one object, window.aiwallet. This page specifies that object as AI Wallet implements it and as @aiconnect/core uses it. A wallet that implements it works with sites built on the SDK, and an SDK that follows it works with AI Wallet.
The protocol carries no version number. The surface below is the whole of it in this release.
Discovery
window.aiwallet
The wallet defines window.aiwallet in the page's own JavaScript world before the page's scripts run. AI Wallet does this from a content script that runs at document_start in the page's main world, in the top frame of each tab. It defines the property with Object.defineProperty with writable: false and configurable: false, and freezes the object. A second wallet in the same browser therefore cannot replace it.
AI Wallet runs on pages served over HTTPS, and on http://localhost and http://127.0.0.1 at any port.
aiwallet:announce
The wallet dispatches a CustomEvent named aiwallet:announce on window, with the provider object as its detail. It dispatches it once when it defines the provider, and again each time the page dispatches aiwallet:request.
aiwallet:request
A script that loads after the wallet's announce asks for it again by dispatching a plain Event named aiwallet:request on window.
How the SDK finds the wallet
@aiconnect/core looks for a wallet this way:
- When
window.aiwalletexists and itsisAIWalletistrue, it uses it. - Otherwise it listens for
aiwallet:announce, dispatchesaiwallet:request, and takes thedetailof the first announce. - When no announce arrives within 300 milliseconds, it concludes that no wallet is installed.
function findWallet(): Promise<AiWalletProvider | null> {
if (window.aiwallet?.isAIWallet) {
return Promise.resolve(window.aiwallet);
}
return new Promise((resolve) => {
const onAnnounce = (event: Event) => {
window.removeEventListener('aiwallet:announce', onAnnounce);
resolve((event as CustomEvent<AiWalletProvider>).detail);
};
window.addEventListener('aiwallet:announce', onAnnounce);
window.dispatchEvent(new Event('aiwallet:request'));
setTimeout(() => {
window.removeEventListener('aiwallet:announce', onAnnounce);
resolve(null);
}, 300);
});
}The provider object
interface AiWalletProvider {
isAIWallet: true;
// AI Wallet sets 'AI Wallet'. The SDK does not read it.
name: string;
request(
method: string, params?: unknown, onEvent?: (event: ChatEvent) => void, signal?: AbortSignal,
): Promise<unknown>;
// Called when the visitor changes, in the wallet, the account or model this site answers with.
onLink(listener: (link: WalletLink) => void): () => void;
}- isAIWallet. The value
true. The SDK checks it before it useswindow.aiwallet. - request(method, params, onEvent, signal). Sends one request and resolves with its result.
onEventreceives the events achatstreams: who answers, then its text (see Streaming events). Abortingsignalrejects the request at once with the signal's reason, and the wallet stops the chat it runs for it. - onLink(listener). Adds a listener for link notices and returns a function that removes it.
Shared types
The methods exchange the types of @aiconnect/core, documented in the core reference.
type WalletLink =
| { connected: false }
| { connected: true; providerName: string; model?: string };
type ChatEvent =
| { kind: 'start'; providerName: string; model: string }
| { kind: 'text'; delta: string };
type ChatMessage = SystemMessage | UserMessage | AssistantMessage | ToolMessage;
interface UserMessage {
role: 'user';
// An image is a data: URL, or an https URL for the providers that fetch images themselves.
content: string | ({ type: 'text'; text: string } | { type: 'image'; url: string })[];
}
interface AssistantMessage {
role: 'assistant';
content: string;
toolCalls?: { id: string; name: string; arguments: Record<string, unknown> }[];
}
interface ToolDefinition {
name: string;
description: string;
inputSchema: Record<string, unknown>;
}
interface GenerationOptions {
maxTokens?: number;
temperature?: number;
topP?: number;
stop?: string[];
toolChoice?: 'auto' | 'none' | 'required' | { name: string };
responseFormat?: { type: 'json'; schema?: Record<string, unknown>; name?: string; strict?: boolean };
}
interface ChatResponse {
message: AssistantMessage;
finishReason: 'stop' | 'length' | 'toolCalls' | 'contentFilter';
// The model that answered, as the provider named it.
model: string;
// Absent when the provider reports no token counts. inputTokens includes cachedInputTokens.
usage?: { inputTokens: number; outputTokens: number; cachedInputTokens: number };
}In a connected WalletLink, providerName is the display name of the account that answers the site, such as ChatGPT or Claude, and Auto when the wallet picks the account for each answer. model is the default model of that account, and is absent with Auto.
Methods
| Method | Params | Result | Asks the visitor |
|---|---|---|---|
status | none | WalletLink | No |
connect | none | WalletLink | The first time, and to unlock |
disconnect | none | { connected: false } | No |
chat | { messages, tools, options } | ChatResponse | To unlock |
status
Tells the page whether its origin may use the visitor's AI, without any window or prompt. The SDK calls it when it starts, so a site the visitor approved earlier connects on its own.
const link = await window.aiwallet.request('status');
// { connected: true, providerName: 'ChatGPT', model: 'gpt-6-luna' }The result is { connected: false } when the origin is not approved, and also while the wallet is locked. A page that reads connected: false because of a lock receives a link notice when the visitor unlocks the wallet.
connect
Asks for the visitor's approval of the origin, then returns its link.
- A wallet that is not set up yet rejects with "AI Wallet is not set up yet. Open it from the browser toolbar."
- A locked wallet opens its unlock window. When the visitor closes it without unlocking, the request rejects with "AI Wallet is locked. Unlock it to continue." Requests from several pages at the same moment share one unlock window.
- An origin the visitor already approved gets its link at once.
- Any other origin makes the wallet open its approval window, which shows the origin's host and the account that will answer it. Declining or closing the window rejects with "The connection was declined". A wallet that holds no account yet asks the visitor to connect one first, and its Approve button stays disabled.
- On approval the wallet saves the origin with the visitor's default account, and the request resolves with the link.
try {
const link = await window.aiwallet.request('connect');
} catch (error) {
// 'The connection was declined', or the reason the wallet could not ask.
}disconnect
Removes the origin from the visitor's approved sites, sends a link notice of { connected: false } to the origin's open pages, and resolves with { connected: false }. The next connect asks the visitor again. On a locked wallet the request rejects with "The wallet is locked".
await window.aiwallet.request('disconnect');chat
Runs one model call on the account the origin answers with, and resolves with the answer: the assistant message, why it ended, the model that answered and the token counts.
const message = await window.aiwallet.request(
'chat',
{
messages: [
{ role: 'system', content: 'You recommend books from this site\'s catalog.' },
{ role: 'user', content: 'A calm book for tonight' },
],
tools: [{ name: 'search_books', description: 'Find books by mood.', inputSchema: { type: 'object' } }],
options: { toolChoice: 'auto' },
},
(event) => console.log(event.kind === 'start' ? `${event.providerName} on ${event.model}` : event.delta),
);
// {
// message: { role: 'assistant', content: '', toolCalls: [{ id: '…', name: 'search_books', arguments: { mood: 'calm' } }] },
// finishReason: 'toolCalls',
// model: 'gpt-6-luna',
// usage: { inputTokens: 412, outputTokens: 21, cachedInputTokens: 0 },
// }params.messagesis the whole conversation. The wallet keeps no conversation between calls.params.toolslists the tools the model may call on this step. Without it, the model can only answer in text.params.optionsholds what the page asks of the answer. The tool choice and the response format always reach the provider. The token limit,temperature,top_pand stop sequences are left out for the accounts that refuse them, as the AI SDK guide lists.- The result's
message.toolCallslists the tools the model asked for. The page runs them and callschatagain withtoolmessages that answer each call'sid. Onechatrequest is one model call; the loop around it belongs to the page. - A locked wallet opens its unlock window first, as in
connect, and the chat goes on once the visitor unlocks. - An origin the visitor has not approved gets "This site is not connected to AI Wallet", with status 401.
- A wallet with no account to answer gets "AI Wallet has no AI account connected", with status 403.
The account that answers is the one the visitor picked for the origin, on the model picked for the origin when there is one, and on the account's default model otherwise. With Auto, the wallet tries its accounts in turn, as the providers page describes.
Streaming events
During a chat, the wallet calls onEvent with each event of the answer, in order, before the promise settles.
- One
startevent names who answers:providerNameis the display name of the account, such asClaude, andmodelis the model the wallet asks that account for. The wallet sends it right before the first text, or right before the result when the answer has no text, which happens on a step that only calls tools. A chat that fails sends nostart. - A
textevent carries each piece of the answer's text, in order. A step that only calls tools may stream no text at all.
{ kind: 'start', providerName: 'Claude', model: 'claude-haiku-5-5' }
{ kind: 'text', delta: 'Piranesi suits ' }
{ kind: 'text', delta: 'a calm evening.' }With Auto, the wallet picks the account when the answer starts, so the start event is how a page learns which account and model answer: AiConnect.fetch writes that model into the first event of an AI SDK's stream, such as Anthropic's message_start. Every piece of text comes from the account the start names. The wallet moves to the next account only while nothing has streamed, and it sends start once the account that answers is known; an account that failed before it gets no start.
The model of the result is the model as the provider named it, which can be more precise than the start event's, such as a dated version.
Errors
A failed request rejects with a WalletRequestError, exported by @aiconnect/core. Its message is an English sentence meant for the visitor, and its status follows HTTP, set where the failure happens. AiConnect.fetch answers the AI SDKs with that status, so each SDK raises its own error class.
| Status | Message | When |
|---|---|---|
| 400 | A chat request carries its messages | chat without messages. |
| 401 | This site is not connected to AI Wallet | chat from an origin that is not approved. |
| 403 | The connection was declined | The visitor declined the origin, or closed the approval window. |
| 403 | AI Wallet is not set up yet. Open it from the browser toolbar. | connect or chat on a wallet that has no vault. |
| 403 | AI Wallet is locked. Unlock it to continue. | The visitor closed the unlock window without unlocking. |
| 403 | The wallet is locked | disconnect on a locked wallet. |
| 403 | AI Wallet has no AI account connected | chat while the origin's account is gone, or with Auto and no account. |
| 404 | AI Wallet has no method named method | A method outside the four above. |
| 410 | AI Wallet was updated. Reload this page to use it again. | The wallet was updated or reloaded after the page opened. |
| the provider's | Provider answered status: detail | The provider refused the call, for example with 429 when a plan is spent. |
| 499 | The answer was stopped from AI Wallet | The visitor pressed Stop in the wallet's side panel. |
| 502 | The reason the provider could not be reached | A network failure between the wallet and the provider. |
After a wallet update
When Chrome updates or reloads the extension, the content scripts of the pages already open lose their connection to the wallet. AI Wallet's content script then answers every request at once with "AI Wallet was updated. Reload this page to use it again." Show that message to the visitor. A reload injects the new provider.
Link notices
The wallet tells a page when what its origin answers with changes. The page receives the new WalletLink through the listeners it added with onLink.
const stop = window.aiwallet.onLink((link) => {
console.log(link.connected ? `Now answered by ${link.providerName}` : 'This site was switched off');
});AI Wallet sends a notice to every open tab whose origin is approved, each time the vault's contents change or the wallet unlocks. The changes include the visitor picking another account or model for a site, adding or removing an account, and changing the default account. A site the visitor switches off, from the wallet or through disconnect, gets a last notice of { connected: false } in its open tabs. A notice can repeat the link a page already holds. The wallet sends nothing when it locks.
Origin binding
The wallet identifies the site by its origin: scheme, host and port. AI Wallet reads the origin from the sender of the runtime port its content script opens, which Chrome sets from the page the content script runs in. Nothing the page sends names the origin, so a page cannot ask on behalf of another site.
Approvals belong to one origin. https://example.com and https://shop.example.com are two sites, each approved on its own.
Inside AI Wallet
This part is informative. Another wallet may carry requests any way it likes, as long as the page sees the surface above.
- The provider in the page posts each request to its own window as
{ source: 'aiwallet-page', request: { id, method, params } }, with a randomid. An aborted request posts{ source: 'aiwallet-page', cancel: id }. - AI Wallet's content script, in the extension's isolated world, forwards the request on a runtime port named
aiwallet-pageto the background. A request that arrives after the port closed, for example after the background slept, opens a new port. - The background answers on the port with events that carry the same
id:{ id, kind: 'start', providerName, model }once, then{ id, kind: 'text', delta }for streamed text, then one{ id, kind: 'result', result }or{ id, kind: 'error', message, status }. A cancel stops the chat the background runs for thatid. - The content script posts each event back to the page as
{ source: 'aiwallet-content', event }, and each link notice as{ source: 'aiwallet-content', notice: { kind: 'link', link } }.
Implementing a wallet
A wallet that sites built on @aiconnect/core can use does the following:
- Defines
window.aiwalletwithisAIWallet: true,requestandonLinkbefore the page's scripts run, and dispatchesaiwallet:announcewith the provider asdetail, again on eachaiwallet:request. - Binds every request to the page's origin as the browser reports it.
- Answers
statuswithout any prompt, with the origin's link. - On
connect, asks the visitor before an origin's first use, keeps the approval, and lets the visitor revoke it. - On
chat, accepts the chat format above withtoolsandoptions, calls the model on the visitor's account, sends onestartevent throughonEventbefore the first text or the result, streams the text through it, and resolves with aChatResponsewhose tool calls carry stable ids and parsed arguments.AiConnect.fetchfails a stream whose answer never names who answers. - Rejects failures with an error whose
messagethe site can show to the visitor and whosestatusfollows the table above. - Calls the
onLinklisteners of the origin's open pages when the account or model the origin answers with changes, or when the origin loses its approval. - Keeps credentials out of the page. The page receives links, text and answers.
@aiconnect/react
Every public export of @aiconnect/react, the provider, the connect window, the hooks and the window's messages, with its types generated from the source and an example.
Security model
What a site sees, what AI Wallet holds and where, how the vault is encrypted and locked, how sites are approved and revoked, and which browser permissions the wallet uses.