@signalcore/ui 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -21
- package/dist/AgentAbout.js +6 -2
- package/dist/AgentAbout.js.map +1 -1
- package/dist/AgentChat.d.ts +25 -3
- package/dist/AgentChat.js +302 -28
- package/dist/AgentChat.js.map +1 -1
- package/dist/AgentSpace.d.ts +25 -4
- package/dist/AgentSpace.js +55 -11
- package/dist/AgentSpace.js.map +1 -1
- package/dist/Threads.d.ts +73 -0
- package/dist/Threads.js +140 -0
- package/dist/Threads.js.map +1 -0
- package/dist/View.d.ts +48 -0
- package/dist/View.js +101 -0
- package/dist/View.js.map +1 -0
- package/dist/automation/marks.js +7 -3
- package/dist/automation/marks.js.map +1 -1
- package/dist/automation/messages.d.ts +4 -0
- package/dist/automation/messages.js +5 -1
- package/dist/automation/messages.js.map +1 -1
- package/dist/automation/when.js +8 -0
- package/dist/automation/when.js.map +1 -1
- package/dist/blocks.d.ts +83 -2
- package/dist/client.d.ts +23 -5
- package/dist/client.js +8 -2
- package/dist/client.js.map +1 -1
- package/dist/contract.d.ts +9 -4
- package/dist/es.d.ts +2 -0
- package/dist/es.js +34 -3
- package/dist/es.js.map +1 -1
- package/dist/frames.d.ts +7 -1
- package/dist/frames.js.map +1 -1
- package/dist/index.d.ts +14 -7
- package/dist/index.js +11 -4
- package/dist/kit/Agenda.d.ts +25 -0
- package/dist/kit/Agenda.js +119 -0
- package/dist/kit/Agenda.js.map +1 -0
- package/dist/kit/Combobox.js +1 -1
- package/dist/kit/DateField.js +1 -1
- package/dist/kit/Kanban.d.ts +17 -0
- package/dist/kit/Kanban.js +157 -0
- package/dist/kit/Kanban.js.map +1 -0
- package/dist/kit/TimeField.js +1 -1
- package/dist/kit/Timeline.d.ts +14 -0
- package/dist/kit/Timeline.js +71 -0
- package/dist/kit/Timeline.js.map +1 -0
- package/dist/kit/context.d.ts +1 -1
- package/dist/kit/context.js.map +1 -1
- package/dist/kit/messages.d.ts +22 -0
- package/dist/kit/messages.js +10 -0
- package/dist/kit/messages.js.map +1 -1
- package/dist/surface/Form.js +1 -1
- package/dist/surface/Placed.d.ts +20 -0
- package/dist/surface/Placed.js +70 -0
- package/dist/surface/Placed.js.map +1 -0
- package/dist/surface/Surface.d.ts +4 -2
- package/dist/surface/Surface.js +78 -2
- package/dist/surface/Surface.js.map +1 -1
- package/dist/surface/places.d.ts +16 -0
- package/dist/surface/places.js +15 -0
- package/dist/surface/places.js.map +1 -0
- package/dist/surface/scope.d.ts +17 -1
- package/dist/surface/scope.js +1 -0
- package/dist/surface/scope.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.js.map +1 -1
- package/dist/useConversation.d.ts +2 -0
- package/dist/useConversation.js +16 -0
- package/dist/useConversation.js.map +1 -1
- package/dist/widget.js +17 -16
- package/package.json +1 -1
- package/src/AgentAbout.tsx +4 -0
- package/src/AgentChat.tsx +417 -34
- package/src/AgentSpace.tsx +106 -14
- package/src/Threads.tsx +218 -0
- package/src/View.tsx +117 -0
- package/src/automation/marks.tsx +7 -0
- package/src/automation/messages.ts +8 -0
- package/src/automation/when.ts +4 -0
- package/src/blocks.ts +100 -2
- package/src/client.ts +42 -10
- package/src/contract.ts +404 -29
- package/src/es.ts +30 -0
- package/src/frames.ts +3 -1
- package/src/index.ts +36 -3
- package/src/kit/Agenda.tsx +161 -0
- package/src/kit/Kanban.tsx +182 -0
- package/src/kit/Timeline.tsx +58 -0
- package/src/kit/context.tsx +45 -1
- package/src/kit/messages.ts +31 -0
- package/src/styles.css +825 -66
- package/src/surface/Placed.tsx +98 -0
- package/src/surface/Surface.tsx +92 -1
- package/src/surface/places.ts +19 -0
- package/src/surface/scope.ts +24 -1
- package/src/types.ts +24 -0
- package/src/useConversation.ts +10 -0
- package/src/widget/element.tsx +54 -7
- package/src/widget/widget.css +13 -0
package/dist/types.d.ts
CHANGED
|
@@ -83,6 +83,12 @@ export interface Conversation {
|
|
|
83
83
|
* host's app (`host_person`, its `contact` then) or the widget's visitor.
|
|
84
84
|
*/
|
|
85
85
|
person?: ConversationPerson | null | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* For the person it is with: how many of the business's messages came since they last read it,
|
|
88
|
+
* and whether it waits on them (a question, their yes); left out by an older API.
|
|
89
|
+
*/
|
|
90
|
+
unread?: number | undefined;
|
|
91
|
+
waiting_on_person?: boolean | undefined;
|
|
86
92
|
}
|
|
87
93
|
/** Who a person's token or a publishable key started a conversation for. */
|
|
88
94
|
export interface ConversationPerson {
|
|
@@ -215,6 +221,8 @@ export interface Request {
|
|
|
215
221
|
input: Record<string, unknown>;
|
|
216
222
|
/** The person's when left out (an API from before it). */
|
|
217
223
|
decides?: Decides | undefined;
|
|
224
|
+
/** The JSON Schema of its input, which an edit given with the yes must fit. */
|
|
225
|
+
schema?: Record<string, unknown> | null | undefined;
|
|
218
226
|
}
|
|
219
227
|
/** An action a reply called: what, how it went, and what it drew. */
|
|
220
228
|
export interface Step {
|
|
@@ -280,6 +288,9 @@ export interface Message {
|
|
|
280
288
|
heard?: string | null | undefined;
|
|
281
289
|
/** On a channel, a person's message: what was decided of it; null otherwise. */
|
|
282
290
|
decision?: Decision | null | undefined;
|
|
291
|
+
/** The agent's words first, without a turn of the person's, and the run whose code sent them. */
|
|
292
|
+
proactive?: boolean | undefined;
|
|
293
|
+
run_id?: string | null | undefined;
|
|
283
294
|
created_at: string;
|
|
284
295
|
}
|
|
285
296
|
/**
|
|
@@ -432,6 +443,18 @@ export interface FileBlock {
|
|
|
432
443
|
title?: string;
|
|
433
444
|
file: Attachment | null;
|
|
434
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* An agent's view rendered for one contact (`GET /views/{name}/render`): its blocks as the kit
|
|
448
|
+
* draws them, filled from the rows when it was read, and `text`, the same in words.
|
|
449
|
+
*/
|
|
450
|
+
export interface ViewRendered {
|
|
451
|
+
name: string;
|
|
452
|
+
title: string;
|
|
453
|
+
contact_id: string;
|
|
454
|
+
blocks: Surface["blocks"];
|
|
455
|
+
text: string;
|
|
456
|
+
rendered_at: string;
|
|
457
|
+
}
|
|
435
458
|
/** Who holds the conversation's browser: the agent drives it, or the person does. */
|
|
436
459
|
export type BrowserControl = "agent" | "person";
|
|
437
460
|
/** Whose the browser is: a clean one in the cloud, or the owner's own Chrome. */
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/** The API's objects this package reads (v3/README.md). */\n\nimport type { ChannelKind } from \"./about\";\nimport type { Surface } from \"./blocks\";\n\n/** The channel a conversation is on, as a list draws it. */\nexport interface ChannelGlance {\n id: string;\n kind: ChannelKind;\n /** The number, the @username. */\n name: string;\n}\n\n/** The person a conversation on a channel is with, or a person who signed in to the host's app. */\nexport interface ContactGlance {\n id: string;\n /** As they show it on the channel; null when it doesn't say. */\n name: string | null;\n /** Where they are on it: the number, the Instagram id; their account in the host's app. */\n address: string;\n /** The owner marked them: the agent doesn't answer them; left out by an API from before. */\n muted?: boolean | undefined;\n}\n\n/**\n * Who answers a conversation: its agent; a person of the business (the agent paused in it: it\n * handed it over, or one of them wrote in it); or nobody (it is ignored: what comes is kept, never\n * answered).\n */\nexport type Holder = \"agent\" | \"person\" | \"nobody\";\n\n/** A change of who answers a conversation: to whom, why, who changed it and when. */\nexport interface Handover {\n holder: Holder;\n /** Why, in the words of whoever changed it; \"\" when they said nothing. */\n reason: string;\n /**\n * `owner` (a person of the business), `agent` (its `escalate`), `platform`, or an automation by\n * its file's name.\n */\n by: string;\n /** The automation's title, when one changed it. */\n title: string | null;\n at: string;\n}\n\n/**\n * What was decided of a person's message on a channel before the agent answered it: `deciding`\n * while the automations that decide go; then the agent answers it (`answer`), lets it pass this\n * time (`skip`), or the conversation goes to a person (`hold`) or to nobody (`ignore`).\n */\nexport interface Decision {\n kind: \"deciding\" | \"answer\" | \"skip\" | \"hold\" | \"ignore\";\n /** Why, in the owner's words; null when it says nothing. */\n reason: string | null;\n /** The automation that decided, by its file's name; null when the platform did (a filter, the hours). */\n automation: string | null;\n /** Its title. */\n title: string | null;\n /** Its run that decided. */\n run_id: string | null;\n}\n\n/** A conversation between a person and one agent. */\nexport interface Conversation {\n id: string;\n agent_id: string;\n /** A turn stopped, waiting for the person to answer on a surface. */\n pending: boolean;\n /** Who answers it now: its agent, a person of the business, or nobody. */\n handled_by: Holder;\n /** Its last change of who answers it; null when it never changed, left out by an older API. */\n handover?: Handover | null | undefined;\n /** The owner trying the agent out: no customer sees it, and it is never the agent's activity. */\n test: boolean;\n created_at: string;\n /** Its title in a list: what a person first said in it; null while they've said nothing. */\n title: string | null;\n /** On a channel: which, and the person; null (or left out by an older API) elsewhere. */\n channel?: ChannelGlance | null | undefined;\n contact?: ContactGlance | null | undefined;\n /** On a channel: when the agent's reply is due, once the person has been quiet a moment. */\n reply_due_at?: string | null | undefined;\n /** On a channel: when the person last wrote (WhatsApp's 24 h window counts from it). */\n last_inbound_at?: string | null | undefined;\n /** The call it is, when it is one; null elsewhere, and left out by an API from before calls. */\n call?: CallGlance | null | undefined;\n /**\n * Who it is with, when a person's token or a publishable key started it: a person of the\n * host's app (`host_person`, its `contact` then) or the widget's visitor.\n */\n person?: ConversationPerson | null | undefined;\n}\n\n/** Who a person's token or a publishable key started a conversation for. */\nexport interface ConversationPerson {\n kind: \"host_person\" | \"visitor\" | \"contact\" | \"owner\";\n /** The host's id for them, the visitor's id. */\n external_id: string;\n name: string | null;\n email: string | null;\n}\n\n/**\n * Why a call ended, as the API keeps it: the caller hung up, the agent said goodbye, it reached\n * its longest time, it failed, its end never came, or it was never taken.\n */\nexport type CallReason = \"caller\" | \"agent\" | \"max_duration\" | \"error\" | \"lost\" | \"missed\";\n\n/**\n * The call a conversation is (v3/VOICE.md): the owner's in the browser or a phone call, when the\n * agent took it and when it ended, why and how long.\n */\nexport interface CallGlance {\n id: string;\n kind: \"browser\" | \"phone\";\n /** Null while it is on. */\n reason: CallReason | null;\n /** From when the agent took it to when it ended; null while it is on. */\n seconds: number | null;\n started_at: string | null;\n ended_at: string | null;\n /** Whether it was recorded, to play from the conversation; left out by an older API. */\n recording?: boolean | undefined;\n}\n\n/** Who said a message on a channel: the person, the agent, or a person of the business. */\nexport type Author = \"contact\" | \"agent\" | \"operator\";\n\n/** Where a message that goes out on a channel got to: it only moves forward, and `failed` wins. */\nexport type Delivery = \"queued\" | \"sent\" | \"delivered\" | \"read\" | \"failed\";\n\n/** How a channel's account stands, as its provider last said. */\nexport type ChannelStatus = \"pending\" | \"active\" | \"error\" | \"disconnected\";\n\n/** A day of the week, as a channel's hours name it. */\nexport type Weekday = \"mon\" | \"tue\" | \"wed\" | \"thu\" | \"fri\" | \"sat\" | \"sun\";\n\n/** A stretch of a day, from one time to a later one (\"HH:MM\"). */\nexport interface HoursSpan {\n start: string;\n end: string;\n}\n\n/**\n * A week of hours in the agent's time zone: each day's stretches, in order and apart, four at\n * most; a day left out, or empty, is closed.\n */\nexport type WeekHours = Partial<Record<Weekday, HoursSpan[]>>;\n\n/**\n * How the agent behaves on a channel, the same for every text channel (each shows only what its\n * medium can do): when it answers, how what it says goes out, and what it is told there.\n */\nexport interface ChannelSettings {\n /** It answers on this channel; off, everything goes to the inbox for a person. */\n answers: boolean;\n /** Who it answers, in the owner's words; null: everyone. */\n filter: string | null;\n /** Words that, said in a message, have it answer without weighing the filter. */\n filter_keywords: string[];\n /** The week it keeps; null: it answers at any hour. */\n hours: WeekHours | null;\n /** It answers inside the hours, or outside them (the business's people take the hours). */\n answers_when: \"in_hours\" | \"out_of_hours\";\n /** Sent once per conversation while the hours keep it from answering; null: nothing. */\n away_message: string | null;\n /** WhatsApp: it answers in group chats too. */\n groups: boolean;\n /**\n * A conversation a person took over goes back to the agent after this long without them; null:\n * it stays theirs until they give it back.\n */\n auto_release_after_h: number | null;\n /** What it is told on this channel only, besides its own instructions. */\n instructions: string | null;\n /** The quiet period after the person's last message before the agent answers them all. */\n reply_delay_s: number;\n /** A long answer goes in up to three messages as a person writes them, its words as they are. */\n split: boolean;\n /** The channel shows the agent writing between the parts of an answer, where it can. */\n typing: boolean;\n /** WhatsApp, past 24 h since the person last wrote: the template that carries the answer. */\n window_template: string | null;\n /**\n * WhatsApp: the answer as a voice note in the agent's voice, its words kept as what it said;\n * never, when the person's last message was a voice note, or always.\n */\n voice_replies: \"never\" | \"when_voice\" | \"always\";\n}\n\n/**\n * An account on a provider (a WhatsApp number, an Instagram account, an SMS number) and the agent\n * that answers it (`GET /channels?agent=`).\n */\nexport interface Channel {\n id: string;\n agent_id: string;\n kind: \"whatsapp\" | \"instagram\" | \"sms\" | \"phone\" | \"web\";\n /** meta · twilio · livekit · signalcore (the widget) */\n provider: string;\n /** The number, the @username. */\n name: string;\n /** The provider's id for the account. */\n external_id: string;\n /** A phone number's: where its provider sends its calls (a SIP address); null for others. */\n address: string | null;\n /** A phone number's: whether the agent calls out from it (it has the number's SIP login). */\n outbound: boolean;\n status: ChannelStatus;\n /** The provider's word on its status, or what went wrong; null when it said nothing. */\n detail: string | null;\n settings: ChannelSettings;\n /** A web channel's: the widget's publishable key, its sites and its look; null for others. */\n web: ChannelWeb | null;\n created_at: string;\n}\n\n/** The widget a web channel is: the key its line carries, the sites it loads on, its look. */\nexport interface ChannelWeb {\n key: string;\n origins: string[];\n appearance: WidgetLook;\n}\n\nexport type StepState = \"running\" | \"waiting\" | \"done\" | \"failed\";\n\n/**\n * Who gives the yes an action waits for: the person on the screen (the turn stopped for it), or\n * the business (the turn went on; its people allow or decline it from the app, the inbox).\n */\nexport type Decides = \"person\" | \"business\";\n\n/** An action the platform will not run until it is allowed: what, with what input, by whom. */\nexport interface Request {\n action: string;\n input: Record<string, unknown>;\n /** The person's when left out (an API from before it). */\n decides?: Decides | undefined;\n}\n\n/** An action a reply called: what, how it went, and what it drew. */\nexport interface Step {\n id: string;\n action: string;\n state: StepState;\n output: string | null;\n surface: Surface | null;\n /**\n * The turn stopped on it: the person's answer on its surface carries it on. Never a host's tool\n * the page runs, nor a yes the business gives.\n */\n awaits: boolean;\n /** Set while it waits to be allowed: by the person (the turn stopped), or by the business. */\n request: Request | null;\n /** A connected app's action: the app, its logo an https address; absent for the platform's. */\n app?: StepApp | null | undefined;\n}\n\n/** The connected app a step's action belongs to: its name and logo. */\nexport interface StepApp {\n name: string;\n icon: string | null;\n}\n\n/**\n * What a person did on a surface: `surface` is its id, `name` the block's action, `data` what it\n * carries (a pick, a form's values), `label` the words the app shows for it.\n */\nexport interface Action {\n surface: string;\n name: string;\n data: Record<string, unknown>;\n label: string;\n}\n\n/** What is read of a message: who said it, its text, what it did on a surface, its steps. */\nexport interface Message {\n id: string;\n role: \"user\" | \"assistant\";\n text: string;\n /** When the message came from a surface; `text` is then its label. */\n action: Action | null;\n steps: Step[];\n /** The files the person sent with it; an API from before files leaves it out. */\n files?: Attachment[] | undefined;\n /** On a channel, who said it; null elsewhere. A person of the business's is `assistant`. */\n author?: Author | null | undefined;\n /** On a channel, where what goes out got to, and why it failed when it did. */\n delivery?: Delivery | null | undefined;\n delivery_error?: string | null | undefined;\n /** Gone out on a channel in several parts: the words of each, in order; null as one. */\n pieces?: string[] | null | undefined;\n /** Going out on a channel: how many of its parts went so far; null for what doesn't go out. */\n sent_parts?: number | null | undefined;\n /**\n * On a channel, a voice note, the person's or the agent's: its file, played with `text` (what\n * was said) under it; null otherwise, and left out by an API from before voice notes.\n */\n voice?: Attachment | null | undefined;\n /**\n * On a call, a person's message: what they heard of the agent's last words before saying it,\n * when that isn't what was stored (a reply or the greeting they cut short; \"\" for nothing of\n * it); null otherwise, and left out by an API from before calls.\n */\n heard?: string | null | undefined;\n /** On a channel, a person's message: what was decided of it; null otherwise. */\n decision?: Decision | null | undefined;\n created_at: string;\n}\n\n/**\n * A call with an agent from the browser (the owner's test, `POST /agents/{id}/voice/token`; anyone\n * who may talk to it, a person's as theirs, `POST /agents/{id}/calls`): LiveKit's address, a token\n * to join the call's room (for 10 minutes), and the call and its own conversation, made with it,\n * whose live socket shows what is said.\n */\nexport interface CallToken {\n url: string;\n token: string;\n room: string;\n call_id: string;\n conversation_id: string;\n}\n\n/** A file that arrived in a conversation, as a message carries it. */\nexport interface Attachment {\n id: string;\n name: string;\n media_type: string;\n size: number;\n}\n\n/** Where a file of a conversation plays from, until `expires_at`: a path under the API's root. */\nexport interface FileLink {\n path: string;\n expires_at: string;\n}\n\n/** A conversation's file, as the API stores it (`POST /conversations/{id}/files`). */\nexport interface ConversationFile extends Attachment {\n conversation_id: string;\n created_at: string;\n}\n\n/** A conversation with its messages, in the order they were said. */\nexport interface ConversationDetail extends Conversation {\n messages: Message[];\n /** Every change of who answers it, in order; left out by an API from before them. */\n handovers?: Handover[] | undefined;\n}\n\n/**\n * How far a searchable file's index is: its passages being made, made, or failed; what to know\n * of it (why it failed, what of it can't be searched); its pages without text the model read.\n */\nexport interface FileIndex {\n state: \"indexing\" | \"ready\" | \"failed\";\n detail: string | null;\n scanned: number;\n}\n\n/** A file kept under a name: the organization's, or an agent's own. */\nexport interface StoredFile {\n id: string;\n path: string;\n media_type: string;\n size: number;\n /** Text, Markdown, JSON, YAML or PDF, read by `search_files`; anything else is only kept. */\n searchable: boolean;\n /** Its index, while it is searchable; left out by an API from before it. */\n index?: FileIndex | null | undefined;\n created_at: string;\n}\n\n/**\n * Where a file is, for what reads any of them: the organization's or an agent's own by its path,\n * a conversation's by its id.\n */\nexport type FileAt =\n { path: string } | { agentId: string; path: string } | { conversationId: string; fileId: string };\n\n/** A PDF: the viewer draws the bytes it fetches. Null pages when they can't be counted. */\nexport interface PdfPreview {\n kind: \"pdf\";\n pages: number | null;\n}\n\n/**\n * One sheet: its first row with anything in it as its columns (null for an empty sheet), and the\n * rows under it, the first 500 (an empty row between two as `[]`), each cell as Excel shows it;\n * `total_rows`, every row under its columns.\n */\nexport interface PreviewSheet {\n name: string;\n columns: string[] | null;\n rows: string[][];\n total_rows: number;\n}\n\n/** A spreadsheet's sheets (20 at most), or a CSV as one, named as its file. */\nexport interface SheetPreview {\n kind: \"sheet\";\n sheets: PreviewSheet[];\n /** Rows, columns (50 at most), long cells or sheets were left out. */\n truncated: boolean;\n}\n\n/**\n * A Word file as simple HTML: headings, paragraphs, lists, tables, bold and italic, small\n * pictures as data URLs. Cleaned by the API against an allow-list: nothing in it runs, styles\n * or loads from another address.\n */\nexport interface DocumentPreview {\n kind: \"document\";\n html: string;\n truncated: boolean;\n}\n\n/** A slide: its title (null without one), the words of its other shapes, its notes. */\nexport interface PreviewSlide {\n title: string | null;\n text: string;\n notes: string | null;\n}\n\n/** A deck's slides, in order. */\nexport interface SlidesPreview {\n kind: \"slides\";\n slides: PreviewSlide[];\n truncated: boolean;\n}\n\n/** Words as written: Markdown, plain text, JSON. */\nexport interface TextPreview {\n kind: \"markdown\" | \"text\" | \"json\";\n text: string;\n truncated: boolean;\n}\n\n/** A source as written, never run (HTML too), in the language highlighters name it by. */\nexport interface CodePreview {\n kind: \"code\";\n text: string;\n truncated: boolean;\n language: string;\n}\n\n/** A picture, a video or a sound: the viewer plays the bytes, typed as `media_type`. */\nexport interface MediaPreview {\n kind: \"image\" | \"video\" | \"audio\";\n media_type: string;\n}\n\n/** Nothing to draw: a kind no viewer shows, or a file that can't be read; it is saved. */\nexport interface UnknownPreview {\n kind: \"unknown\";\n}\n\n/**\n * What a viewer draws of a file (`GET …/preview`), by its `kind`. What is too long answers what\n * fits, with `truncated`.\n */\nexport type FilePreview =\n | PdfPreview\n | SheetPreview\n | DocumentPreview\n | SlidesPreview\n | TextPreview\n | CodePreview\n | MediaPreview\n | UnknownPreview;\n\n/**\n * A file the agent shows in a surface (`show`'s `file` block): it names one it may read (`path`:\n * its own or the organization's by path, or the conversation's by id), and the API fills in\n * `file`, the conversation's file it put there (`{conversationId, fileId}` to preview and fetch\n * it); null while there is none (the reply's message before the show ran, or one it refused).\n */\nexport interface FileBlock {\n type: \"file\";\n path: string;\n /** Over it; none: its name. */\n title?: string;\n file: Attachment | null;\n}\n\n/** Who holds the conversation's browser: the agent drives it, or the person does. */\nexport type BrowserControl = \"agent\" | \"person\";\n\n/** Whose the browser is: a clean one in the cloud, or the owner's own Chrome. */\nexport type BrowserWhere = \"hosted\" | \"mine\";\n\n/**\n * The conversation's hosted browser, while it is open: where it is, who holds it, and, when the\n * agent handed it over, why (`reason`) and the step the person's answer carries on (`wait`).\n * `width` and `height` are the page's, the pixels its pointer events are in.\n */\nexport interface BrowserState {\n where: BrowserWhere;\n url: string;\n title: string;\n control: BrowserControl;\n reason: string | null;\n wait: string | null;\n width: number;\n height: number;\n}\n\n/** A picture of the browser's screen: a JPEG in base64, showing the page at this size. */\nexport interface BrowserFrame {\n jpeg: string;\n width: number;\n height: number;\n}\n\n/**\n * What the person does on the browser's screen while they hold it, in the page's pixels: the\n * pointer, the wheel, keys (the DOM's `key`, `code` and `keyCode`; `modifiers` is Alt 1, Ctrl 2,\n * Meta 4, Shift 8) and text pasted.\n */\nexport type BrowserInput =\n | {\n kind: \"mouse\";\n action: \"move\" | \"down\" | \"up\";\n x: number;\n y: number;\n button?: \"left\" | \"middle\" | \"right\";\n clicks?: number;\n }\n | { kind: \"wheel\"; x: number; y: number; dx: number; dy: number }\n | {\n kind: \"key\";\n action: \"down\" | \"up\";\n key: string;\n code: string;\n keyCode: number;\n modifiers: number;\n }\n | { kind: \"paste\"; text: string };\n\n/**\n * What a person may know of the agent they talk to (`GET /agents/{id}/face`): its name, its role\n * in a line, the mark of its work (an `AgentIcon`; null until it picked one) and whether it takes\n * calls.\n */\n/**\n * How a web channel's widget looks (`PUT /channels/{id}/appearance`): each unset field keeps the\n * widget's own default (the page's type and ink, neutral greys). Colours are `#rrggbb`, sizes\n * pixels; `css` is the widget's own stylesheet, drawn inside it after ours.\n */\nexport interface WidgetLook {\n accent?: string | null;\n accent_fg?: string | null;\n font?: WidgetFont | null;\n radius?: number | null;\n theme?: \"auto\" | \"light\" | \"dark\";\n position?: \"right\" | \"left\";\n launcher_size?: number | null;\n panel_width?: number | null;\n panel_height?: number | null;\n header?: boolean;\n css?: string | null;\n}\n\n/** The faces a widget may wear besides the page's own: Google Fonts it loads by name. */\nexport const WIDGET_FONTS = [\n \"Inter\",\n \"Manrope\",\n \"DM Sans\",\n \"Plus Jakarta Sans\",\n \"Poppins\",\n \"Nunito\",\n \"Lora\",\n \"Playfair Display\",\n] as const;\n\nexport type WidgetFont = (typeof WIDGET_FONTS)[number];\n\n/** What the widget reads as it loads, by its publishable key: its agent, and how it looks. */\nexport interface WidgetConfig {\n agent_id: string;\n agent_name: string;\n appearance: WidgetLook;\n}\n\nexport interface AgentFace {\n id: string;\n name: string;\n /** \"\" when it has none. */\n role: string;\n icon: string | null;\n voice: { enabled: boolean };\n}\n\n/**\n * One of the host's own tools the agent called, to run in the page (a tool declared `client` on\n * the agent): the call's id, the tool's name, and what the agent gave it, as its JSON Schema says.\n */\nexport interface ToolCall {\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n"],"mappings":";;AAsjBA,MAAa,eAAe;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/** The API's objects this package reads (v3/README.md). */\n\nimport type { ChannelKind } from \"./about\";\nimport type { Surface } from \"./blocks\";\n\n/** The channel a conversation is on, as a list draws it. */\nexport interface ChannelGlance {\n id: string;\n kind: ChannelKind;\n /** The number, the @username. */\n name: string;\n}\n\n/** The person a conversation on a channel is with, or a person who signed in to the host's app. */\nexport interface ContactGlance {\n id: string;\n /** As they show it on the channel; null when it doesn't say. */\n name: string | null;\n /** Where they are on it: the number, the Instagram id; their account in the host's app. */\n address: string;\n /** The owner marked them: the agent doesn't answer them; left out by an API from before. */\n muted?: boolean | undefined;\n}\n\n/**\n * Who answers a conversation: its agent; a person of the business (the agent paused in it: it\n * handed it over, or one of them wrote in it); or nobody (it is ignored: what comes is kept, never\n * answered).\n */\nexport type Holder = \"agent\" | \"person\" | \"nobody\";\n\n/** A change of who answers a conversation: to whom, why, who changed it and when. */\nexport interface Handover {\n holder: Holder;\n /** Why, in the words of whoever changed it; \"\" when they said nothing. */\n reason: string;\n /**\n * `owner` (a person of the business), `agent` (its `escalate`), `platform`, or an automation by\n * its file's name.\n */\n by: string;\n /** The automation's title, when one changed it. */\n title: string | null;\n at: string;\n}\n\n/**\n * What was decided of a person's message on a channel before the agent answered it: `deciding`\n * while the automations that decide go; then the agent answers it (`answer`), lets it pass this\n * time (`skip`), or the conversation goes to a person (`hold`) or to nobody (`ignore`).\n */\nexport interface Decision {\n kind: \"deciding\" | \"answer\" | \"skip\" | \"hold\" | \"ignore\";\n /** Why, in the owner's words; null when it says nothing. */\n reason: string | null;\n /** The automation that decided, by its file's name; null when the platform did (a filter, the hours). */\n automation: string | null;\n /** Its title. */\n title: string | null;\n /** Its run that decided. */\n run_id: string | null;\n}\n\n/** A conversation between a person and one agent. */\nexport interface Conversation {\n id: string;\n agent_id: string;\n /** A turn stopped, waiting for the person to answer on a surface. */\n pending: boolean;\n /** Who answers it now: its agent, a person of the business, or nobody. */\n handled_by: Holder;\n /** Its last change of who answers it; null when it never changed, left out by an older API. */\n handover?: Handover | null | undefined;\n /** The owner trying the agent out: no customer sees it, and it is never the agent's activity. */\n test: boolean;\n created_at: string;\n /** Its title in a list: what a person first said in it; null while they've said nothing. */\n title: string | null;\n /** On a channel: which, and the person; null (or left out by an older API) elsewhere. */\n channel?: ChannelGlance | null | undefined;\n contact?: ContactGlance | null | undefined;\n /** On a channel: when the agent's reply is due, once the person has been quiet a moment. */\n reply_due_at?: string | null | undefined;\n /** On a channel: when the person last wrote (WhatsApp's 24 h window counts from it). */\n last_inbound_at?: string | null | undefined;\n /** The call it is, when it is one; null elsewhere, and left out by an API from before calls. */\n call?: CallGlance | null | undefined;\n /**\n * Who it is with, when a person's token or a publishable key started it: a person of the\n * host's app (`host_person`, its `contact` then) or the widget's visitor.\n */\n person?: ConversationPerson | null | undefined;\n /**\n * For the person it is with: how many of the business's messages came since they last read it,\n * and whether it waits on them (a question, their yes); left out by an older API.\n */\n unread?: number | undefined;\n waiting_on_person?: boolean | undefined;\n}\n\n/** Who a person's token or a publishable key started a conversation for. */\nexport interface ConversationPerson {\n kind: \"host_person\" | \"visitor\" | \"contact\" | \"owner\";\n /** The host's id for them, the visitor's id. */\n external_id: string;\n name: string | null;\n email: string | null;\n}\n\n/**\n * Why a call ended, as the API keeps it: the caller hung up, the agent said goodbye, it reached\n * its longest time, it failed, its end never came, or it was never taken.\n */\nexport type CallReason = \"caller\" | \"agent\" | \"max_duration\" | \"error\" | \"lost\" | \"missed\";\n\n/**\n * The call a conversation is (v3/VOICE.md): the owner's in the browser or a phone call, when the\n * agent took it and when it ended, why and how long.\n */\nexport interface CallGlance {\n id: string;\n kind: \"browser\" | \"phone\";\n /** Null while it is on. */\n reason: CallReason | null;\n /** From when the agent took it to when it ended; null while it is on. */\n seconds: number | null;\n started_at: string | null;\n ended_at: string | null;\n /** Whether it was recorded, to play from the conversation; left out by an older API. */\n recording?: boolean | undefined;\n}\n\n/** Who said a message on a channel: the person, the agent, or a person of the business. */\nexport type Author = \"contact\" | \"agent\" | \"operator\";\n\n/** Where a message that goes out on a channel got to: it only moves forward, and `failed` wins. */\nexport type Delivery = \"queued\" | \"sent\" | \"delivered\" | \"read\" | \"failed\";\n\n/** How a channel's account stands, as its provider last said. */\nexport type ChannelStatus = \"pending\" | \"active\" | \"error\" | \"disconnected\";\n\n/** A day of the week, as a channel's hours name it. */\nexport type Weekday = \"mon\" | \"tue\" | \"wed\" | \"thu\" | \"fri\" | \"sat\" | \"sun\";\n\n/** A stretch of a day, from one time to a later one (\"HH:MM\"). */\nexport interface HoursSpan {\n start: string;\n end: string;\n}\n\n/**\n * A week of hours in the agent's time zone: each day's stretches, in order and apart, four at\n * most; a day left out, or empty, is closed.\n */\nexport type WeekHours = Partial<Record<Weekday, HoursSpan[]>>;\n\n/**\n * How the agent behaves on a channel, the same for every text channel (each shows only what its\n * medium can do): when it answers, how what it says goes out, and what it is told there.\n */\nexport interface ChannelSettings {\n /** It answers on this channel; off, everything goes to the inbox for a person. */\n answers: boolean;\n /** Who it answers, in the owner's words; null: everyone. */\n filter: string | null;\n /** Words that, said in a message, have it answer without weighing the filter. */\n filter_keywords: string[];\n /** The week it keeps; null: it answers at any hour. */\n hours: WeekHours | null;\n /** It answers inside the hours, or outside them (the business's people take the hours). */\n answers_when: \"in_hours\" | \"out_of_hours\";\n /** Sent once per conversation while the hours keep it from answering; null: nothing. */\n away_message: string | null;\n /** WhatsApp: it answers in group chats too. */\n groups: boolean;\n /**\n * A conversation a person took over goes back to the agent after this long without them; null:\n * it stays theirs until they give it back.\n */\n auto_release_after_h: number | null;\n /** What it is told on this channel only, besides its own instructions. */\n instructions: string | null;\n /** The quiet period after the person's last message before the agent answers them all. */\n reply_delay_s: number;\n /** A long answer goes in up to three messages as a person writes them, its words as they are. */\n split: boolean;\n /** The channel shows the agent writing between the parts of an answer, where it can. */\n typing: boolean;\n /** WhatsApp, past 24 h since the person last wrote: the template that carries the answer. */\n window_template: string | null;\n /**\n * WhatsApp: the answer as a voice note in the agent's voice, its words kept as what it said;\n * never, when the person's last message was a voice note, or always.\n */\n voice_replies: \"never\" | \"when_voice\" | \"always\";\n}\n\n/**\n * An account on a provider (a WhatsApp number, an Instagram account, an SMS number) and the agent\n * that answers it (`GET /channels?agent=`).\n */\nexport interface Channel {\n id: string;\n agent_id: string;\n kind: \"whatsapp\" | \"instagram\" | \"sms\" | \"phone\" | \"web\";\n /** meta · twilio · livekit · signalcore (the widget) */\n provider: string;\n /** The number, the @username. */\n name: string;\n /** The provider's id for the account. */\n external_id: string;\n /** A phone number's: where its provider sends its calls (a SIP address); null for others. */\n address: string | null;\n /** A phone number's: whether the agent calls out from it (it has the number's SIP login). */\n outbound: boolean;\n status: ChannelStatus;\n /** The provider's word on its status, or what went wrong; null when it said nothing. */\n detail: string | null;\n settings: ChannelSettings;\n /** A web channel's: the widget's publishable key, its sites and its look; null for others. */\n web: ChannelWeb | null;\n created_at: string;\n}\n\n/** The widget a web channel is: the key its line carries, the sites it loads on, its look. */\nexport interface ChannelWeb {\n key: string;\n origins: string[];\n appearance: WidgetLook;\n}\n\nexport type StepState = \"running\" | \"waiting\" | \"done\" | \"failed\";\n\n/**\n * Who gives the yes an action waits for: the person on the screen (the turn stopped for it), or\n * the business (the turn went on; its people allow or decline it from the app, the inbox).\n */\nexport type Decides = \"person\" | \"business\";\n\n/** An action the platform will not run until it is allowed: what, with what input, by whom. */\nexport interface Request {\n action: string;\n input: Record<string, unknown>;\n /** The person's when left out (an API from before it). */\n decides?: Decides | undefined;\n /** The JSON Schema of its input, which an edit given with the yes must fit. */\n schema?: Record<string, unknown> | null | undefined;\n}\n\n/** An action a reply called: what, how it went, and what it drew. */\nexport interface Step {\n id: string;\n action: string;\n state: StepState;\n output: string | null;\n surface: Surface | null;\n /**\n * The turn stopped on it: the person's answer on its surface carries it on. Never a host's tool\n * the page runs, nor a yes the business gives.\n */\n awaits: boolean;\n /** Set while it waits to be allowed: by the person (the turn stopped), or by the business. */\n request: Request | null;\n /** A connected app's action: the app, its logo an https address; absent for the platform's. */\n app?: StepApp | null | undefined;\n}\n\n/** The connected app a step's action belongs to: its name and logo. */\nexport interface StepApp {\n name: string;\n icon: string | null;\n}\n\n/**\n * What a person did on a surface: `surface` is its id, `name` the block's action, `data` what it\n * carries (a pick, a form's values), `label` the words the app shows for it.\n */\nexport interface Action {\n surface: string;\n name: string;\n data: Record<string, unknown>;\n label: string;\n}\n\n/** What is read of a message: who said it, its text, what it did on a surface, its steps. */\nexport interface Message {\n id: string;\n role: \"user\" | \"assistant\";\n text: string;\n /** When the message came from a surface; `text` is then its label. */\n action: Action | null;\n steps: Step[];\n /** The files the person sent with it; an API from before files leaves it out. */\n files?: Attachment[] | undefined;\n /** On a channel, who said it; null elsewhere. A person of the business's is `assistant`. */\n author?: Author | null | undefined;\n /** On a channel, where what goes out got to, and why it failed when it did. */\n delivery?: Delivery | null | undefined;\n delivery_error?: string | null | undefined;\n /** Gone out on a channel in several parts: the words of each, in order; null as one. */\n pieces?: string[] | null | undefined;\n /** Going out on a channel: how many of its parts went so far; null for what doesn't go out. */\n sent_parts?: number | null | undefined;\n /**\n * On a channel, a voice note, the person's or the agent's: its file, played with `text` (what\n * was said) under it; null otherwise, and left out by an API from before voice notes.\n */\n voice?: Attachment | null | undefined;\n /**\n * On a call, a person's message: what they heard of the agent's last words before saying it,\n * when that isn't what was stored (a reply or the greeting they cut short; \"\" for nothing of\n * it); null otherwise, and left out by an API from before calls.\n */\n heard?: string | null | undefined;\n /** On a channel, a person's message: what was decided of it; null otherwise. */\n decision?: Decision | null | undefined;\n /** The agent's words first, without a turn of the person's, and the run whose code sent them. */\n proactive?: boolean | undefined;\n run_id?: string | null | undefined;\n created_at: string;\n}\n\n/**\n * A call with an agent from the browser (the owner's test, `POST /agents/{id}/voice/token`; anyone\n * who may talk to it, a person's as theirs, `POST /agents/{id}/calls`): LiveKit's address, a token\n * to join the call's room (for 10 minutes), and the call and its own conversation, made with it,\n * whose live socket shows what is said.\n */\nexport interface CallToken {\n url: string;\n token: string;\n room: string;\n call_id: string;\n conversation_id: string;\n}\n\n/** A file that arrived in a conversation, as a message carries it. */\nexport interface Attachment {\n id: string;\n name: string;\n media_type: string;\n size: number;\n}\n\n/** Where a file of a conversation plays from, until `expires_at`: a path under the API's root. */\nexport interface FileLink {\n path: string;\n expires_at: string;\n}\n\n/** A conversation's file, as the API stores it (`POST /conversations/{id}/files`). */\nexport interface ConversationFile extends Attachment {\n conversation_id: string;\n created_at: string;\n}\n\n/** A conversation with its messages, in the order they were said. */\nexport interface ConversationDetail extends Conversation {\n messages: Message[];\n /** Every change of who answers it, in order; left out by an API from before them. */\n handovers?: Handover[] | undefined;\n}\n\n/**\n * How far a searchable file's index is: its passages being made, made, or failed; what to know\n * of it (why it failed, what of it can't be searched); its pages without text the model read.\n */\nexport interface FileIndex {\n state: \"indexing\" | \"ready\" | \"failed\";\n detail: string | null;\n scanned: number;\n}\n\n/** A file kept under a name: the organization's, or an agent's own. */\nexport interface StoredFile {\n id: string;\n path: string;\n media_type: string;\n size: number;\n /** Text, Markdown, JSON, YAML or PDF, read by `search_files`; anything else is only kept. */\n searchable: boolean;\n /** Its index, while it is searchable; left out by an API from before it. */\n index?: FileIndex | null | undefined;\n created_at: string;\n}\n\n/**\n * Where a file is, for what reads any of them: the organization's or an agent's own by its path,\n * a conversation's by its id.\n */\nexport type FileAt =\n { path: string } | { agentId: string; path: string } | { conversationId: string; fileId: string };\n\n/** A PDF: the viewer draws the bytes it fetches. Null pages when they can't be counted. */\nexport interface PdfPreview {\n kind: \"pdf\";\n pages: number | null;\n}\n\n/**\n * One sheet: its first row with anything in it as its columns (null for an empty sheet), and the\n * rows under it, the first 500 (an empty row between two as `[]`), each cell as Excel shows it;\n * `total_rows`, every row under its columns.\n */\nexport interface PreviewSheet {\n name: string;\n columns: string[] | null;\n rows: string[][];\n total_rows: number;\n}\n\n/** A spreadsheet's sheets (20 at most), or a CSV as one, named as its file. */\nexport interface SheetPreview {\n kind: \"sheet\";\n sheets: PreviewSheet[];\n /** Rows, columns (50 at most), long cells or sheets were left out. */\n truncated: boolean;\n}\n\n/**\n * A Word file as simple HTML: headings, paragraphs, lists, tables, bold and italic, small\n * pictures as data URLs. Cleaned by the API against an allow-list: nothing in it runs, styles\n * or loads from another address.\n */\nexport interface DocumentPreview {\n kind: \"document\";\n html: string;\n truncated: boolean;\n}\n\n/** A slide: its title (null without one), the words of its other shapes, its notes. */\nexport interface PreviewSlide {\n title: string | null;\n text: string;\n notes: string | null;\n}\n\n/** A deck's slides, in order. */\nexport interface SlidesPreview {\n kind: \"slides\";\n slides: PreviewSlide[];\n truncated: boolean;\n}\n\n/** Words as written: Markdown, plain text, JSON. */\nexport interface TextPreview {\n kind: \"markdown\" | \"text\" | \"json\";\n text: string;\n truncated: boolean;\n}\n\n/** A source as written, never run (HTML too), in the language highlighters name it by. */\nexport interface CodePreview {\n kind: \"code\";\n text: string;\n truncated: boolean;\n language: string;\n}\n\n/** A picture, a video or a sound: the viewer plays the bytes, typed as `media_type`. */\nexport interface MediaPreview {\n kind: \"image\" | \"video\" | \"audio\";\n media_type: string;\n}\n\n/** Nothing to draw: a kind no viewer shows, or a file that can't be read; it is saved. */\nexport interface UnknownPreview {\n kind: \"unknown\";\n}\n\n/**\n * What a viewer draws of a file (`GET …/preview`), by its `kind`. What is too long answers what\n * fits, with `truncated`.\n */\nexport type FilePreview =\n | PdfPreview\n | SheetPreview\n | DocumentPreview\n | SlidesPreview\n | TextPreview\n | CodePreview\n | MediaPreview\n | UnknownPreview;\n\n/**\n * A file the agent shows in a surface (`show`'s `file` block): it names one it may read (`path`:\n * its own or the organization's by path, or the conversation's by id), and the API fills in\n * `file`, the conversation's file it put there (`{conversationId, fileId}` to preview and fetch\n * it); null while there is none (the reply's message before the show ran, or one it refused).\n */\nexport interface FileBlock {\n type: \"file\";\n path: string;\n /** Over it; none: its name. */\n title?: string;\n file: Attachment | null;\n}\n\n/**\n * An agent's view rendered for one contact (`GET /views/{name}/render`): its blocks as the kit\n * draws them, filled from the rows when it was read, and `text`, the same in words.\n */\nexport interface ViewRendered {\n name: string;\n title: string;\n contact_id: string;\n blocks: Surface[\"blocks\"];\n text: string;\n rendered_at: string;\n}\n\n/** Who holds the conversation's browser: the agent drives it, or the person does. */\nexport type BrowserControl = \"agent\" | \"person\";\n\n/** Whose the browser is: a clean one in the cloud, or the owner's own Chrome. */\nexport type BrowserWhere = \"hosted\" | \"mine\";\n\n/**\n * The conversation's hosted browser, while it is open: where it is, who holds it, and, when the\n * agent handed it over, why (`reason`) and the step the person's answer carries on (`wait`).\n * `width` and `height` are the page's, the pixels its pointer events are in.\n */\nexport interface BrowserState {\n where: BrowserWhere;\n url: string;\n title: string;\n control: BrowserControl;\n reason: string | null;\n wait: string | null;\n width: number;\n height: number;\n}\n\n/** A picture of the browser's screen: a JPEG in base64, showing the page at this size. */\nexport interface BrowserFrame {\n jpeg: string;\n width: number;\n height: number;\n}\n\n/**\n * What the person does on the browser's screen while they hold it, in the page's pixels: the\n * pointer, the wheel, keys (the DOM's `key`, `code` and `keyCode`; `modifiers` is Alt 1, Ctrl 2,\n * Meta 4, Shift 8) and text pasted.\n */\nexport type BrowserInput =\n | {\n kind: \"mouse\";\n action: \"move\" | \"down\" | \"up\";\n x: number;\n y: number;\n button?: \"left\" | \"middle\" | \"right\";\n clicks?: number;\n }\n | { kind: \"wheel\"; x: number; y: number; dx: number; dy: number }\n | {\n kind: \"key\";\n action: \"down\" | \"up\";\n key: string;\n code: string;\n keyCode: number;\n modifiers: number;\n }\n | { kind: \"paste\"; text: string };\n\n/**\n * What a person may know of the agent they talk to (`GET /agents/{id}/face`): its name, its role\n * in a line, the mark of its work (an `AgentIcon`; null until it picked one) and whether it takes\n * calls.\n */\n/**\n * How a web channel's widget looks (`PUT /channels/{id}/appearance`): each unset field keeps the\n * widget's own default (the page's type and ink, neutral greys). Colours are `#rrggbb`, sizes\n * pixels; `css` is the widget's own stylesheet, drawn inside it after ours.\n */\nexport interface WidgetLook {\n accent?: string | null;\n accent_fg?: string | null;\n font?: WidgetFont | null;\n radius?: number | null;\n theme?: \"auto\" | \"light\" | \"dark\";\n position?: \"right\" | \"left\";\n launcher_size?: number | null;\n panel_width?: number | null;\n panel_height?: number | null;\n header?: boolean;\n css?: string | null;\n}\n\n/** The faces a widget may wear besides the page's own: Google Fonts it loads by name. */\nexport const WIDGET_FONTS = [\n \"Inter\",\n \"Manrope\",\n \"DM Sans\",\n \"Plus Jakarta Sans\",\n \"Poppins\",\n \"Nunito\",\n \"Lora\",\n \"Playfair Display\",\n] as const;\n\nexport type WidgetFont = (typeof WIDGET_FONTS)[number];\n\n/** What the widget reads as it loads, by its publishable key: its agent, and how it looks. */\nexport interface WidgetConfig {\n agent_id: string;\n agent_name: string;\n appearance: WidgetLook;\n}\n\nexport interface AgentFace {\n id: string;\n name: string;\n /** \"\" when it has none. */\n role: string;\n icon: string | null;\n voice: { enabled: boolean };\n}\n\n/**\n * One of the host's own tools the agent called, to run in the page (a tool declared `client` on\n * the agent): the call's id, the tool's name, and what the agent gave it, as its JSON Schema says.\n */\nexport interface ToolCall {\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n"],"mappings":";;AA8kBA,MAAa,eAAe;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
@@ -31,6 +31,8 @@ export interface LiveConversation {
|
|
|
31
31
|
say: (text: string, files?: readonly string[]) => boolean;
|
|
32
32
|
/** What the person did on a surface; false when the socket is closed. */
|
|
33
33
|
act: (action: Action) => boolean;
|
|
34
|
+
/** Several things done at once (a yes or no on each call a reply waits on), sent together. */
|
|
35
|
+
actAll: (actions: readonly Action[]) => boolean;
|
|
34
36
|
/** Stops the reply on its way; false when the socket is closed. */
|
|
35
37
|
stop: () => boolean;
|
|
36
38
|
/** The conversation's browser while it is open: where it is, who holds it. */
|
package/dist/useConversation.js
CHANGED
|
@@ -491,6 +491,21 @@ function useConversation(client, conversationId, { onError, onHolder, onStep, ha
|
|
|
491
491
|
});
|
|
492
492
|
return true;
|
|
493
493
|
};
|
|
494
|
+
const actAll = (actions) => {
|
|
495
|
+
const sent = actions.map((one) => ({
|
|
496
|
+
type: "action",
|
|
497
|
+
...one
|
|
498
|
+
}));
|
|
499
|
+
if (!live.send({
|
|
500
|
+
type: "actions",
|
|
501
|
+
actions: sent
|
|
502
|
+
})) return false;
|
|
503
|
+
for (const action of actions) dispatch({
|
|
504
|
+
type: "acted",
|
|
505
|
+
action
|
|
506
|
+
});
|
|
507
|
+
return true;
|
|
508
|
+
};
|
|
494
509
|
return {
|
|
495
510
|
messages: state.messages,
|
|
496
511
|
draft: state.draft,
|
|
@@ -501,6 +516,7 @@ function useConversation(client, conversationId, { onError, onHolder, onStep, ha
|
|
|
501
516
|
error: state.error,
|
|
502
517
|
say,
|
|
503
518
|
act,
|
|
519
|
+
actAll,
|
|
504
520
|
stop: () => live.send({ type: "stop" }),
|
|
505
521
|
browser: state.browser,
|
|
506
522
|
watchScreen: screen.watch,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConversation.js","names":[],"sources":["../src/useConversation.ts"],"sourcesContent":["import { useEffect, useReducer, useRef, useState } from \"react\";\n\nimport { createScreen, type Screen } from \"./browser\";\nimport type { Client } from \"./client\";\nimport { note } from \"./dev\";\nimport { sayOf, toolResultOf, type Frame, type ToolResult } from \"./frames\";\nimport type {\n Action,\n BrowserControl,\n BrowserFrame,\n BrowserInput,\n BrowserState,\n Handover,\n Holder,\n Message,\n Request,\n Step,\n ToolCall,\n} from \"./types\";\nimport { useLive } from \"./useLive\";\n\n/**\n * Where a conversation stands: its stored messages, the turn (whether a reply is on its way, what\n * of it has come so far, and what it waits on), and what its history read has said.\n */\nexport interface ConversationState {\n messages: Message[];\n pending: boolean;\n draft: string;\n /** The step the turn stopped on (a surface to answer, or an action to allow), until the person acts. */\n awaiting: string | null;\n /** Whether the history has landed once; until then an empty list means nothing. */\n loaded: boolean;\n /** What the last read threw; null once one lands. */\n error: unknown;\n /** The conversation's browser while it is open. */\n browser: BrowserState | null;\n /** Who answers it now; null until the history lands. */\n holder: Holder | null;\n /** Every change of who answers it, in order: read with the history, then heard live. */\n handovers: Handover[];\n /**\n * The host's tools the page runs now, by the call's id: the turn is under way while one runs\n * (its `done` said only that it stopped to wait for them), until its step is done or failed.\n */\n tools: string[];\n /**\n * The yes or no the person just gave, until the step's own frames say what came of it: what\n * the API refuses as the business's (`business`) is drawn as waiting for the business again.\n */\n answered: { id: string; request: Request } | null;\n}\n\nexport const EMPTY: ConversationState = {\n messages: [],\n pending: false,\n draft: \"\",\n awaiting: null,\n loaded: false,\n error: null,\n browser: null,\n holder: null,\n handovers: [],\n tools: [],\n answered: null,\n};\n\n/**\n * What moves the state: the socket opening afresh, a read landing (with who answers it, and its\n * changes, when the API tells them) or failing, the person acting on a surface, or a frame.\n */\nexport type ConversationAction =\n | { type: \"reset\" }\n | {\n type: \"history\";\n messages: Message[];\n pending: boolean;\n holder?: Holder;\n handovers?: Handover[];\n }\n | { type: \"failed\"; error: unknown }\n | { type: \"acted\"; action: Action }\n | Frame;\n\n/**\n * Errors told only to the socket that sent something (the agent still replying, a frame it did\n * not understand, a turn waiting on a surface, files it can't carry, what only the channel or the\n * business answers, a tool's answer no call waits on: nothing was stored): the turn, if there is\n * one, goes on. Any other error ends it.\n */\nconst ASIDE: ReadonlySet<string> = new Set([\n \"busy\",\n \"unknown\",\n \"pending\",\n \"files\",\n \"channel\",\n \"business\",\n \"tool\",\n]);\n\n/**\n * Told when the page answered a host's tool no call waits on any more (answered already, from\n * another tab or before a reload; or late): nothing for the person to hear.\n */\nconst STALE = \"tool\";\n\n/** Whether a step's state is its last: done, or failed. */\nconst settled = (step: Step | undefined): boolean =>\n step?.state === \"done\" || step?.state === \"failed\";\n\n/**\n * The turn, one step at a time, from what the socket says alone, so a page opened mid-turn (a\n * reload, another tab) reads it as the one that sent. The person's message opens it (their words,\n * or their answer on a surface); on a channel, where the reply comes on its own time, its first\n * words or step do. `done`, `stopped` or an error closes it, and either way the\n * draft goes: the reply now lives in the messages, and after an error there is none (on a\n * refusal the partial text was never one). A reply that calls actions is stored before they run,\n * so what `step`, `ui` and `request` frames say lands on its steps: the message is the one place\n * a step is drawn from, live or read back. The socket opening afresh empties the draft too, since\n * the turn so far is then told again. Who answers the conversation is the read's, then each\n * `holder` frame's: its change kept once beside those read, in order, and who answers taken from\n * it only when it is the newest; a `decision` lands on its message (null clears it). A host's\n * tool the page runs (`tool`) is its step working, and a turn under way until its step is done\n * or failed, whatever `done` said meanwhile. A yes the business gives (`decides: \"business\"`)\n * waits on its step without stopping the turn or holding the person; one the person gave and the\n * API refused as the business's (`business`) waits for the business again.\n */\nexport function reduce(state: ConversationState, action: ConversationAction): ConversationState {\n switch (action.type) {\n case \"reset\":\n // The socket that opens is told the browser again, if there is one, and the tools awaited.\n return {\n ...state,\n pending: false,\n draft: \"\",\n awaiting: null,\n browser: null,\n tools: [],\n answered: null,\n };\n case \"history\": {\n // A change heard while the read was on its way is newer than what it says.\n const read = action.handovers ?? [];\n const handovers = withHandovers(read, state.handovers);\n const heard = handovers.length > read.length ? handovers.at(-1)?.holder : undefined;\n return {\n ...state,\n messages: withMessages(action.messages, state.messages),\n awaiting: action.pending ? awaited(action.messages) : null,\n loaded: true,\n error: null,\n holder: heard ?? action.holder ?? state.holder,\n handovers,\n };\n }\n case \"failed\":\n return { ...state, error: action.error };\n case \"working\":\n // The agent began its reply: told first to a socket that opens while it thinks.\n return { ...state, pending: true };\n case \"text\":\n return { ...state, pending: true, draft: state.draft + action.delta };\n case \"message\": {\n const { message } = action;\n const messages = withStored(state.messages, message);\n // On a channel the turn is no one's message's: the person's words wait for the agent's\n // reply on its own time, and the business's own go out beside the turn, never in it.\n if (message.author === \"contact\" || message.author === \"operator\") {\n return { ...state, messages };\n }\n if (message.role === \"assistant\") return { ...state, messages, draft: \"\" };\n return {\n ...state,\n messages: message.action ? answered(messages, message.action) : messages,\n pending: true,\n draft: \"\",\n };\n }\n case \"ui\":\n return {\n ...state,\n messages: withStep(state.messages, action.id, (step) => ({\n ...step,\n surface: action.surface,\n awaits: action.awaits,\n })),\n awaiting: action.awaits ? action.id : state.awaiting,\n };\n case \"step\": {\n // A step that waits holds the turn until the person answers, as the history reads it; one\n // that runs says a turn is under way (on a channel, nobody's message told so before).\n const waits = action.state === \"waiting\";\n const ended = action.state === \"done\" || action.state === \"failed\";\n return {\n ...state,\n pending: state.pending || action.state === \"running\",\n messages: withStep(state.messages, action.id, (step) => ({\n ...step,\n state: action.state,\n output: action.output ?? step.output,\n awaits: waits || step.awaits,\n })),\n // One answered elsewhere (another screen, the owner's own Chrome) ends here too.\n awaiting: waits ? action.id : ended && state.awaiting === action.id ? null : state.awaiting,\n tools: ended ? state.tools.filter((id) => id !== action.id) : state.tools,\n answered: state.answered?.id === action.id ? null : state.answered,\n };\n }\n case \"tool\":\n // Told again once its answer is in (a socket opening as it went): nothing runs anew.\n if (settled(stepOf(state.messages, action.id))) return state;\n // The page runs it now: its step works like any step, until the step's own frames move it,\n // and it is never the person's to answer.\n return {\n ...state,\n pending: true,\n awaiting: state.awaiting === action.id ? null : state.awaiting,\n messages: withStep(state.messages, action.id, (step) => ({\n ...step,\n state: \"running\",\n awaits: false,\n })),\n tools: state.tools.includes(action.id) ? state.tools : [...state.tools, action.id],\n };\n case \"request\": {\n const { id, action: name, input, decides = \"person\" } = action;\n // The business's yes: the turn went on, and nothing waits on the person.\n const theirs = decides === \"person\";\n return {\n ...state,\n awaiting: theirs ? id : state.awaiting,\n messages: withStep(state.messages, id, (step) => ({\n ...step,\n state: \"waiting\",\n awaits: theirs,\n request: { action: name, input, decides },\n })),\n };\n }\n case \"acted\": {\n const { surface } = action.action;\n const request = stepOf(state.messages, surface)?.request ?? null;\n return {\n ...state,\n awaiting: surface === state.awaiting ? null : state.awaiting,\n // Leave given or refused: the card goes at once; the step's own frames say what came of it.\n messages: withStep(state.messages, surface, (step) => ({ ...step, request: null })),\n answered: request === null ? state.answered : { id: surface, request },\n };\n }\n case \"handoff\":\n // Nothing to keep: the reply's `escalate` step says it, live (its `done` follows this\n // frame) and read back alike; who answers now comes in its `holder` frame.\n return state;\n case \"holder\": {\n const handovers = withHandovers(state.handovers, [action.handover]);\n // One older than a change known says nothing of who answers now.\n const newest = handovers.at(-1) === action.handover;\n return { ...state, holder: newest ? action.handled_by : state.holder, handovers };\n }\n case \"decision\":\n return {\n ...state,\n messages: withMessage(state.messages, action.message_id, (message) => ({\n ...message,\n decision: action.decision,\n })),\n };\n case \"done\":\n // The turn ended, or stopped to wait: on the host's tools the page still runs, it goes on.\n return { ...state, pending: state.tools.length > 0, draft: \"\" };\n case \"stopped\":\n return { ...state, pending: false, draft: \"\", tools: [] };\n case \"error\":\n if (action.code === \"business\" && state.answered !== null) {\n // The person's yes or no on what the business decides: it waits for the business again.\n const { id, request } = state.answered;\n return {\n ...state,\n answered: null,\n messages: withStep(state.messages, id, (step) => ({\n ...step,\n awaits: false,\n request: { ...request, decides: \"business\" },\n })),\n };\n }\n return ASIDE.has(action.code)\n ? state\n : { ...state, pending: false, draft: \"\", awaiting: null, tools: [], answered: null };\n case \"browser\":\n if (!action.open) return { ...state, browser: null };\n return {\n ...state,\n browser: {\n url: action.url,\n title: action.title,\n where: action.where,\n control: action.control,\n reason: action.reason,\n wait: action.wait,\n width: action.width,\n height: action.height,\n },\n };\n case \"browser.frame\":\n // Pictures repaint the screen, never the conversation (`Screen`).\n return state;\n }\n}\n\n/**\n * `known` with whatever of `more` it lacks, at the end, by id: a message is kept once however\n * many times it is heard (stored live, then read in the history, or told again on a reconnect).\n */\nfunction withMessages(known: Message[], more: Message[]): Message[] {\n const ids = new Set(known.map((message) => message.id));\n const missing = more.filter((message) => !ids.has(message.id));\n return missing.length === 0 ? known : [...known, ...missing];\n}\n\n/** What going out on a channel changes of a message once it is stored, as it goes. */\nconst OUTGOING = [\"delivery\", \"delivery_error\", \"pieces\", \"sent_parts\", \"voice\"] as const;\n\n/**\n * `known` with a message stored: one it lacks joins at the end; one it holds takes what going out\n * on its channel changed since (where it got to, its parts, its voice note) and keeps the rest as\n * this page has it (its steps, moved by their own frames); untouched when nothing changed.\n */\nfunction withStored(known: Message[], stored: Message): Message[] {\n const held = known.find((message) => message.id === stored.id);\n if (held === undefined) return [...known, stored];\n if (OUTGOING.every((field) => isEqual(held[field], stored[field]))) return known;\n const outgoing = Object.fromEntries(OUTGOING.map((field) => [field, stored[field]]));\n return known.map((message) => (message === held ? { ...held, ...outgoing } : message));\n}\n\n/** The same value, told again: equal by what it holds. */\nconst isEqual = (one: unknown, other: unknown): boolean =>\n JSON.stringify(one ?? null) === JSON.stringify(other ?? null);\n\n/** A change of who answers, as one however it was told: its moment, and to whom. */\nconst handoverKey = ({ at, holder }: Handover): string => `${Date.parse(at)} ${holder}`;\n\n/**\n * `known` with whatever of `more` it lacks, in the order they were made: a change is kept once\n * however often, and by whatever, it is told (the read, a frame, the host's own request).\n */\nfunction withHandovers(known: Handover[], more: Handover[]): Handover[] {\n const keys = new Set(known.map(handoverKey));\n const missing = more.filter((handover) => !keys.has(handoverKey(handover)));\n if (missing.length === 0) return known;\n return [...known, ...missing].sort((one, other) => Date.parse(one.at) - Date.parse(other.at));\n}\n\n/** The messages with message `id` changed; untouched when none is it. */\nfunction withMessage(\n messages: Message[],\n id: string,\n change: (message: Message) => Message,\n): Message[] {\n const at = messages.findIndex((message) => message.id === id);\n const message = messages[at];\n return message ? messages.with(at, change(message)) : messages;\n}\n\n/** Step `id`, wherever it is; none when no message holds it. */\nfunction stepOf(messages: Message[], id: string): Step | undefined {\n return messages.flatMap((message) => message.steps).findLast((step) => step.id === id);\n}\n\n/** The messages with step `id` changed, wherever it is; untouched when no message holds it. */\nfunction withStep(messages: Message[], id: string, change: (step: Step) => Step): Message[] {\n const at = messages.findLastIndex((message) => message.steps.some((step) => step.id === id));\n const message = messages[at];\n if (!message) return messages;\n const steps = message.steps.map((step) => (step.id === id ? change(step) : step));\n return messages.with(at, { ...message, steps });\n}\n\n/** The surface a stopped turn waits on, as the history tells it. */\nfunction awaited(messages: Message[]): string | null {\n return messages.flatMap((message) => message.steps).find((step) => step.awaits)?.id ?? null;\n}\n\n/**\n * The person's answer, landed on the step that waited for it: a question is done with their\n * words as its output (what the history reads back); an action they allowed or declined was\n * already told its own state.\n */\nfunction answered(messages: Message[], action: Action): Message[] {\n return withStep(messages, action.surface, (step) =>\n step.state === \"waiting\"\n ? { ...step, state: \"done\", output: action.label, awaits: false, request: null }\n : { ...step, awaits: false, request: null },\n );\n}\n\n/** How often the history is read again while something goes out on a channel. */\nconst DELIVERY_EVERY = 3_000;\n/** How long a message sent is watched for its receipt before the next read brings it. */\nconst RECEIPT_WITHIN = 60_000;\n\n/**\n * Whether a message is still on its way to the person on a channel: waiting to go, or gone a\n * moment ago and not yet received. What it gets to moves without a frame, so it is read again.\n */\nexport function onItsWay(message: Message, now: number): boolean {\n if (message.delivery === \"queued\") return true;\n return message.delivery === \"sent\" && now - Date.parse(message.created_at) < RECEIPT_WITHIN;\n}\n\n/**\n * Runs one of the host's own tools in the page, for the agent: what it resolves to goes back as\n * the tool's output, what it rejects with as why it failed (an error's message).\n */\nexport type ToolHandler = (call: ToolCall) => Promise<unknown>;\n\n/** What the page answers a tool of the host's when it runs none (no `onTool`), for the agent. */\nexport const NO_TOOL = \"This page can't do that\";\n\nconst refuse = (): Promise<never> => Promise.reject(new Error(NO_TOOL));\n\n/** A conversation, live: its messages, where its turn stands, and the ways to speak. */\nexport interface LiveConversation {\n messages: Message[];\n draft: string;\n pending: boolean;\n /**\n * The step the turn waits on: a surface to answer, or an action the person allows (its\n * `request`, `decides: \"person\"`; `act` on its id with `approve` or `decline`). Nothing else\n * goes until the person acts. A yes the business gives holds nothing.\n */\n awaiting: string | null;\n open: boolean;\n /** Until the history has landed once. */\n loading: boolean;\n /** What the last history read threw, if it failed. */\n error: unknown;\n /**\n * Says something: words, the ids of files uploaded to the conversation for it, or both; false\n * when it cannot go now: nothing to say, a turn running or waiting, the socket closed.\n */\n say: (text: string, files?: readonly string[]) => boolean;\n /** What the person did on a surface; false when the socket is closed. */\n act: (action: Action) => boolean;\n /** Stops the reply on its way; false when the socket is closed. */\n stop: () => boolean;\n /** The conversation's browser while it is open: where it is, who holds it. */\n browser: BrowserState | null;\n /** Hears each picture of the browser's screen, the latest at once; returns how to stop. */\n watchScreen: (listener: (frame: BrowserFrame) => void) => () => void;\n /** Takes the browser from the agent, or gives it back; false when the socket is closed. */\n control: (to: BrowserControl) => boolean;\n /** Closes the browser now (it stops being billed); false when the socket is closed. */\n closeBrowser: () => boolean;\n /** What the person does on the browser's screen while they hold it. */\n input: (event: BrowserInput) => boolean;\n /** Who answers it now: its agent, a person of the business, or nobody; null until the history lands. */\n holder: Holder | null;\n /** Every change of who answers it, in order, kept live. */\n handovers: Handover[];\n}\n\n/**\n * Conversation `conversationId` as the person sees it: the history, kept live by the socket.\n * Each time the socket opens the history is read, with the socket already listening, so nothing\n * stored can fall between the two; what came live meanwhile is kept. `onError` hears each\n * error's code (the socket's, or `load` for a history that could not be read): the host words\n * it, this hook shows no text; but `tool`, the page's answer to a call no longer waiting (one it\n * answered already, told again), which is let pass (noted in development). `onHolder` hears each\n * change of who answers it, live, for the host's own views of it; `handover` is the last change\n * the host knows of (one it made itself, as its request answered), taken at once, so it shows\n * whether or not the socket tells it.\n * `onStep` hears each step the agent starts, live, by its action's name, for the host's own\n * views of it (the builder's column, opened as the agent asks it).\n * `onTool` runs the host's own tools the agent calls in the page (a `tool` frame), and its answer\n * goes up (`tool_result`); each call runs once, and one told again while it waits (the socket\n * opened anew) is answered again with what it gave. Without `onTool`, each is refused with\n * `NO_TOOL`, so the turn never waits on a page that can't run it. One conversation per mount: key\n * the component on the id.\n */\nexport function useConversation(\n client: Client,\n conversationId: string,\n {\n onError,\n onHolder,\n onStep,\n handover,\n onTool,\n }: {\n onError?: ((code: string) => void) | undefined;\n onHolder?: ((holder: Holder, handover: Handover) => void) | undefined;\n onStep?: ((action: string) => void) | undefined;\n handover?: Handover | null | undefined;\n onTool?: ToolHandler | undefined;\n } = {},\n): LiveConversation {\n const [state, dispatch] = useReducer(reduce, EMPTY);\n // The host's change, taken once by what it is: the same one told again changes nothing.\n const told = handover ?? null;\n const toldKey = told === null ? null : handoverKey(told);\n useEffect(() => {\n if (told !== null) dispatch({ type: \"holder\", handled_by: told.holder, handover: told });\n }, [toldKey]);\n // The browser's pictures, out of the state: each repaints its screen alone.\n const [screen] = useState<Screen>(createScreen);\n // Reads are numbered: one that lands after a newer one began, or after the conversation went,\n // is dropped on the floor.\n const reads = useRef(0);\n useEffect(\n () => () => {\n reads.current += 1;\n },\n [client, conversationId],\n );\n\n const read = () => {\n const run = ++reads.current;\n client.conversations.get(conversationId).then(\n ({ messages, pending, handled_by, handovers = [] }) => {\n if (run !== reads.current) return;\n dispatch({ type: \"history\", messages, pending, holder: handled_by, handovers });\n },\n (error: unknown) => {\n if (run !== reads.current) return;\n dispatch({ type: \"failed\", error });\n onError?.(\"load\");\n },\n );\n };\n\n // The host's tools the agent called, by the call's id: running (null), or what each answered.\n const tools = useRef(new Map<string, ToolResult | null>());\n const runTool = ({ id, name, input }: ToolCall) => {\n const known = tools.current.get(id);\n if (known !== undefined) {\n if (known !== null) live.send(known);\n return;\n }\n tools.current.set(id, null);\n void Promise.resolve({ id, name, input })\n .then(onTool ?? refuse)\n .then(\n (value): PromiseSettledResult<unknown> => ({ status: \"fulfilled\", value }),\n (reason: unknown): PromiseSettledResult<unknown> => ({ status: \"rejected\", reason }),\n )\n .then((settled) => {\n const answer = toolResultOf(id, settled);\n tools.current.set(id, answer);\n // A socket closed now hears it when it opens: the call is told again while it waits.\n live.send(answer);\n });\n };\n\n const live = useLive(client, conversationId, {\n onFrame: (frame: Frame) => {\n if (frame.type === \"browser.frame\") {\n screen.show({ jpeg: frame.jpeg, width: frame.width, height: frame.height });\n return;\n }\n if (frame.type === \"error\" && frame.code === STALE) {\n // An answer of the page's that no call waits on any more: nothing for the person.\n note(\"A host's tool answer was let pass:\", frame.detail);\n return;\n }\n if (frame.type === \"browser\" && !frame.open) screen.clear();\n dispatch(frame);\n if (frame.type === \"error\") onError?.(frame.code);\n if (frame.type === \"holder\") onHolder?.(frame.handled_by, frame.handover);\n if (frame.type === \"step\" && frame.state === \"running\" && frame.action !== undefined) {\n onStep?.(frame.action);\n }\n if (frame.type === \"tool\") runTool(frame);\n },\n onOpen: () => {\n dispatch({ type: \"reset\" });\n read();\n },\n });\n\n // On a channel, while something of the business's is on its way to the person, the history is\n // read again every few seconds: where it got to (sent, delivered, read, failed) comes no other\n // way. Each read is a new state, so the question is asked again after it.\n const travelling = state.messages.some((message) => onItsWay(message, Date.now()));\n useEffect(() => {\n if (!travelling || !live.open) return undefined;\n const again = setInterval(read, DELIVERY_EVERY);\n return () => {\n clearInterval(again);\n };\n // `read` does the same work from any render: what it reads is in refs and the dispatch.\n }, [travelling, live.open]);\n\n const say = (text: string, files: readonly string[] = []): boolean => {\n const frame = sayOf(text, files);\n if (frame === null || state.pending || state.awaiting !== null) return false;\n return live.send(frame);\n };\n\n const act = (action: Action): boolean => {\n if (!live.send({ type: \"action\", ...action })) return false;\n dispatch({ type: \"acted\", action });\n return true;\n };\n\n return {\n messages: state.messages,\n draft: state.draft,\n pending: state.pending,\n awaiting: state.awaiting,\n open: live.open,\n loading: !state.loaded,\n error: state.error,\n say,\n act,\n stop: () => live.send({ type: \"stop\" }),\n browser: state.browser,\n watchScreen: screen.watch,\n control: (to) => live.send({ type: \"browser.control\", to }),\n closeBrowser: () => live.send({ type: \"browser.close\" }),\n input: (event) => live.send({ type: \"browser.input\", event }),\n holder: state.holder,\n handovers: state.handovers,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useConversation.js","names":[],"sources":["../src/useConversation.ts"],"sourcesContent":["import { useEffect, useReducer, useRef, useState } from \"react\";\n\nimport { createScreen, type Screen } from \"./browser\";\nimport type { Client } from \"./client\";\nimport { note } from \"./dev\";\nimport { sayOf, toolResultOf, type Frame, type ToolResult } from \"./frames\";\nimport type {\n Action,\n BrowserControl,\n BrowserFrame,\n BrowserInput,\n BrowserState,\n Handover,\n Holder,\n Message,\n Request,\n Step,\n ToolCall,\n} from \"./types\";\nimport { useLive } from \"./useLive\";\n\n/**\n * Where a conversation stands: its stored messages, the turn (whether a reply is on its way, what\n * of it has come so far, and what it waits on), and what its history read has said.\n */\nexport interface ConversationState {\n messages: Message[];\n pending: boolean;\n draft: string;\n /** The step the turn stopped on (a surface to answer, or an action to allow), until the person acts. */\n awaiting: string | null;\n /** Whether the history has landed once; until then an empty list means nothing. */\n loaded: boolean;\n /** What the last read threw; null once one lands. */\n error: unknown;\n /** The conversation's browser while it is open. */\n browser: BrowserState | null;\n /** Who answers it now; null until the history lands. */\n holder: Holder | null;\n /** Every change of who answers it, in order: read with the history, then heard live. */\n handovers: Handover[];\n /**\n * The host's tools the page runs now, by the call's id: the turn is under way while one runs\n * (its `done` said only that it stopped to wait for them), until its step is done or failed.\n */\n tools: string[];\n /**\n * The yes or no the person just gave, until the step's own frames say what came of it: what\n * the API refuses as the business's (`business`) is drawn as waiting for the business again.\n */\n answered: { id: string; request: Request } | null;\n}\n\nexport const EMPTY: ConversationState = {\n messages: [],\n pending: false,\n draft: \"\",\n awaiting: null,\n loaded: false,\n error: null,\n browser: null,\n holder: null,\n handovers: [],\n tools: [],\n answered: null,\n};\n\n/**\n * What moves the state: the socket opening afresh, a read landing (with who answers it, and its\n * changes, when the API tells them) or failing, the person acting on a surface, or a frame.\n */\nexport type ConversationAction =\n | { type: \"reset\" }\n | {\n type: \"history\";\n messages: Message[];\n pending: boolean;\n holder?: Holder;\n handovers?: Handover[];\n }\n | { type: \"failed\"; error: unknown }\n | { type: \"acted\"; action: Action }\n | Frame;\n\n/**\n * Errors told only to the socket that sent something (the agent still replying, a frame it did\n * not understand, a turn waiting on a surface, files it can't carry, what only the channel or the\n * business answers, a tool's answer no call waits on: nothing was stored): the turn, if there is\n * one, goes on. Any other error ends it.\n */\nconst ASIDE: ReadonlySet<string> = new Set([\n \"busy\",\n \"unknown\",\n \"pending\",\n \"files\",\n \"channel\",\n \"business\",\n \"tool\",\n]);\n\n/**\n * Told when the page answered a host's tool no call waits on any more (answered already, from\n * another tab or before a reload; or late): nothing for the person to hear.\n */\nconst STALE = \"tool\";\n\n/** Whether a step's state is its last: done, or failed. */\nconst settled = (step: Step | undefined): boolean =>\n step?.state === \"done\" || step?.state === \"failed\";\n\n/**\n * The turn, one step at a time, from what the socket says alone, so a page opened mid-turn (a\n * reload, another tab) reads it as the one that sent. The person's message opens it (their words,\n * or their answer on a surface); on a channel, where the reply comes on its own time, its first\n * words or step do. `done`, `stopped` or an error closes it, and either way the\n * draft goes: the reply now lives in the messages, and after an error there is none (on a\n * refusal the partial text was never one). A reply that calls actions is stored before they run,\n * so what `step`, `ui` and `request` frames say lands on its steps: the message is the one place\n * a step is drawn from, live or read back. The socket opening afresh empties the draft too, since\n * the turn so far is then told again. Who answers the conversation is the read's, then each\n * `holder` frame's: its change kept once beside those read, in order, and who answers taken from\n * it only when it is the newest; a `decision` lands on its message (null clears it). A host's\n * tool the page runs (`tool`) is its step working, and a turn under way until its step is done\n * or failed, whatever `done` said meanwhile. A yes the business gives (`decides: \"business\"`)\n * waits on its step without stopping the turn or holding the person; one the person gave and the\n * API refused as the business's (`business`) waits for the business again.\n */\nexport function reduce(state: ConversationState, action: ConversationAction): ConversationState {\n switch (action.type) {\n case \"reset\":\n // The socket that opens is told the browser again, if there is one, and the tools awaited.\n return {\n ...state,\n pending: false,\n draft: \"\",\n awaiting: null,\n browser: null,\n tools: [],\n answered: null,\n };\n case \"history\": {\n // A change heard while the read was on its way is newer than what it says.\n const read = action.handovers ?? [];\n const handovers = withHandovers(read, state.handovers);\n const heard = handovers.length > read.length ? handovers.at(-1)?.holder : undefined;\n return {\n ...state,\n messages: withMessages(action.messages, state.messages),\n awaiting: action.pending ? awaited(action.messages) : null,\n loaded: true,\n error: null,\n holder: heard ?? action.holder ?? state.holder,\n handovers,\n };\n }\n case \"failed\":\n return { ...state, error: action.error };\n case \"working\":\n // The agent began its reply: told first to a socket that opens while it thinks.\n return { ...state, pending: true };\n case \"text\":\n return { ...state, pending: true, draft: state.draft + action.delta };\n case \"message\": {\n const { message } = action;\n const messages = withStored(state.messages, message);\n // On a channel the turn is no one's message's: the person's words wait for the agent's\n // reply on its own time, and the business's own go out beside the turn, never in it.\n if (message.author === \"contact\" || message.author === \"operator\") {\n return { ...state, messages };\n }\n if (message.role === \"assistant\") return { ...state, messages, draft: \"\" };\n return {\n ...state,\n messages: message.action ? answered(messages, message.action) : messages,\n pending: true,\n draft: \"\",\n };\n }\n case \"ui\":\n return {\n ...state,\n messages: withStep(state.messages, action.id, (step) => ({\n ...step,\n surface: action.surface,\n awaits: action.awaits,\n })),\n awaiting: action.awaits ? action.id : state.awaiting,\n };\n case \"step\": {\n // A step that waits holds the turn until the person answers, as the history reads it; one\n // that runs says a turn is under way (on a channel, nobody's message told so before).\n const waits = action.state === \"waiting\";\n const ended = action.state === \"done\" || action.state === \"failed\";\n return {\n ...state,\n pending: state.pending || action.state === \"running\",\n messages: withStep(state.messages, action.id, (step) => ({\n ...step,\n state: action.state,\n output: action.output ?? step.output,\n awaits: waits || step.awaits,\n })),\n // One answered elsewhere (another screen, the owner's own Chrome) ends here too.\n awaiting: waits ? action.id : ended && state.awaiting === action.id ? null : state.awaiting,\n tools: ended ? state.tools.filter((id) => id !== action.id) : state.tools,\n answered: state.answered?.id === action.id ? null : state.answered,\n };\n }\n case \"tool\":\n // Told again once its answer is in (a socket opening as it went): nothing runs anew.\n if (settled(stepOf(state.messages, action.id))) return state;\n // The page runs it now: its step works like any step, until the step's own frames move it,\n // and it is never the person's to answer.\n return {\n ...state,\n pending: true,\n awaiting: state.awaiting === action.id ? null : state.awaiting,\n messages: withStep(state.messages, action.id, (step) => ({\n ...step,\n state: \"running\",\n awaits: false,\n })),\n tools: state.tools.includes(action.id) ? state.tools : [...state.tools, action.id],\n };\n case \"request\": {\n const { id, action: name, input, decides = \"person\" } = action;\n // The business's yes: the turn went on, and nothing waits on the person.\n const theirs = decides === \"person\";\n return {\n ...state,\n awaiting: theirs ? id : state.awaiting,\n messages: withStep(state.messages, id, (step) => ({\n ...step,\n state: \"waiting\",\n awaits: theirs,\n request: { action: name, input, decides },\n })),\n };\n }\n case \"acted\": {\n const { surface } = action.action;\n const request = stepOf(state.messages, surface)?.request ?? null;\n return {\n ...state,\n awaiting: surface === state.awaiting ? null : state.awaiting,\n // Leave given or refused: the card goes at once; the step's own frames say what came of it.\n messages: withStep(state.messages, surface, (step) => ({ ...step, request: null })),\n answered: request === null ? state.answered : { id: surface, request },\n };\n }\n case \"handoff\":\n // Nothing to keep: the reply's `escalate` step says it, live (its `done` follows this\n // frame) and read back alike; who answers now comes in its `holder` frame.\n return state;\n case \"holder\": {\n const handovers = withHandovers(state.handovers, [action.handover]);\n // One older than a change known says nothing of who answers now.\n const newest = handovers.at(-1) === action.handover;\n return { ...state, holder: newest ? action.handled_by : state.holder, handovers };\n }\n case \"decision\":\n return {\n ...state,\n messages: withMessage(state.messages, action.message_id, (message) => ({\n ...message,\n decision: action.decision,\n })),\n };\n case \"done\":\n // The turn ended, or stopped to wait: on the host's tools the page still runs, it goes on.\n return { ...state, pending: state.tools.length > 0, draft: \"\" };\n case \"stopped\":\n return { ...state, pending: false, draft: \"\", tools: [] };\n case \"error\":\n if (action.code === \"business\" && state.answered !== null) {\n // The person's yes or no on what the business decides: it waits for the business again.\n const { id, request } = state.answered;\n return {\n ...state,\n answered: null,\n messages: withStep(state.messages, id, (step) => ({\n ...step,\n awaits: false,\n request: { ...request, decides: \"business\" },\n })),\n };\n }\n return ASIDE.has(action.code)\n ? state\n : { ...state, pending: false, draft: \"\", awaiting: null, tools: [], answered: null };\n case \"browser\":\n if (!action.open) return { ...state, browser: null };\n return {\n ...state,\n browser: {\n url: action.url,\n title: action.title,\n where: action.where,\n control: action.control,\n reason: action.reason,\n wait: action.wait,\n width: action.width,\n height: action.height,\n },\n };\n case \"browser.frame\":\n // Pictures repaint the screen, never the conversation (`Screen`).\n return state;\n }\n}\n\n/**\n * `known` with whatever of `more` it lacks, at the end, by id: a message is kept once however\n * many times it is heard (stored live, then read in the history, or told again on a reconnect).\n */\nfunction withMessages(known: Message[], more: Message[]): Message[] {\n const ids = new Set(known.map((message) => message.id));\n const missing = more.filter((message) => !ids.has(message.id));\n return missing.length === 0 ? known : [...known, ...missing];\n}\n\n/** What going out on a channel changes of a message once it is stored, as it goes. */\nconst OUTGOING = [\"delivery\", \"delivery_error\", \"pieces\", \"sent_parts\", \"voice\"] as const;\n\n/**\n * `known` with a message stored: one it lacks joins at the end; one it holds takes what going out\n * on its channel changed since (where it got to, its parts, its voice note) and keeps the rest as\n * this page has it (its steps, moved by their own frames); untouched when nothing changed.\n */\nfunction withStored(known: Message[], stored: Message): Message[] {\n const held = known.find((message) => message.id === stored.id);\n if (held === undefined) return [...known, stored];\n if (OUTGOING.every((field) => isEqual(held[field], stored[field]))) return known;\n const outgoing = Object.fromEntries(OUTGOING.map((field) => [field, stored[field]]));\n return known.map((message) => (message === held ? { ...held, ...outgoing } : message));\n}\n\n/** The same value, told again: equal by what it holds. */\nconst isEqual = (one: unknown, other: unknown): boolean =>\n JSON.stringify(one ?? null) === JSON.stringify(other ?? null);\n\n/** A change of who answers, as one however it was told: its moment, and to whom. */\nconst handoverKey = ({ at, holder }: Handover): string => `${Date.parse(at)} ${holder}`;\n\n/**\n * `known` with whatever of `more` it lacks, in the order they were made: a change is kept once\n * however often, and by whatever, it is told (the read, a frame, the host's own request).\n */\nfunction withHandovers(known: Handover[], more: Handover[]): Handover[] {\n const keys = new Set(known.map(handoverKey));\n const missing = more.filter((handover) => !keys.has(handoverKey(handover)));\n if (missing.length === 0) return known;\n return [...known, ...missing].sort((one, other) => Date.parse(one.at) - Date.parse(other.at));\n}\n\n/** The messages with message `id` changed; untouched when none is it. */\nfunction withMessage(\n messages: Message[],\n id: string,\n change: (message: Message) => Message,\n): Message[] {\n const at = messages.findIndex((message) => message.id === id);\n const message = messages[at];\n return message ? messages.with(at, change(message)) : messages;\n}\n\n/** Step `id`, wherever it is; none when no message holds it. */\nfunction stepOf(messages: Message[], id: string): Step | undefined {\n return messages.flatMap((message) => message.steps).findLast((step) => step.id === id);\n}\n\n/** The messages with step `id` changed, wherever it is; untouched when no message holds it. */\nfunction withStep(messages: Message[], id: string, change: (step: Step) => Step): Message[] {\n const at = messages.findLastIndex((message) => message.steps.some((step) => step.id === id));\n const message = messages[at];\n if (!message) return messages;\n const steps = message.steps.map((step) => (step.id === id ? change(step) : step));\n return messages.with(at, { ...message, steps });\n}\n\n/** The surface a stopped turn waits on, as the history tells it. */\nfunction awaited(messages: Message[]): string | null {\n return messages.flatMap((message) => message.steps).find((step) => step.awaits)?.id ?? null;\n}\n\n/**\n * The person's answer, landed on the step that waited for it: a question is done with their\n * words as its output (what the history reads back); an action they allowed or declined was\n * already told its own state.\n */\nfunction answered(messages: Message[], action: Action): Message[] {\n return withStep(messages, action.surface, (step) =>\n step.state === \"waiting\"\n ? { ...step, state: \"done\", output: action.label, awaits: false, request: null }\n : { ...step, awaits: false, request: null },\n );\n}\n\n/** How often the history is read again while something goes out on a channel. */\nconst DELIVERY_EVERY = 3_000;\n/** How long a message sent is watched for its receipt before the next read brings it. */\nconst RECEIPT_WITHIN = 60_000;\n\n/**\n * Whether a message is still on its way to the person on a channel: waiting to go, or gone a\n * moment ago and not yet received. What it gets to moves without a frame, so it is read again.\n */\nexport function onItsWay(message: Message, now: number): boolean {\n if (message.delivery === \"queued\") return true;\n return message.delivery === \"sent\" && now - Date.parse(message.created_at) < RECEIPT_WITHIN;\n}\n\n/**\n * Runs one of the host's own tools in the page, for the agent: what it resolves to goes back as\n * the tool's output, what it rejects with as why it failed (an error's message).\n */\nexport type ToolHandler = (call: ToolCall) => Promise<unknown>;\n\n/** What the page answers a tool of the host's when it runs none (no `onTool`), for the agent. */\nexport const NO_TOOL = \"This page can't do that\";\n\nconst refuse = (): Promise<never> => Promise.reject(new Error(NO_TOOL));\n\n/** A conversation, live: its messages, where its turn stands, and the ways to speak. */\nexport interface LiveConversation {\n messages: Message[];\n draft: string;\n pending: boolean;\n /**\n * The step the turn waits on: a surface to answer, or an action the person allows (its\n * `request`, `decides: \"person\"`; `act` on its id with `approve` or `decline`). Nothing else\n * goes until the person acts. A yes the business gives holds nothing.\n */\n awaiting: string | null;\n open: boolean;\n /** Until the history has landed once. */\n loading: boolean;\n /** What the last history read threw, if it failed. */\n error: unknown;\n /**\n * Says something: words, the ids of files uploaded to the conversation for it, or both; false\n * when it cannot go now: nothing to say, a turn running or waiting, the socket closed.\n */\n say: (text: string, files?: readonly string[]) => boolean;\n /** What the person did on a surface; false when the socket is closed. */\n act: (action: Action) => boolean;\n /** Several things done at once (a yes or no on each call a reply waits on), sent together. */\n actAll: (actions: readonly Action[]) => boolean;\n /** Stops the reply on its way; false when the socket is closed. */\n stop: () => boolean;\n /** The conversation's browser while it is open: where it is, who holds it. */\n browser: BrowserState | null;\n /** Hears each picture of the browser's screen, the latest at once; returns how to stop. */\n watchScreen: (listener: (frame: BrowserFrame) => void) => () => void;\n /** Takes the browser from the agent, or gives it back; false when the socket is closed. */\n control: (to: BrowserControl) => boolean;\n /** Closes the browser now (it stops being billed); false when the socket is closed. */\n closeBrowser: () => boolean;\n /** What the person does on the browser's screen while they hold it. */\n input: (event: BrowserInput) => boolean;\n /** Who answers it now: its agent, a person of the business, or nobody; null until the history lands. */\n holder: Holder | null;\n /** Every change of who answers it, in order, kept live. */\n handovers: Handover[];\n}\n\n/**\n * Conversation `conversationId` as the person sees it: the history, kept live by the socket.\n * Each time the socket opens the history is read, with the socket already listening, so nothing\n * stored can fall between the two; what came live meanwhile is kept. `onError` hears each\n * error's code (the socket's, or `load` for a history that could not be read): the host words\n * it, this hook shows no text; but `tool`, the page's answer to a call no longer waiting (one it\n * answered already, told again), which is let pass (noted in development). `onHolder` hears each\n * change of who answers it, live, for the host's own views of it; `handover` is the last change\n * the host knows of (one it made itself, as its request answered), taken at once, so it shows\n * whether or not the socket tells it.\n * `onStep` hears each step the agent starts, live, by its action's name, for the host's own\n * views of it (the builder's column, opened as the agent asks it).\n * `onTool` runs the host's own tools the agent calls in the page (a `tool` frame), and its answer\n * goes up (`tool_result`); each call runs once, and one told again while it waits (the socket\n * opened anew) is answered again with what it gave. Without `onTool`, each is refused with\n * `NO_TOOL`, so the turn never waits on a page that can't run it. One conversation per mount: key\n * the component on the id.\n */\nexport function useConversation(\n client: Client,\n conversationId: string,\n {\n onError,\n onHolder,\n onStep,\n handover,\n onTool,\n }: {\n onError?: ((code: string) => void) | undefined;\n onHolder?: ((holder: Holder, handover: Handover) => void) | undefined;\n onStep?: ((action: string) => void) | undefined;\n handover?: Handover | null | undefined;\n onTool?: ToolHandler | undefined;\n } = {},\n): LiveConversation {\n const [state, dispatch] = useReducer(reduce, EMPTY);\n // The host's change, taken once by what it is: the same one told again changes nothing.\n const told = handover ?? null;\n const toldKey = told === null ? null : handoverKey(told);\n useEffect(() => {\n if (told !== null) dispatch({ type: \"holder\", handled_by: told.holder, handover: told });\n }, [toldKey]);\n // The browser's pictures, out of the state: each repaints its screen alone.\n const [screen] = useState<Screen>(createScreen);\n // Reads are numbered: one that lands after a newer one began, or after the conversation went,\n // is dropped on the floor.\n const reads = useRef(0);\n useEffect(\n () => () => {\n reads.current += 1;\n },\n [client, conversationId],\n );\n\n const read = () => {\n const run = ++reads.current;\n client.conversations.get(conversationId).then(\n ({ messages, pending, handled_by, handovers = [] }) => {\n if (run !== reads.current) return;\n dispatch({ type: \"history\", messages, pending, holder: handled_by, handovers });\n },\n (error: unknown) => {\n if (run !== reads.current) return;\n dispatch({ type: \"failed\", error });\n onError?.(\"load\");\n },\n );\n };\n\n // The host's tools the agent called, by the call's id: running (null), or what each answered.\n const tools = useRef(new Map<string, ToolResult | null>());\n const runTool = ({ id, name, input }: ToolCall) => {\n const known = tools.current.get(id);\n if (known !== undefined) {\n if (known !== null) live.send(known);\n return;\n }\n tools.current.set(id, null);\n void Promise.resolve({ id, name, input })\n .then(onTool ?? refuse)\n .then(\n (value): PromiseSettledResult<unknown> => ({ status: \"fulfilled\", value }),\n (reason: unknown): PromiseSettledResult<unknown> => ({ status: \"rejected\", reason }),\n )\n .then((settled) => {\n const answer = toolResultOf(id, settled);\n tools.current.set(id, answer);\n // A socket closed now hears it when it opens: the call is told again while it waits.\n live.send(answer);\n });\n };\n\n const live = useLive(client, conversationId, {\n onFrame: (frame: Frame) => {\n if (frame.type === \"browser.frame\") {\n screen.show({ jpeg: frame.jpeg, width: frame.width, height: frame.height });\n return;\n }\n if (frame.type === \"error\" && frame.code === STALE) {\n // An answer of the page's that no call waits on any more: nothing for the person.\n note(\"A host's tool answer was let pass:\", frame.detail);\n return;\n }\n if (frame.type === \"browser\" && !frame.open) screen.clear();\n dispatch(frame);\n if (frame.type === \"error\") onError?.(frame.code);\n if (frame.type === \"holder\") onHolder?.(frame.handled_by, frame.handover);\n if (frame.type === \"step\" && frame.state === \"running\" && frame.action !== undefined) {\n onStep?.(frame.action);\n }\n if (frame.type === \"tool\") runTool(frame);\n },\n onOpen: () => {\n dispatch({ type: \"reset\" });\n read();\n },\n });\n\n // On a channel, while something of the business's is on its way to the person, the history is\n // read again every few seconds: where it got to (sent, delivered, read, failed) comes no other\n // way. Each read is a new state, so the question is asked again after it.\n const travelling = state.messages.some((message) => onItsWay(message, Date.now()));\n useEffect(() => {\n if (!travelling || !live.open) return undefined;\n const again = setInterval(read, DELIVERY_EVERY);\n return () => {\n clearInterval(again);\n };\n // `read` does the same work from any render: what it reads is in refs and the dispatch.\n }, [travelling, live.open]);\n\n const say = (text: string, files: readonly string[] = []): boolean => {\n const frame = sayOf(text, files);\n if (frame === null || state.pending || state.awaiting !== null) return false;\n return live.send(frame);\n };\n\n const act = (action: Action): boolean => {\n if (!live.send({ type: \"action\", ...action })) return false;\n dispatch({ type: \"acted\", action });\n return true;\n };\n\n const actAll = (actions: readonly Action[]): boolean => {\n const sent = actions.map((one) => ({ type: \"action\" as const, ...one }));\n if (!live.send({ type: \"actions\", actions: sent })) return false;\n for (const action of actions) dispatch({ type: \"acted\", action });\n return true;\n };\n\n return {\n messages: state.messages,\n draft: state.draft,\n pending: state.pending,\n awaiting: state.awaiting,\n open: live.open,\n loading: !state.loaded,\n error: state.error,\n say,\n act,\n actAll,\n stop: () => live.send({ type: \"stop\" }),\n browser: state.browser,\n watchScreen: screen.watch,\n control: (to) => live.send({ type: \"browser.control\", to }),\n closeBrowser: () => live.send({ type: \"browser.close\" }),\n input: (event) => live.send({ type: \"browser.input\", event }),\n holder: state.holder,\n handovers: state.handovers,\n };\n}\n"],"mappings":"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"}
|