Copilot API¶
Status: Draft | Last reviewed: 2026-10-07
How to put a TrueServe API in front of the copilot (agent-assist) pipeline so a custom Salesforce Lightning Web Component — or any host — gets real-time suggestions and chat Q&A during a live conversation, with the underlying AI (Google, AWS, or a TrueServe agent) swappable by config. Read this before building the /copilot/* facade or a Salesforce connector; it maps what already exists to what a follow-up must add.
This is a design/feasibility doc. No /copilot/* routes or connector LWC exist yet — the sections below name the existing building blocks each one reuses.
For live Genesys transcripts, the existing AudioHook connector and conversation SDK already provide the capture/subscription path without this proposed facade. Use Live Genesys transcripts for that implemented contract and its external test-phone limitations.
The existing conversation API now supports host events without the proposed
facade: POST /conversations/{id}/ui-action accepts delivery: card | stream |
both. SDK sendCopilotEvent uses stream delivery; opted-in Copilot workers
process it independently of speech. The Salesforce Agent Assist wrapper
exchanges these events over LMS. See Copilot host context
and the embed protocol.
The Google Agent Assist contract you are replacing¶
Google's agent-assist-integrations ships the reference backend for hosted Agent Assist. Its contract has three seams:
- Browser event bus — a global
add/dispatchAgentAssistEvent(name, {detail}, {namespace})API served by prebuilt web components (gstaticcommon.js/container.js). Key events:analyze-content-requested,analyze-content-response-received,smart-reply-selected,conversation-summarization-received,search-knowledge-requested/-response-received. - UI-Connector backend — Cloud Run Flask service:
POST /registerexchanges a platform token for a short-lived HS256 JWT, then transparently proxies the Dialogflow v2beta1 REST surface and pushes voice suggestions over Socket.IO (join-conversation+ four event types). - Dialogflow resources —
ConversationProfile/Conversation/Participant/AnalyzeContent, each suggestion carrying ananswerRecordyou PATCH for feedback.
You do not reimplement any of this. The Salesforce panels that consume Agent Assist (below) discard Google's widget UI and its Dialogflow event vocabulary — they keep only a small provider-agnostic seam. That seam is the target.
The DCC swap seam (target contract)¶
The two Aura containers to integrate — DCC_RightPanel (voice) and DCC_CoupaChatRightPanel (chat) — live in the devsdo Salesforce org, not in this repo. Retrieve them read-only with sf project retrieve start -m "AuraDefinitionBundle:DCC_RightPanel" -o devsdo.
Each panel embeds Google's connector headless: a hidden div mounts agentAssistContainerModule / googleAgentAssistHeadlessConnector, which subscribes to the Agent Assist event bus and re-emits every event upward as one bubbling agentassistevent carrying { eventType, eventDetail }. Transform LWCs then normalize that into a provider-agnostic seam that three backends — Google (dccGoogleAgentAssistInsights), a raw WebSocket (dccRealTimeInsights), and Amazon Q (dccAmazonQRealTimeInsights, over a Lightning Message Service channel) — already emit identically.
| Direction | Event | detail shape |
|---|---|---|
| Backend → panel (insights) | conversationinsight |
{ isQa: true, questionData: { question, answer, id, status, isAnswerGenerated, currentTime, isRead, recommendationsList } } |
| Panel → backend (chat) | sendquery |
{ query } |
| Backend → panel (chat) | receiveResponse (via @api) |
{ answer, sources: [{ title, url }], error } |
Because Amazon Q already rides this seam without any Dialogflow vocabulary, a TrueServe backend joins the same way: a connector LWC that emits conversationinsight and answers sendquery with receiveResponse. No /register, no Socket.IO, no answerRecord.
TrueServe substrate you build on¶
The copilot runtime already exists end to end. The API is a facade plus a subscribe/turn broker over it — not new AI.
| Capability | Where it lives |
|---|---|
| Structured-output suggestion LLM | SuggestionLLMProcessor — per-type JSON schemas (smart_reply, response_suggestion, knowledge_assist, article, form_filling); provider env map covers openai / google_cloud / google_vertex / aws_bedrock |
| Service-agnostic suggestion frame | suggestion_frames.py — SuggestionType enum + SuggestionFrame |
| Frame → conversation-api | ConversationAPIPublisher → Redis entries:{id} (+ reserved entries:{id}-assist shard) |
| Redis → SSE normalization | process_sse_outgoing_entry in http_api.py |
| First-class copilot agent subtypes | agent_assistant, voice_augmentation in service_constants.py |
| Synchronous turn pattern | run_agent_turn() in a2a_server.py |
The SSE stream (GET /sse/conversations/{id}) already emits copilot entries. The normalizer collapses SUGGESTED_RESPONSE and SUGGESTED_KNOWLEDGE to type: "knowledge_suggestion" (with suggestion_type smart_reply / article), SUGGESTION to knowledge_suggestion or suggestion (form-filling), and CUSTOM_INSIGHT to its own kind. That is the raw material a connector maps to conversationinsight.
flowchart LR
subgraph SF["Salesforce (devsdo org)"]
panel["DCC_RightPanel / DCC_CoupaChatRightPanel"]
connector["trueServeCopilotConnector (new LWC)"]
insights["dcc*RealTimeInsights"]
chat["dccCoupaAgentAssistChat"]
end
subgraph CAPI["conversation-api"]
facade["/copilot/* facade (new)"]
sse["GET /sse/conversations/:id"]
turn["run_agent_turn()"]
redis[("Redis entries:{id} + -assist")]
end
subgraph ORCH["ai-orchestrator"]
proc["SuggestionLLMProcessor"]
pub["ConversationAPIPublisher"]
llm{{"stage provider/service"}}
end
llm --> proc --> pub --> redis
redis --> sse
redis --> turn --> facade
connector -- subscribe --> sse
sse -- "suggestion / knowledge_suggestion" --> connector
connector -- "conversationinsight" --> insights
chat -- "sendquery" --> connector
connector -- "POST /copilot/:id/query" --> facade
facade -- "{answer, sources[]}" --> connector -- receiveResponse --> chat
Proposed copilot API facade¶
Add a /copilot/* router to conversation-api. Every route reuses existing plumbing; none needs a new AI path.
| Route | Purpose | Reuses |
|---|---|---|
POST /copilot/sessions |
Open a copilot session against an agent, or bind to an existing conversation | create_session; get_conversation_by_external_id |
POST /copilot/{id}/analyze |
Push one transcript turn (foreign-telephony fallback) | the POST /conversations/{id}/inject / send_user_message path |
POST /copilot/{id}/query |
Synchronous chat Q&A → { answer, sources[] } |
a generalized run_agent_turn |
POST /copilot/{id}/summary |
Conversation summary on demand | a copilot agent turn |
GET /sse/conversations/{id} |
Live suggestion stream (existing — no new route) | process_sse_outgoing_entry |
Suggestions flow over the existing SSE suggestion / knowledge_suggestion events; the facade adds no second stream. The synchronous endpoints reuse the Redis-drain turn pattern: create or reuse a session, post the text, drain entries:{id} until the turn goes idle or A2A_TURN_TIMEOUT_SEC elapses, return the collected agent text.
Authenticate a Salesforce caller with either a tsf_ bearer token (agents:manage) presented from Apex via a Named Credential, or the surface-internal HMAC path that surface-app already uses. The Salesforce package uses separate endpoints for these calls: ai-manager mints embed sessions, while GET /conversations/by-external-id/{external_id} targets the public conversation-api host. Pointing the resolver Named Credential at the Agent Studio web host produces a 307 /login response that Apex will not follow. Register the Salesforce origin in ai-manager's allowed-origins for any browser-direct calls (GET /api/allowed-origins/active gates conversation-api CORS).
The connector LWC (swap point)¶
Build one small LWC — call it trueServeCopilotConnector — as a drop-in replacement for agentAssistContainerModule inside the DCC panels. Both panels already forward their connector's output centrally (handleGoogleAgentAssistEvent fans agentassistevent out to child components), so the connector only has to emit the normalized events the children already understand:
- On mount, resolve the conversation (see binding modes below) and subscribe to
GET /sse/conversations/{id}. - Map each
suggestion/knowledge_suggestion/custom_insightSSE event to aconversationinsightCustomEvent with the{ isQa, questionData }shape from the seam table. - Handle
sendqueryby callingPOST /copilot/{id}/queryand returning the result todccCoupaAgentAssistChatvia itsreceiveResponse@api.
Use the existing Agent Studio for Salesforce package as the auth precedent: TrueServeSurfaceController.mintEmbedToken(agentId, conversationId, audienceOrigin, sessionVars) already mints the embed JWT from Apex, and its LWCs already consume TrueServe SSE (final_transcript).
Voice vs chat binding¶
The chat panel always drives a text session. The voice panel resolves the transcript of a live call, and there are two modes — support both.
- Resolve an existing conversation (primary). When the call runs on the TrueServe stack (Service Cloud Voice BYOT via connect-scv-translator, or TrueServe telephony), the conversation and its transcript already exist. The LWC resolves it with
GET /conversations/by-external-id/{external_id}(this route exists; its code comment names Salesforce LWCs as the consumer and it returns{conversation, surface_config_id, surface_config}), then subscribes to SSE. No transcript is pushed. - Push the transcript (fallback). When the call runs on foreign telephony (Genesys, Amazon Connect) and TrueServe provides only the copilot, Salesforce pushes each transcript turn to
POST /copilot/{id}/analyze(mirroring Google'sanalyze-content-requested), and suggestions come back over SSE.
Swapping the underlying AI¶
This is the payoff. The copilot's LLM is the stage provider / service in agent_versions.config. SERVICE_LOOKUP_V2 already carries AWS/Amazon (Nova Sonic, Bedrock), Google, OpenAI, and Salesforce Agentforce entries. Retargeting the copilot from one backend to another is a config edit resolved by get_service_mapping() — no change to the /copilot/* API, the connector LWC, or the DCC panels. See universal-pipeline-builder.md for how config becomes a running pipeline.
Feasibility verdict and gaps¶
Ready today:
- The suggestion pipeline (
SuggestionLLMProcessor→SuggestionFrame→ publisher) and its SSE events. agent_assistant/voice_augmentationsubtypes.GET /conversations/by-external-idfor voice binding.run_agent_turnas the synchronous-turn template.- The DCC provider-agnostic seam (proven by three existing backends).
- Config-driven AI swap via
SERVICE_LOOKUP_V2.
The follow-up must build:
- The
/copilot/*facade routes in conversation-api. - A generalization of
run_agent_turnforquery/summary(parameterize the throwaway vs. bound conversation). - The
trueServeCopilotConnectorLWC. - Registration of the Salesforce origin in allowed-origins.
Watch these:
- Sync-turn latency.
query/summaryblock on the Redis drain up toA2A_TURN_TIMEOUT_SEC(default 45s). Keep copilot turns short, or return partials. - Token handling in the browser. A browser LWC must not hold a
tsf_token. Use an Apex proxy or the embed/HMAC path; the token stays server-side.