@aiconnect/core
Every public export of @aiconnect/core, the site's connection to the visitor's AI, the fetch for AI SDKs, the tool loop and the MCP tool source, with its types generated from the source and an example.
@aiconnect/core is the site's side of AI Connect, without any framework. It exports the AiConnect client, the McpToolSource class, the runConversation and finalAnswer functions, the chat content helpers, WalletRequestError, and the types they share with AI Wallet.
The tables on this page are generated from the package's TypeScript source each time the docs are built. Open a row to see its full type.
import {
AiConnect, McpToolSource, WalletRequestError, contentParts, dataUrl, finalAnswer, inlineData, runConversation, textOf,
} from '@aiconnect/core';
import type {
ChatEvent, ChatMessage, ChatResponse, ChatStart, ConnectionState, ContentPart, GenerationHint, JsonSchema, ToolSource,
Usage, UserMessage, WalletChatParams, WalletLink, WalletProvider,
} from '@aiconnect/core';AiConnect
The site's connection to the visitor's AI, through AI Wallet. Its public members:
Prop
Type
- constructor.
new AiConnect()takes no options. In a browser, the client looks for the wallet at once and asks for the site's status. A site the visitor already approved becomesconnectedwithout a prompt. Built wherewindowdoes not exist, during a server render, the client does nothing and stayschecking. - installUrl. The AI Wallet page, https://tensorpass.app/wallet, where a visitor without the wallet gets it. The connect window of
@aiconnect/reactlinks to it in thenoWalletstate. - state. The current connection state.
- subscribe(listener). Calls
listenerwith each new state and returns a function that removes it. The listener is not called with the current state when you subscribe, so readstatefirst. - connect(). Asks the wallet to let this site use the visitor's AI. Without a wallet the state becomes
noWallet. Otherwise it moves toconnecting, then to the state the wallet's answer gives, usuallyconnected, or toerrorwith the wallet's reason, such as "The connection was declined". The promise resolves in each of these cases. A locked wallet first asks the visitor to unlock it, and the wallet asks the visitor to approve the site the first time it connects. - disconnect(). Asks the wallet to remove this site from the visitor's approved sites, then sets the state to
disconnected, or tonoWalletwithout a wallet. The nextconnect()asks the visitor again. The promise rejects when the wallet refuses, for example "The wallet is locked". - fetch. A
fetchfor the AI SDKs, which answers their requests through the wallet in their own protocol. See WalletFetch. - sdkOptions. The options an OpenAI or Anthropic SDK client takes to answer through the wallet. See SdkOptions.
The state also changes on its own when the visitor acts in the wallet: picking another account for the site, removing the site, or unlocking the wallet.
const client = new AiConnect();
client.subscribe((state) => {
if (state.status === 'connected') {
console.log(`Answered by ${state.providerName}`);
}
});
document.querySelector('#connect')?.addEventListener('click', () => client.connect());ConnectionState
A union of six states, one per status. Each row holds the shape of the state with that status.
Prop
Type
| Status | Meaning |
|---|---|
checking | The client is looking for the wallet, or the page is rendering on a server. |
noWallet | No wallet answered within 300 milliseconds of the page asking for one. |
disconnected | The wallet answered, and this site is not connected. A wallet that is locked when the page loads reports the site this way until the visitor unlocks it. |
connecting | connect() is waiting for the visitor in the wallet. |
connected | The site may chat. providerName is the display name of the account that answers it, or Auto. model is that account's default model, absent with Auto. session sends chats to the wallet. |
error | The last connect() failed. message holds the reason. |
function label(state: ConnectionState): string {
switch (state.status) {
case 'connected':
return state.model ? `${state.providerName} (${state.model})` : state.providerName;
case 'error':
return state.message;
default:
return state.status;
}
}SdkOptions
The type of client.sdkOptions: the options an OpenAI or Anthropic SDK client takes to answer through the wallet.
Prop
Type
- apiKey. A placeholder, since the SDKs require a key. The wallet does not read it.
- fetch. The client's
fetch. - dangerouslyAllowBrowser.
true. The SDKs guard real keys from the browser, and no real key reaches the page.
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';
const openai = new OpenAI(client.sdkOptions);
const anthropic = new Anthropic(client.sdkOptions);WalletFetch
The type of client.fetch, a function with the signature of the global fetch:
Prop
Type
It lets the AI SDK a site already uses answer through the visitor's AI. The request stays in the page: fetch reads neither the host the SDK targets nor its API key.
- It answers
POSTrequests whose path ends with/chat/completions(OpenAI Chat Completions),/responses(OpenAI Responses),/messages(Anthropic Messages), or:generateContentand:streamGenerateContent(Gemini). Any other request gets a 404 with a JSON error in the shape the OpenAI, Anthropic and Google SDKs read. - It reads the request into the wallet's chat format, asks the wallet, and answers in the same protocol, streamed or whole. The wallet answers with the account the visitor picked for the site, and the response names the model that answered. The model the request names is not used.
- It waits for the status check or the visitor's approval to end. A site that is not connected gets a 401 in the protocol's error shape, with "AI Wallet is not connected on this site" or "AI Wallet is not installed in this browser". Called outside a browser, it answers 401 with "AI Wallet answers in the browser only".
- An answer that fails before any text comes back as an HTTP error with the wallet's status, so each SDK raises its own typed error. Aborting the SDK's signal stops the chat in the wallet.
The SDKs that take a fetch of their own receive it directly:
import { createOpenAI } from '@ai-sdk/openai';
import { GoogleGenAI } from '@google/genai';
const openai = createOpenAI({ apiKey: client.sdkOptions.apiKey, fetch: client.fetch });
const google = new GoogleGenAI({ apiKey: client.sdkOptions.apiKey, httpOptions: { fetch: client.fetch } });The existing SDKs guide covers each SDK in full.
WalletRequestError
A request the wallet refused or could not answer. It extends Error with the request's HTTP status.
Prop
Type
| Status | Cause |
|---|---|
| The provider's status | The provider refused the call. |
401 | The visitor has not connected the site. |
403 | The visitor declined the connection, the wallet is locked, or it holds no account. |
499 | The visitor stopped the answer in AI Wallet. |
AI Wallet builds its errors with its own copy of the class, so a site reads name and status and leaves instanceof aside. A chat aborted by the site's own signal rejects with the signal's reason instead.
try {
transcript.push(...await runConversation({ session: state.session, tools, messages: transcript }));
} catch (error) {
const { name, status } = error as { name?: string; status?: number };
if (name === 'WalletRequestError' && status === 499) {
showNotice('You stopped the answer in AI Wallet.');
}
}McpToolSource
The site's tools, read from its MCP server over Streamable HTTP. It implements ToolSource.
Prop
Type
- constructor.
new McpToolSource(url, clientName)takes the server's address, as a string or aURL. A relative URL resolves against the page's address, so outside a page pass an absolute one.clientNameis the client name your MCP server sees when the client initializes,aiconnectby default. - listTools(). Connects on first use, reads the tool list once and keeps it for the life of the instance. A failed attempt is forgotten, so the next call asks the server again.
- callTool(name, args). Returns the result's text parts joined with line breaks. A result marked
isErrorcomes back asTool error:followed by its text.
const tools = new McpToolSource('/api/mcp', 'Bookshop');
const definitions = await tools.listTools();
const result = await tools.callTool('search_books', { mood: 'calm' });The MCP guide covers the server side.
runConversation
Runs the agent loop in the browser: the model answers or asks for tools, the site's tools run, their results go back to the model, until it answers in plain text.
Prop
Type
It returns the messages the conversation gained: the assistant turns and the tool results, ending with the final assistant answer. It leaves options.messages untouched, so append the result to your transcript yourself.
- It lists the tools once, then sends
messagesand the tool definitions tosession.chat. It sends no generation options; callsession.chatdirectly to set them. - It runs the tools the model asks for one after the other, in the model's order, and adds each result as a
toolmessage. - A tool that throws becomes a result with the text
Tool error:and the error's message, which the model reads. - It stops when an answer has no tool calls, when no
toolswere given, or aftermaxStepssteps (6 by default). The last step offers no tools, so the model has to conclude. - An error from
session.chat, such as a provider refusal or the visitor stopping the answer in the wallet, rejects the promise.
const { state } = client;
if (state.status === 'connected') {
const asked: ChatMessage = { role: 'user', content: 'A calm book for tonight' };
const added = await runConversation({
session: state.session,
tools,
messages: [{ role: 'system', content: SYSTEM }, asked],
onText: (delta) => output.append(delta),
});
transcript.push(asked, ...added);
}ConversationOptions
Prop
Type
- session. The
sessionof aconnectedstate. - messages. The conversation so far, system prompt first.
- tools. The tool source. Without it, the model answers once.
- maxSteps. The number of model calls at most. The default is 6.
- signal. Passed to
session.chaton each step. With the wallet's session, aborting it stops the chat in AI Wallet, and the promise rejects with the signal's reason. - onText, onToolCall, onToolResult. The hooks.
const controller = new AbortController();
stopButton.onclick = () => controller.abort();
const added = await runConversation({ session: state.session, tools, messages, maxSteps: 3, signal: controller.signal });ConversationHooks
Prop
Type
- onText. Each piece of the answer's text as it streams, across every step of the loop.
- onToolCall. A tool call, before the tool runs.
- onToolResult. The same call with its result, after the tool ran or failed.
await runConversation({
session: state.session,
tools,
messages,
onToolCall: (call) => showStatus(`Running ${call.name}`),
onToolResult: (call, result) => renderCards(call.name, result),
});finalAnswer
Prop
Type
Returns the last message when it is an assistant message, and null otherwise.
const added = await runConversation({ session: state.session, tools, messages });
const answer = finalAnswer(added)?.content ?? '';Chat content helpers
The functions that read a message's content, as parts or as text, and an image's data: URL as its media type and base64 data. AI Wallet's connectors use them in every translation.
Prop
Type
- contentParts(content). The content as parts. A string becomes one text part, and an empty string none.
- textOf(content). The text of the parts joined, without the images.
- inlineData(url). The media type and data of a base64
data:URL, ornullfor any other URL. - dataUrl(data). The
data:URL of anInlineData.
InlineData
Prop
Type
const question: UserMessage = {
role: 'user',
content: [
{ type: 'text', text: 'What does this label say?' },
{ type: 'image', url: dataUrl({ mediaType: 'image/png', data: PNG_BASE64 }) },
],
};
textOf(question.content); // 'What does this label say?'
// An Anthropic image block: inline data when the URL carries it, the URL otherwise.
const blocks = contentParts(question.content).map((part) => {
if (part.type === 'text') {
return { type: 'text', text: part.text };
}
const inline = inlineData(part.url);
return inline
? { type: 'image', source: { type: 'base64', media_type: inline.mediaType, data: inline.data } }
: { type: 'image', source: { type: 'url', url: part.url } };
});Chat types
The chat format the site and the wallet share. The wallet translates it to each provider's own format and back. ChatMessage is a union of four messages, one per role:
Prop
Type
SystemMessage
Prop
Type
UserMessage
Prop
Type
content is a string, or a list of parts when the message carries images.
AssistantMessage
Prop
Type
ToolMessage
Prop
Type
A ToolMessage answers the ToolCall whose id it carries in toolCallId.
ToolCall
Prop
Type
ContentPart
A union of two parts, one per type:
Prop
Type
TextPart
Prop
Type
ImagePart
Prop
Type
url is a data: URL, or an https URL for the providers that fetch images themselves.
const messages: ChatMessage[] = [
{ role: 'system', content: 'You recommend books from this site\'s catalog.' },
{ role: 'user', content: 'A calm book for tonight' },
{ role: 'assistant', content: '', toolCalls: [{ id: 'call_1', name: 'search_books', arguments: { mood: 'calm' } }] },
{ role: 'tool', toolCallId: 'call_1', name: 'search_books', content: '[{"title":"Piranesi"}]' },
];
const cover: ContentPart[] = [
{ type: 'text', text: 'Which book has this cover?' },
{ type: 'image', url: 'https://example.com/covers/piranesi.jpg' },
];
messages.push({ role: 'user', content: cover });Tool types
The tools a site offers. Any object that implements ToolSource can feed runConversation and useAiChat.
ToolDefinition
Prop
Type
ToolSource
Prop
Type
JsonSchema
The type of inputSchema, a JSON Schema object:
Prop
Type
const empty: JsonSchema = { type: 'object', properties: {} };
const clock: ToolSource = {
listTools: async () => [{
name: 'current_time',
description: 'The current time of the visitor\'s computer, in UTC, as an ISO 8601 string.',
inputSchema: empty,
}],
callTool: async () => new Date().toISOString(),
};GenerationOptions
What the site asks of the answer, in ChatRequest.options.
Prop
Type
maxTokens, temperature, topP and stop are hints: the account's connector leaves out those its provider refuses. toolChoice and responseFormat always reach the provider.
GenerationHint
The names of the hints, each with the type of its option:
Prop
Type
ToolChoice
Whether the model may answer in text, must call a tool, or must call the named one. A tool choice is one of these strings:
Prop
Type
Or an object that names the tool:
Prop
Type
ResponseFormat
An answer in JSON, held to schema when one is given. strict asks the providers that offer it to refuse any schema they cannot enforce exactly.
Prop
Type
const { message, finishReason } = await state.session.chat({
messages: [{ role: 'user', content: 'Three calm books, as JSON.' }],
options: {
maxTokens: 400,
toolChoice: 'none',
responseFormat: {
type: 'json',
name: 'books',
schema: { type: 'object', properties: { titles: { type: 'array', items: { type: 'string' } } } },
},
},
});
const { titles } = JSON.parse(message.content) as { titles: string[] };
// The hints a ChatGPT plan account refuses, which its connector leaves out of every call.
const refusedByChatGpt: GenerationHint[] = ['maxTokens', 'temperature', 'topP'];Session types
A ProviderSession answers chats. On a site it is the wallet answering with the visitor's account: the session of a connected state, with providerId set to aiwallet and model set to the account's default model, or an empty string with Auto, where the request's onStart names the account and model of each answer. Its chat sends one request to the wallet and resolves with the answer.
ProviderSession
Prop
Type
ChatRequest
Prop
Type
onStart receives who answers, once, before the first text or the result: the account's display name and the model the wallet asks it for. A site on Auto learns there which account the wallet picked. onText receives the answer's text as it streams. Aborting signal stops the chat in the wallet.
ChatStart
Prop
Type
ChatResponse
Prop
Type
model is the model that answered, as the provider named it. usage is absent when the provider reports no token counts.
FinishReason
Why the answer ended: it was complete, it reached the token limit, it calls tools, or the provider's filter stopped it.
Prop
Type
Usage
The tokens the provider counted for the call. inputTokens includes cachedInputTokens.
Prop
Type
const { state } = client;
if (state.status === 'connected') {
const response: ChatResponse = await state.session.chat({
messages: [{ role: 'user', content: 'Summarise this page in two sentences.' }],
onText: (delta) => output.append(delta),
});
if (response.finishReason === 'length') {
output.append(' (cut at the token limit)');
}
const usage: Usage | undefined = response.usage;
console.log(response.model, usage?.inputTokens, usage?.outputTokens);
}WalletLink
What a site sees of the wallet: whether it may use the visitor's AI, and then which account answers it and on which model. AiConnect builds its ConnectionState from the links the wallet returns and announces. You meet this type directly when you implement the wallet protocol.
A site that is not connected:
Prop
Type
A connected site:
Prop
Type
function describe(link: WalletLink): string {
return link.connected ? `${link.providerName} answers this site` : 'Not connected';
}WalletProvider
The wallet as a page sees it: window.aiwallet, which the extension announces to every page. The wallet protocol specifies its methods.
Prop
Type
A chat streams its events to onEvent: one start naming who answers, then each piece of text. Aborting signal rejects the request with the signal's reason, and the wallet stops the chat it runs for it. onLink registers a listener for the visitor's changes, in the wallet, to the account or model the site answers with, and returns a function that removes it.
WalletChatParams
What a site sends with a chat request.
Prop
Type
A wallet of your own for a page's tests, set before the page creates its AiConnect:
const testWallet: WalletProvider = {
isAIWallet: true,
request: async (method, params, onEvent) => {
if (method === 'status' || method === 'connect') {
return { connected: true, providerName: 'Test' } satisfies WalletLink;
}
if (method === 'disconnect') {
return { connected: false } satisfies WalletLink;
}
if (method === 'chat') {
const { messages } = params as WalletChatParams;
const content = `You said: ${textOf(messages.at(-1)?.content ?? '')}`;
onEvent?.({ kind: 'start', providerName: 'Test', model: 'test' });
onEvent?.({ kind: 'text', delta: content });
return { message: { role: 'assistant', content }, finishReason: 'stop', model: 'test' } satisfies ChatResponse;
}
throw new WalletRequestError(`Unknown method ${method}`, 400);
},
onLink: () => () => undefined,
};
Object.assign(window, { aiwallet: testWallet });ChatEvent
What a chat streams to the page before its result, in order: one start, sent before the first text or, when the answer has no text, before the result, then a text event per piece of text. The wallet protocol describes them.
Prop
Type
Theming
Match the connect window to your design system with the CSS tokens of @aiconnect/design, in light and dark, and in right to left languages.
@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.