AI
Assistant turns grounded in a workspace's own data, structured extraction, and the readiness report — for a signed-in member.
connect.ai.* acts for a signed-in member of a workspace: it answers from
that workspace's own corpus, and it stores a conversation against the person
who held it. The public counterpart — a stranger on your customer's marketing
site — is the visitor assistant, and the two deliberately
share no store.
Four scopes, because four different outbound surfaces:
| Call | Scope |
|---|---|
chat, listConversations, getConversation, deleteConversation | ai:chat |
extract | ai:extract |
draftAssessment, reviewAssessment | ai:assess |
readiness | ai:readiness |
The panel
import { ConnectAskAi } from "@cosmoops/connect-sdk/client";
<ConnectAskAi
screens={[
{ path: "/deals/[dealId]", purpose: "Work one deal: stage, value, next step.",
suggestions: ["What changed on this deal?", "Who is the contact?"] },
]}
renderLink={({ href, className, children }) => (
<Link href={href} className={className}>{children}</Link>
)}
/>screens is what makes it your product's assistant. Without them it
answers from general knowledge about software, because Connect has no idea
what your application is. With them, "what can I do here?" answers in your
own words, an empty panel offers your own opening questions, and actions can
link into your routes. Write dynamic segments in brackets — /deals/[dealId]
— so a live path with a real id still matches.
They are prose and links only. Connect never uses a screen description to decide what anyone may do, and never executes anything on your behalf.
Leave activePath unset in a Next app: the panel resolves the live path
itself through a useSyncExternalStore whose server snapshot is empty, so it
works inside a shared App Shell where usePathname() is refused outright
(CLIENT_HOOK_DYNAMIC). Pass it only where your router's path is not
location.pathname — a hash router, or a panel under a basePath your routes
do not include.
Chat, server-side
const response = await connect.ai.chat({
workspaceId,
messages,
asUser: sessionToken,
conversationId,
});It returns the raw streaming Response, not a parsed body — the endpoint
answers server-sent events, and buffering here would throw away the only
reason to stream. Pipe response.body onward, or hand it to the AI SDK's own
reader.
asUser takes a signed-in member's session token, and Connect then resolves
the workspace from that, ignoring workspaceId. Without it you are the
application, naming the workspace yourself. The token travels as a header and
never reaches the body.
History belongs to a person
conversationId is your own id for a conversation, sent with every turn
of it. Connect upserts against it, and that is what makes the conversation
resumable.
It is persisted only alongside asUser — a conversation belongs to
someone, and a server-to-server call acts as nobody. Omit the id and the turn
stays out of history entirely, which is how a caller opts out.
const { items } = await connect.ai.listConversations({ asUser: sessionToken });
const detail = await connect.ai.getConversation({ conversationId, asUser: sessionToken });
await connect.ai.deleteConversation({ conversationId, asUser: sessionToken });asUseris required on all three, unlike onchat: the rows are scoped to a person and there is no application-wide view to fall back to.- The list is cursor-paginated with no total — it is ordered by last use, which moves while it is being read.
turns[].partscomes back exactly as the model produced it — prose, reasoning and tool calls in order — so it can be handed straight to a chat UI. A conversation that is not this user's, or not your application's, is a404either way.deleteConversationis idempotent: one already gone answers{ deleted: true }, so a retry after a dropped response does not read as a failure.
Extraction
const fields = await connect.ai.extract<{ name: string; phone: string }>({
schemaId: process.env.CRM_AI_CAPTURE_SCHEMA_ID!,
fileUrl,
});Structured JSON out of a file or image URL, against a platform extraction schema referenced by id — Connect holds the prompt and the JSON Schema, your application holds only the id. So the wording that reads a business card is tuned in one place for every application that reads one, and there is no prompt in your repo to drift.
A practical consequence worth copying: treat the id as deployment configuration, and draw no control at all when it is unset. An environment without a schema id should render no camera button rather than one that always fails.
Readiness
How ready the site at a domain is for AI agents: it probes for an MCP server,
an OpenAPI description, llms.txt, structured data and a markdown twin,
evaluates robots.txt against the assistants that actually fetch pages, and
checks whether the content survives without JavaScript.
const result = await connect.ai.readiness({ domain: "acme.com" });
if (result.status === "assessed") {
console.log(result.report.grade, result.report.topFix?.fix);
}- Set your client timeout to at least 90 seconds. A cold assessment runs
the whole probe before answering. Cached reads return in milliseconds and
are the normal case — reports are held for a week, not the ninety days a
brand profile gets, because a report is a to-do list somebody is working
through. Pass
refreshafter shipping a fix to see it land. - A check with
status: "error"is unknown, not failed. Connect could not measure it, and it is excluded from the score rather than counted against it. skippedsays whose problem it is.unreachablemeans the site did not answer;provider_unavailablemeans Connect's own service did. Only the second is worth retrying silently, and neither should be rendered as a failing grade.
draftAssessment and reviewAssessment round out the surface: they draft
every section of a platform-defined assessment from one sentence, then review
the answers. Both are unattributed — assessments run against platform-wide
definitions, and the first consumers run before anyone has signed in, so there
is no asUser and no workspace. The intended flow is draft first and correct
section by section: a blank questionnaire is why most people abandon one, and
editing a strawman is a far smaller ask than authoring from nothing.
Next steps
- Visitor assistant — the same idea for someone with no account, answering from a separate published store.
- MCP — register an endpoint of your own so Connect's assistant can call your tools, as the member asking.
- API reference — the endpoints and their schemas.