CopilotSurface
Showing a Copilot turn inside the connector's chat — the display that opens the moment a message is accepted, the repaints with the thinking, the tools and the answer so far, and the finished answer with its links, its follow-ups and its confirmation card.
Subscriby\Connector\Contracts\Ports\CopilotSurface. Bound by copilot.
The core drives the four calls in order, the first from the request that accepted the creator's message and the rest from the queue: begin() the moment the message is accepted, progress() about once a second while the model streams, then one finish() or one fail(). The whole state travels on every repaint, so a connector repaints rather than appends and a lost call costs nothing. What a display is (a streamed draft on a platform that has them, a message edited in place on one that does not) is yours, kept in a CopilotTurnHandle of scalars the core carries back unopened.
Methods
| Method | Called when | Receives | Returns |
|---|---|---|---|
begin() | Copilot::ask() or a confirming decide() accepted the message, in that request. | CopilotTurnOpening: the thread, the thinking sentence, and the question when it was tapped rather than typed. | CopilotTurnHandle |
progress() | The model streamed something new, at most about once a second, from the worker. | The handle and a CopilotTurnProgress: the reasoning so far, the tools in words, the answer so far as Markdown and as canonical HTML. | CopilotTurnHandle |
finish() | The turn ended with an answer. | The handle and a CopilotTurnAnswer. | void |
fail() | The turn ended without one. | The handle and one sentence, in the creator's language. | void |
The handle
CopilotTurnHandle is array<string, scalar|null> behind get() and with(): a draft id, a message id, which of the two the turn is on, whatever you need to find the display again. The core never reads it and carries it across the queue, which is why it holds scalars only. progress() returns a handle because a display may move: a platform that refuses the draft mid-turn is answered with a plain message from then on, and the handle says so.
The answer
CopilotTurnAnswer carries the answer twice, as the Markdown the model wrote with the follow-up block already removed and as the canonical HTML subset every renderer takes; links, one MessageAction::url() per dashboard page the turn's work opens at, each a signed address that lands the creator in the dashboard signed in; suggestions, up to three follow-up requests in the creator's words, for buttons of your own that send them back through Copilot::ask() with showQuestion set; card, the CopilotPendingCard the turn paused on, every label translated (the title, the tool in words, one line per argument, the note about what confirming costs, the two button labels), whose actionId your two buttons carry to Copilot::decide(); changedSomething; and question, the tapped question to repeat above the answer, since the chat never showed it.
How Telegram does it
The platform bot opens a rich message draft with a <tg-thinking> block that reads "Subscriby Copilot is thinking…", repaints the same draft with the reasoning's tail, a line per tool and the answer in Rich Markdown as it arrives, and sends the answer as a rich message with the card's Confirm and Cancel, the links and the follow-ups as inline buttons; the follow-ups' words are parked in the chat's storage, because callback data has sixty-four bytes. A Bot API server without the two calls is answered with a plain message edited in place, the handle remembering which of the two the turn is on.
What the kit checks
No kit rule drives CopilotSurface, because a display needs a platform. The fake connector binds FakeCopilotSurface, which records every opening, repaint, answer and failure (assertOpenedFor(), assertFinishedFor(), assertNothingOpened()); drive the core's side against it and your platform's fake client against the port.
Open the display in the request
begin() runs inside the connector's own webhook request so the creator sees the Copilot thinking at once; keep it to one platform call and let the worker do the rest. A begin() that throws stops the turn before anything is charged.
How is this guide?
Last updated on
ManagementSurface
The creator's in-chat admin surface over the core's command catalogue — declaring the commands, handling events, rendering command buttons, and calling the same actions the dashboard calls.
ProvidesPaymentMethods
Payment methods that exist only because of a platform — the NativePaymentProvider contract, why the capability is official-only, and how the core shows, sets up and rates a native currency.