Chat providers
A chat provider runs the turns of an agent chat that Terminay owns, such as a conversation with Pixel. Terminay keeps the conversation, mints the MCP capability, and shows approvals; the provider starts the user's installed agent and streams what it says. Added in API 3.2.
Manifest contribution
"api": "^3.2.0",
"permissions": ["agent-chat"],
"contributes": {
"agentChatProviders": [{
"id": "com.example.agent/chat",
"displayName": "Example Agent",
"description": "Chats through the Example Agent CLI.",
"platforms": ["darwin", "linux"]
}]
}
Declare contributes.agentChatProviders with a namespaced id, a
displayName, and optional description and platforms. It requires
the agent-chat permission. A manifest may declare up to 8.
Runtime contract
Register during activation with context.agents.registerChatProvider(id, runtime).
import { defineExtension } from '@terminay/extension-api';
export default defineExtension({
activate(context) {
context.subscriptions.add(
context.agents.registerChatProvider('com.example.agent/chat', {
async availability(signal) {
const cli = await findExampleCli(signal);
if (!cli)
return {
available: false,
reason: 'not_installed',
detail: 'npm install -g example-agent',
};
return { available: true };
},
async openSession(request, events, signal) {
// Start the user's installed CLI in request.workingDirectory, with
// request.mcpServer as its only MCP server, and stream its output.
const agent = await startExampleAgent(request, signal);
agent.on('text', (delta) => events.text(delta));
agent.on('tool', (call) =>
events.tool({ callId: call.id, server: call.server, name: call.name, state: call.state }),
);
agent.on('exit', (code, sig) => events.exit({ code, signal: sig }));
return {
prompt: (text, turnSignal) => agent.runTurn(text, turnSignal),
cancel: () => agent.interrupt(),
close: () => agent.kill(),
};
},
}),
);
},
});| Member | Contract |
|---|---|
availability(signal) | Returns { available: true } or { available: false, reason, detail? }, where reason is not_installed, not_signed_in, or unsupported_platform. Put the command that fixes it in detail. |
openSession(request, events, signal) | Starts one session and resolves to { prompt, cancel, close }. |
session.prompt(text, signal) | Runs one turn and resolves when the agent finishes or cancel() stops it. |
session.close() | Ends the session and its process. |
Request member Meaning sessionIdOpaque, at most 128 characters. workingDirectoryAn empty, private directory for this session. instruction?System-level instruction for the agent, at most 8,192 characters. mcpServerThe one MCP server the agent may use (name, command, args, env), or null when the chat has no MCP reach.
Events
Event Report events.text(delta)Reply text as it streams, at most 64 KiB per delta. events.tool({ callId, server?, name, state })A tool call by name only, with state running, completed, or failed. Never arguments or results. events.failure({ reason, detail? })not_installed, not_signed_in, crashed, or failed. events.exit({ code, signal })The agent process ended.
Confinement
A provider must confine its agent to mcpServer:
- Turn built-in tools off where the agent allows it, and decline every request for any other tool.
- Load no other MCP server and no user MCP configuration.
- Keep no session history on disk, and write nothing to the agent's own configuration, settings, hooks, or credentials.
- Start the user's installed agent. Never download one.
A provider cannot read or change the conversation, the capability, or the approvals Terminay shows.
Testing
createAgentExtensionHarness drives a provider with chatAvailability(providerId) and
openChatSession(providerId, request?), which fills the request from fixtureChatSessionRequest.
The returned session records its validated events(), any violations(), and the reply
text(). See Testing and conformance.