Files
sausage/extensions/messages/api.ts
T

82 lines
3.4 KiB
TypeScript

/**
* Messages: messages between conversations and the user, delivered as inputs or notes. Other extensions send one-way
* messages and answer the user's open questions here.
*/
import type { Context } from '@earendil-works/chord'
import { defineDoc } from '@earendil-works/pi-durable'
import type { EntryId } from '@earendil-works/pi-durable'
import { defineApi } from '../messages/compat.ts'
import type { Device } from '../messages/compat.ts'
import { MessagesDoc } from './refs.ts'
import type { Question, Via } from './refs.ts'
/** One message as the log stores it. */
export type Message = {
id: number
from: number | 'user'
to: number | 'user'
text: string
at: number
reply: boolean
re?: number
state: 'sent' | 'delivered' | 'answered' | 'dismissed' | 'failed' | 'stopped'
entry?: EntryId
answer?: EntryId
error?: string
/** The tool call that sent it: `message`'s or `branch`'s own id, `<script>/<n>` for a nested codemode call. */
call?: string
/** Images forwarded with it: the sender's entry that holds them, attached to the recipient's input on delivery. */
images?: { entry: EntryId; count: number }
/** A branch's final answer routed to its starter after it stopped with nothing under way: it awaits the starter. */
report?: true
/** A branch's reply to its starter: it awaits the starter's answer, routed down to it. */
awaits?: true
/** The starter's final answer to a branch's report or reply, routed to that branch: the answer's entry. */
routed?: EntryId
/** A branch's report answering routed answers only: the starter's answer to it stays local. */
echo?: true
/** The questions a reply-expected message to the user asks, answered on its card. */
questions?: Question[]
/** A user answer or dismissal that the main agent settled for the user, not their own click. */
via?: Via
}
/** Every message between conversations and the user, on the root conversation. */
export const Messages = defineDoc<{ messages: Message[]; next: number }>({
kind: MessagesDoc.kind,
checkpointWhen: () => false,
version: 1,
scope: 'conversation',
history: 'latest',
fork: 'initial',
initial: () => ({ messages: [], next: 1 }),
})
export type MessagesService = {
/**
* Reply `text` from the user to open message `messageId`, from browser `device`; returns the reply's message
* id. Records a `user.input` event.
*/
answer(messageId: number, text: string, ctx: Context, device?: Device): Promise<number>
/**
* The messages that wait on the user's answer, oldest first: each one's id, the conversation that asked, when, and its
* question in one line (the line its card's header shows).
*/
openQuestions(
ctx: Context,
): Promise<{ message: number; conversation: number; at: number; text: string }[]>
/** Close open message `messageId` unanswered; its sender gets a one-way note. Returns the note's message id. */
dismiss(messageId: number, ctx: Context, device?: Device): Promise<number>
/** Send a one-way message from conversation `from` to `to` (`#id`, `@name` or `user`), once per `requestId`. */
send(
from: number,
input: { to: string; text: string; reply: false },
requestId: string,
ctx: Context,
): Promise<number>
}
export { MessageSent } from './refs.ts'
export type { Question } from './refs.ts'
/** Messages between conversations; core, so `need` it. */
export const MessagesApi = defineApi<MessagesService>()('messages')
export const tier = 'core'