@mercury-fw/channel-http 0.15.0 → 0.16.0
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/CHANGELOG.md +21 -0
- package/README.md +8 -2
- package/dist/http-provider.d.ts +3 -1
- package/dist/http-server.d.ts +9 -5
- package/http-provider.ts +4 -1
- package/http-server.ts +44 -15
- package/index.ts +1 -0
- package/openapi.yaml +157 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @mercury-fw/channel-http
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 07d1ef6: - conversations get a title, generated from the first question and its answer after the first turn the person wrote
|
|
8
|
+
- channel contract: optional `ctx.conversations` with `rename`, the title the person chose, never replaced by a generated one
|
|
9
|
+
- `GET /conversations` and `GET /conversation` carry `title` once a conversation has one
|
|
10
|
+
- `PATCH /conversation?id=<conversationId>` with `{ title }` renames one of the caller's conversations
|
|
11
|
+
|
|
12
|
+
## 0.15.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 9e2fbe4: - the verbatim archive keeps, with each answer, what its turn showed besides the text, as `trace`: reasoning blocks, tool status lines with their outcome, confirmations staged, logins asked for (no tokens, no login links)
|
|
17
|
+
- a stopped turn's interrupted answer keeps its `trace` up to the stop
|
|
18
|
+
- an answer that only staged an action keeps its `pendingNote`
|
|
19
|
+
- a resolved confirmation is archived in its conversation as a message with `confirmation: { id, status, choice? }`, its `content` the reply the person got
|
|
20
|
+
- `GET /conversation` returns `trace`, `pendingNote` and `confirmation`; `recall_verbatim` and a conversation rebuilt from the archive leave them out
|
|
21
|
+
- `ConfirmDeps.onResolved` (`@mercury-fw/confirm-engine`): told how a staged action was resolved
|
|
22
|
+
- `openapi.yaml`: `Message.trace`, `Message.pendingNote`, `Message.confirmation`, `TraceEntry`
|
|
23
|
+
|
|
3
24
|
## 0.15.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -97,6 +97,10 @@ A code for linking another of the caller's accounts to them, for the UI to show:
|
|
|
97
97
|
|
|
98
98
|
Saves the caller's time zone, for a UI's settings: body `{ timeZone }`, an IANA name such as `Europe/Rome`; returns `{ ok: true, timeZone }` as saved (`europe/rome` is saved as `Europe/Rome`), or `400` with the reason for what isn't a zone's name (a fixed offset such as `+02:00` isn't one) and for a body without it. From then on the caller's turns are stamped with the time there, whatever `X-Time-Zone` says. Not there on a core without settings.
|
|
99
99
|
|
|
100
|
+
## `PATCH /conversation`
|
|
101
|
+
|
|
102
|
+
Renames one of the caller's conversations: `?id=<conversationId>`, body `{ title }`; returns `{ ok: true, title }` with the title as saved (trimmed). A title has at most 100 characters, on one line: an empty one, a longer one or one with a line break is `400` with the reason, as are a missing or malformed `id` and a body without `title`. `404` when no conversation of the caller's has that id: only a conversation with at least one turn in the archive can be renamed. Mercury never replaces a title the person chose. Not there on a core that can't rename conversations.
|
|
103
|
+
|
|
100
104
|
## `POST /confirm`
|
|
101
105
|
|
|
102
106
|
Explicit alternative to re-sending a token as `/turn` `text`. Body `{ token, conversationId }`; returns `{ ok: true, resolved: true, text }` when the token was a pending confirmation (`text`: the core's reply in the caller's language, "Confirmed and done." or the failure, never the action's raw result), `{ ok: true, resolved: false }` otherwise. When the confirmed action hands its result back to the model, it returns `{ ok: true, resolved: true, resume: true }` instead: open `POST /turn { resume: true, conversationId }` to hear the model's answer. `400` if `token` or `conversationId` is missing, or the id has other characters than `/turn` accepts. Never invokes the model.
|
|
@@ -107,8 +111,8 @@ All `GET`, all JSON, all reporting state already held in-process. They need an a
|
|
|
107
111
|
|
|
108
112
|
| endpoint | `data` on success |
|
|
109
113
|
|---|---|
|
|
110
|
-
| `GET /conversation?id=<conversationId>&limit=<n>&offset=<cursor>` | `{ messages: [{ role, content, interrupted?, timestamp }], nextOffset }` — one of your conversations, its durable transcript in order, each answer as you saw it with the lists it showed (empty for an id that isn't yours). An answer whose turn was cancelled has `interrupted: true` and an empty `content`: the question before it was left unanswered. `400` if `id` is missing or has other characters than `/turn` accepts |
|
|
111
|
-
| `GET /conversations?limit=<n>` | `{ conversations: [{ conversationId, lastTimestamp, preview }] }` — your conversations, most-recently-active first; the preview is the newest message that shows something, so the question when the last answer was interrupted |
|
|
114
|
+
| `GET /conversation?id=<conversationId>&limit=<n>&offset=<cursor>` | `{ messages: [{ role, content, interrupted?, trace?, pendingNote?, confirmation?, timestamp }], nextOffset, title? }` — one of your conversations, its durable transcript in order, each answer as you saw it with the lists it showed (empty for an id that isn't yours). An answer whose turn was cancelled has `interrupted: true` and an empty `content`: the question before it was left unanswered. An answer's `trace` is what its turn showed besides the text, in order, as the events above showed it: each reasoning block in full, each status line with its tool and outcome, each confirmation staged (its command and its choices' labels, named by an `id`, never a token), each login asked for (its service, never the link); up to the stop for a cancelled turn. `pendingNote` is the turn's `final` one. A message with `confirmation: { id, status, choice? }` says how the staged action with that `id` (its trace entry) was resolved (`ok`, `failed`, or `resume`, whose answer is the next message): its `content` is the reply you got, empty for `resume`. Answers archived before these fields have none of them. `400` if `id` is missing or has other characters than `/turn` accepts |
|
|
115
|
+
| `GET /conversations?limit=<n>` | `{ conversations: [{ conversationId, lastTimestamp, preview, title? }] }` — your conversations, most-recently-active first; the preview is the newest message that shows something, so the question when the last answer was interrupted |
|
|
112
116
|
| `GET /manifest` | `{ manifest: { coreApiVersion, plugins: [{ name, apiVersion, active, skills, hasBuild, customStatus }], activeClis, skills } }` |
|
|
113
117
|
| `GET /confirmations` | `{ pending: [{ sessionKey, summary, expiresAt }] }` — your pending confirmations; tokens are deliberately never included |
|
|
114
118
|
| `GET /me` | `{ name, language, timeZone }` — how you asked the assistant to call you, the language you asked it to use and the time zone you saved, `null` when you never said; `language` and `timeZone` are missing on a core that doesn't know them, and the route isn't there on a core older than this channel |
|
|
@@ -122,6 +126,8 @@ All `GET`, all JSON, all reporting state already held in-process. They need an a
|
|
|
122
126
|
|
|
123
127
|
Conversation history and the conversation list are backed by the durable verbatim archive; they degrade to empty when the vector store is unreachable.
|
|
124
128
|
|
|
129
|
+
**Titles.** A few seconds after the first turn the person wrote in a conversation, Mercury names it from that first question and its answer (after the next turn if it couldn't, for instance with the model unreachable: until then the conversation has no title), and the title stays the same from then on; the person can rename it (`PATCH /conversation`), and Mercury never replaces a title they chose. `title` is there on both `GET /conversation` and `GET /conversations` once the conversation has one; until then, `preview` is what there is to show. A UI that wants the title of a new conversation reads the list again shortly after the turn's stream closes.
|
|
130
|
+
|
|
125
131
|
## `GET /version`
|
|
126
132
|
|
|
127
133
|
Which Mercury this is, for a UI to show: `{ ok: true, version }`, the framework's version as `major.minor.patch` (`"0.59.0"`), the one of `@mercury-fw/core`, not this channel's own. When the app's config declares its own version (`version` in `mercury.config.ts`, its `package.json`'s, as a scaffolded app does), it comes too, as it is: `{ ok: true, version: "0.65.0", app: { version: "1.2.0" } }`. A new one shows once the app is rebuilt. It needs no token, so anyone who reaches the port sees both.
|
package/dist/http-provider.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* injected (`confirm`/`resolveConfirmation`), so this package never imports the app.
|
|
12
12
|
*/
|
|
13
13
|
import { type AdmitFn, type CompleteLoginFn, type ConfirmFn, type ResolveConfirmationFn } from "./http-server.ts";
|
|
14
|
-
import type { Authenticate, Provider, ChannelHostReads, ChannelI18n, ChannelLinking, ChannelSettings } from "@mercury-fw/channel-types";
|
|
14
|
+
import type { Authenticate, Provider, ChannelHostReads, ChannelI18n, ChannelLinking, ChannelSettings, ChannelConversations } from "@mercury-fw/channel-types";
|
|
15
15
|
export type HttpProviderDeps = {
|
|
16
16
|
port: number;
|
|
17
17
|
/** Bare-token interception before the model. */
|
|
@@ -36,6 +36,8 @@ export type HttpProviderDeps = {
|
|
|
36
36
|
linking?: ChannelLinking;
|
|
37
37
|
/** The person's own settings (`PATCH /me`). */
|
|
38
38
|
settings?: ChannelSettings;
|
|
39
|
+
/** Renaming the person's conversations (`PATCH /conversation`). */
|
|
40
|
+
conversations?: ChannelConversations;
|
|
39
41
|
/** The surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
|
|
40
42
|
resumeMaxChars?: number;
|
|
41
43
|
/** What the pages people read call the assistant. */
|
package/dist/http-server.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Confirm resolution is injected too (`confirm`/`resolveConfirmation`), so this
|
|
23
23
|
* package never imports the app.
|
|
24
24
|
*/
|
|
25
|
-
import { type HandleTurn, type Authenticate, type ChannelHostReads, type ChannelI18n, type ChannelRuntimeContext, type LoginOutcome, type Principal, type Admission, type ChannelLinking, type ChannelSettings } from "@mercury-fw/channel-types";
|
|
25
|
+
import { type HandleTurn, type Authenticate, type ChannelHostReads, type ChannelI18n, type ChannelRuntimeContext, type LoginOutcome, type Principal, type Admission, type ChannelLinking, type ChannelSettings, type ChannelConversations } from "@mercury-fw/channel-types";
|
|
26
26
|
/** Resolves a bare confirmation token to what to tell the person, or `null` if the input isn't a token. Injected by the core (`ctx.confirm`). */
|
|
27
27
|
export type ConfirmFn = ChannelRuntimeContext["confirm"];
|
|
28
28
|
/** The structured sibling of {@link ConfirmFn}, for the `/confirm` `resolved` flag. Injected by the core (`ctx.resolveConfirmation`). */
|
|
@@ -124,10 +124,10 @@ export declare function handleLoginCallback(req: Request, deps: {
|
|
|
124
124
|
assistantName?: string;
|
|
125
125
|
i18n?: ChannelI18n;
|
|
126
126
|
}): Promise<Response>;
|
|
127
|
-
/** A single read route: its `GET` handler plus the shared `OPTIONS` preflight, and on `/me` the `PATCH`
|
|
127
|
+
/** A single read route: its `GET` handler plus the shared `OPTIONS` preflight, and on `/me` and `/conversation` the `PATCH` changing what it reads. */
|
|
128
128
|
type ReadRoute = {
|
|
129
129
|
GET: (req: Request) => Response | Promise<Response>;
|
|
130
|
-
/**
|
|
130
|
+
/** On `/me` when the core offers the person's settings (`PATCH /me`), on `/conversation` when it renames conversations (`PATCH /conversation`). */
|
|
131
131
|
PATCH?: (req: Request) => Promise<Response>;
|
|
132
132
|
OPTIONS: () => Response;
|
|
133
133
|
};
|
|
@@ -141,9 +141,11 @@ type ReadRoute = {
|
|
|
141
141
|
* `/turn`, and its session key is built from the caller.
|
|
142
142
|
* `json` JSON-encodes with the CORS headers merged in; `badRequest` does the
|
|
143
143
|
* same for a 400. With `settings`, `/me` also takes a `PATCH` saving the
|
|
144
|
-
* caller's time zone (a UI's settings area), asking `authenticate` first too
|
|
144
|
+
* caller's time zone (a UI's settings area), asking `authenticate` first too;
|
|
145
|
+
* with `conversations`, `/conversation` takes a `PATCH` renaming one of the
|
|
146
|
+
* caller's conversations, the same way.
|
|
145
147
|
*/
|
|
146
|
-
export declare function readRoutes(reads: ChannelHostReads, authenticate: Authenticate, corsOrigin?: string, admit?: AdmitFn, settings?: ChannelSettings): Record<string, ReadRoute>;
|
|
148
|
+
export declare function readRoutes(reads: ChannelHostReads, authenticate: Authenticate, corsOrigin?: string, admit?: AdmitFn, settings?: ChannelSettings, conversations?: ChannelConversations): Record<string, ReadRoute>;
|
|
147
149
|
export type HttpServerDeps = TurnRequestDeps & {
|
|
148
150
|
port: number;
|
|
149
151
|
reads?: ChannelHostReads;
|
|
@@ -155,6 +157,8 @@ export type HttpServerDeps = TurnRequestDeps & {
|
|
|
155
157
|
linking?: ChannelLinking;
|
|
156
158
|
/** The person's own settings; `PATCH /me` is mounted only with it. */
|
|
157
159
|
settings?: ChannelSettings;
|
|
160
|
+
/** Renaming the person's conversations; `PATCH /conversation` is mounted only with it. */
|
|
161
|
+
conversations?: ChannelConversations;
|
|
158
162
|
/** What the pages people read call the assistant. */
|
|
159
163
|
assistantName?: string;
|
|
160
164
|
/** The framework's version; `/version` is mounted only with it. */
|
package/http-provider.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* injected (`confirm`/`resolveConfirmation`), so this package never imports the app.
|
|
12
12
|
*/
|
|
13
13
|
import { startHttpServer, type AdmitFn, type CompleteLoginFn, type ConfirmFn, type ResolveConfirmationFn } from "./http-server.ts";
|
|
14
|
-
import type { Authenticate, Provider, HandleTurn, ChannelHostReads, ChannelI18n, ChannelLinking, ChannelSettings } from "@mercury-fw/channel-types";
|
|
14
|
+
import type { Authenticate, Provider, HandleTurn, ChannelHostReads, ChannelI18n, ChannelLinking, ChannelSettings, ChannelConversations } from "@mercury-fw/channel-types";
|
|
15
15
|
|
|
16
16
|
export type HttpProviderDeps = {
|
|
17
17
|
port: number;
|
|
@@ -33,6 +33,8 @@ export type HttpProviderDeps = {
|
|
|
33
33
|
linking?: ChannelLinking;
|
|
34
34
|
/** The person's own settings (`PATCH /me`). */
|
|
35
35
|
settings?: ChannelSettings;
|
|
36
|
+
/** Renaming the person's conversations (`PATCH /conversation`). */
|
|
37
|
+
conversations?: ChannelConversations;
|
|
36
38
|
/** The surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
|
|
37
39
|
resumeMaxChars?: number;
|
|
38
40
|
/** What the pages people read call the assistant. */
|
|
@@ -61,6 +63,7 @@ export function createHttpProvider(deps: HttpProviderDeps): Provider & { stop():
|
|
|
61
63
|
completeLogin: deps.logins?.complete,
|
|
62
64
|
...(deps.linking === undefined ? {} : { linking: deps.linking }),
|
|
63
65
|
...(deps.settings === undefined ? {} : { settings: deps.settings }),
|
|
66
|
+
...(deps.conversations === undefined ? {} : { conversations: deps.conversations }),
|
|
64
67
|
...(deps.resumeMaxChars === undefined ? {} : { resumeMaxChars: deps.resumeMaxChars }),
|
|
65
68
|
...(deps.assistantName === undefined ? {} : { assistantName: deps.assistantName }),
|
|
66
69
|
...(deps.i18n === undefined ? {} : { i18n: deps.i18n }),
|
package/http-server.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
type Admission,
|
|
37
37
|
type ChannelLinking,
|
|
38
38
|
type ChannelSettings,
|
|
39
|
+
type ChannelConversations,
|
|
39
40
|
} from "@mercury-fw/channel-types";
|
|
40
41
|
import { englishHttp } from "./messages.ts";
|
|
41
42
|
|
|
@@ -457,10 +458,10 @@ function loginRoutes(
|
|
|
457
458
|
};
|
|
458
459
|
}
|
|
459
460
|
|
|
460
|
-
/** A single read route: its `GET` handler plus the shared `OPTIONS` preflight, and on `/me` the `PATCH`
|
|
461
|
+
/** A single read route: its `GET` handler plus the shared `OPTIONS` preflight, and on `/me` and `/conversation` the `PATCH` changing what it reads. */
|
|
461
462
|
type ReadRoute = {
|
|
462
463
|
GET: (req: Request) => Response | Promise<Response>;
|
|
463
|
-
/**
|
|
464
|
+
/** On `/me` when the core offers the person's settings (`PATCH /me`), on `/conversation` when it renames conversations (`PATCH /conversation`). */
|
|
464
465
|
PATCH?: (req: Request) => Promise<Response>;
|
|
465
466
|
OPTIONS: () => Response;
|
|
466
467
|
};
|
|
@@ -475,7 +476,9 @@ type ReadRoute = {
|
|
|
475
476
|
* `/turn`, and its session key is built from the caller.
|
|
476
477
|
* `json` JSON-encodes with the CORS headers merged in; `badRequest` does the
|
|
477
478
|
* same for a 400. With `settings`, `/me` also takes a `PATCH` saving the
|
|
478
|
-
* caller's time zone (a UI's settings area), asking `authenticate` first too
|
|
479
|
+
* caller's time zone (a UI's settings area), asking `authenticate` first too;
|
|
480
|
+
* with `conversations`, `/conversation` takes a `PATCH` renaming one of the
|
|
481
|
+
* caller's conversations, the same way.
|
|
479
482
|
*/
|
|
480
483
|
export function readRoutes(
|
|
481
484
|
reads: ChannelHostReads,
|
|
@@ -483,6 +486,7 @@ export function readRoutes(
|
|
|
483
486
|
corsOrigin = "*",
|
|
484
487
|
admit?: AdmitFn,
|
|
485
488
|
settings?: ChannelSettings,
|
|
489
|
+
conversations?: ChannelConversations,
|
|
486
490
|
): Record<string, ReadRoute> {
|
|
487
491
|
const cors = corsHeaders(corsOrigin);
|
|
488
492
|
const json = (payload: object, status = 200): Response =>
|
|
@@ -500,26 +504,49 @@ export function readRoutes(
|
|
|
500
504
|
return {
|
|
501
505
|
"/manifest": route(async (_req, principal) => json({ ok: true, manifest: await reads.manifest(principal) })),
|
|
502
506
|
"/confirmations": route(async (_req, principal) => json({ ok: true, pending: await reads.pendingConfirmations(principal) })),
|
|
503
|
-
"/conversation":
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
507
|
+
"/conversation": {
|
|
508
|
+
...route(async (req, principal) => {
|
|
509
|
+
const url = new URL(req.url);
|
|
510
|
+
const id = url.searchParams.get("id");
|
|
511
|
+
if (!id) return badRequest("missing ?id");
|
|
512
|
+
if (!CONVERSATION_ID.test(id)) return badRequest("invalid conversationId");
|
|
513
|
+
const limit = Number(url.searchParams.get("limit") ?? "200");
|
|
514
|
+
const offset = url.searchParams.get("offset") ?? undefined;
|
|
515
|
+
return json({ ok: true, ...(await reads.conversation(principal, `${principal.id}:${id}`, limit, offset) as object) });
|
|
516
|
+
}),
|
|
517
|
+
...(conversations === undefined
|
|
518
|
+
? {}
|
|
519
|
+
: {
|
|
520
|
+
PATCH: async (req: Request) => {
|
|
521
|
+
const principal = await caller(req, { authenticate, ...(admit === undefined ? {} : { admit }) }, corsOrigin);
|
|
522
|
+
if (principal instanceof Response) return principal;
|
|
523
|
+
const id = new URL(req.url).searchParams.get("id");
|
|
524
|
+
if (!id) return badRequest("missing ?id");
|
|
525
|
+
if (!CONVERSATION_ID.test(id)) return badRequest("invalid conversationId");
|
|
526
|
+
const body = (await req.json().catch(() => null)) as { title?: unknown } | null;
|
|
527
|
+
if (typeof body?.title !== "string") return badRequest('the body must be {"title": "<text>"}');
|
|
528
|
+
const renamed = await conversations.rename(principal, `${principal.id}:${id}`, body.title);
|
|
529
|
+
if (renamed.ok) return json({ ok: true, title: renamed.title });
|
|
530
|
+
return renamed.notFound ? json({ ok: false, error: renamed.error }, 404) : badRequest(renamed.error);
|
|
531
|
+
},
|
|
532
|
+
}),
|
|
533
|
+
},
|
|
512
534
|
"/conversations": route(async (req, principal) => {
|
|
513
535
|
const limit = Number(new URL(req.url).searchParams.get("limit") ?? "50");
|
|
514
536
|
const { conversations } = (await reads.conversations(principal, limit)) as {
|
|
515
|
-
conversations: Array<{ sessionKey: string; lastTimestamp: string; preview: string }>;
|
|
537
|
+
conversations: Array<{ sessionKey: string; lastTimestamp: string; preview: string; title?: string }>;
|
|
516
538
|
};
|
|
517
539
|
const prefix = `${principal.id}:`;
|
|
518
540
|
return json({
|
|
519
541
|
ok: true,
|
|
520
542
|
conversations: conversations
|
|
521
543
|
.filter((c) => c.sessionKey.startsWith(prefix))
|
|
522
|
-
.map((c) => ({
|
|
544
|
+
.map((c) => ({
|
|
545
|
+
conversationId: c.sessionKey.slice(prefix.length),
|
|
546
|
+
lastTimestamp: c.lastTimestamp,
|
|
547
|
+
preview: c.preview,
|
|
548
|
+
...(c.title === undefined ? {} : { title: c.title }),
|
|
549
|
+
})),
|
|
523
550
|
});
|
|
524
551
|
}),
|
|
525
552
|
"/tool-log": route(async (_req, principal) => json({ ok: true, entries: await reads.toolLog(principal) })),
|
|
@@ -604,6 +631,8 @@ export type HttpServerDeps = TurnRequestDeps & {
|
|
|
604
631
|
linking?: ChannelLinking;
|
|
605
632
|
/** The person's own settings; `PATCH /me` is mounted only with it. */
|
|
606
633
|
settings?: ChannelSettings;
|
|
634
|
+
/** Renaming the person's conversations; `PATCH /conversation` is mounted only with it. */
|
|
635
|
+
conversations?: ChannelConversations;
|
|
607
636
|
/** What the pages people read call the assistant. */
|
|
608
637
|
assistantName?: string;
|
|
609
638
|
/** The framework's version; `/version` is mounted only with it. */
|
|
@@ -642,7 +671,7 @@ export function startHttpServer(deps: HttpServerDeps): ReturnType<typeof Bun.ser
|
|
|
642
671
|
},
|
|
643
672
|
"/openapi.yaml": { GET: () => openApiResponse(origin), OPTIONS: () => preflight(origin) },
|
|
644
673
|
...(deps.version === undefined ? {} : versionRoutes(deps.version, deps.appVersion, origin)),
|
|
645
|
-
...(deps.reads ? readRoutes(deps.reads, deps.authenticate, origin, deps.admit, deps.settings) : {}),
|
|
674
|
+
...(deps.reads ? readRoutes(deps.reads, deps.authenticate, origin, deps.admit, deps.settings, deps.conversations) : {}),
|
|
646
675
|
...(deps.completeLogin ? loginRoutes(deps.completeLogin, deps.assistantName, deps.i18n) : {}),
|
|
647
676
|
...(deps.linking ? linkRoutes({ ...deps, start: deps.linking.start, corsOrigin: origin }) : {}),
|
|
648
677
|
},
|
package/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ export const httpChannel: ChannelPlugin = {
|
|
|
49
49
|
i18n: ctx.i18n,
|
|
50
50
|
...(ctx.linking === undefined ? {} : { linking: ctx.linking }),
|
|
51
51
|
...(ctx.settings === undefined ? {} : { settings: ctx.settings }),
|
|
52
|
+
...(ctx.conversations === undefined ? {} : { conversations: ctx.conversations }),
|
|
52
53
|
...(resumeMaxChars === undefined ? {} : { resumeMaxChars }),
|
|
53
54
|
...(ctx.assistantName === undefined ? {} : { assistantName: ctx.assistantName }),
|
|
54
55
|
...(ctx.frameworkVersion === undefined ? {} : { version: ctx.frameworkVersion }),
|
package/openapi.yaml
CHANGED
|
@@ -49,6 +49,14 @@ info:
|
|
|
49
49
|
by the verbatim archive; they degrade to empty when the vector store is
|
|
50
50
|
unreachable).
|
|
51
51
|
|
|
52
|
+
A conversation has a title once Mercury has named it, a few seconds after
|
|
53
|
+
the first turn the person wrote in it, from that first question and its
|
|
54
|
+
answer (after the next turn if it couldn't, for instance with the model
|
|
55
|
+
unreachable); it then stays the same. The caller renames one of their
|
|
56
|
+
conversations with `PATCH /conversation`, and Mercury never replaces a
|
|
57
|
+
title the person chose. Until there is a title, `title` is absent from
|
|
58
|
+
both reads and `preview` is what there is to show.
|
|
59
|
+
|
|
52
60
|
A conversation never ends: sent again with the same `conversationId`, a
|
|
53
61
|
turn continues it however long after. Mercury frees a conversation's
|
|
54
62
|
in-memory history once it has been idle a while; the next turn rebuilds
|
|
@@ -262,7 +270,7 @@ paths:
|
|
|
262
270
|
"503":
|
|
263
271
|
$ref: "#/components/responses/NotAdmitted"
|
|
264
272
|
"200":
|
|
265
|
-
description: The conversation's messages in chronological order; none when it isn't one of the caller's.
|
|
273
|
+
description: The conversation's messages in chronological order, each answer with what its turn showed besides the text; none when it isn't one of the caller's.
|
|
266
274
|
content:
|
|
267
275
|
application/json:
|
|
268
276
|
schema:
|
|
@@ -273,6 +281,64 @@ paths:
|
|
|
273
281
|
application/json:
|
|
274
282
|
schema:
|
|
275
283
|
$ref: "#/components/schemas/Error"
|
|
284
|
+
patch:
|
|
285
|
+
summary: Renames one of the caller's conversations
|
|
286
|
+
description: >
|
|
287
|
+
Gives the conversation the title the caller chose, trimmed, in place of
|
|
288
|
+
the one it had; Mercury never replaces it with one it generates. Only a
|
|
289
|
+
conversation already in the archive (one with a turn) can be renamed.
|
|
290
|
+
Not there on a core that can't rename conversations.
|
|
291
|
+
parameters:
|
|
292
|
+
- name: id
|
|
293
|
+
in: query
|
|
294
|
+
required: true
|
|
295
|
+
description: The caller's `conversationId`, as `/conversations` lists it.
|
|
296
|
+
schema:
|
|
297
|
+
type: string
|
|
298
|
+
pattern: "^[A-Za-z0-9_-]{1,128}$"
|
|
299
|
+
requestBody:
|
|
300
|
+
required: true
|
|
301
|
+
content:
|
|
302
|
+
application/json:
|
|
303
|
+
schema:
|
|
304
|
+
type: object
|
|
305
|
+
required: [title]
|
|
306
|
+
properties:
|
|
307
|
+
title:
|
|
308
|
+
type: string
|
|
309
|
+
description: At most 100 characters once trimmed, on one line.
|
|
310
|
+
responses:
|
|
311
|
+
"400":
|
|
312
|
+
description: Missing or malformed `?id`, a body with no `title`, or a title that is empty, longer than 100 characters or holds a line break.
|
|
313
|
+
content:
|
|
314
|
+
application/json:
|
|
315
|
+
schema:
|
|
316
|
+
$ref: "#/components/schemas/Error"
|
|
317
|
+
"401":
|
|
318
|
+
$ref: "#/components/responses/Unauthorized"
|
|
319
|
+
"403":
|
|
320
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
321
|
+
"404":
|
|
322
|
+
description: No conversation of the caller's has this id.
|
|
323
|
+
content:
|
|
324
|
+
application/json:
|
|
325
|
+
schema:
|
|
326
|
+
$ref: "#/components/schemas/Error"
|
|
327
|
+
"503":
|
|
328
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
329
|
+
"200":
|
|
330
|
+
description: The title as saved.
|
|
331
|
+
content:
|
|
332
|
+
application/json:
|
|
333
|
+
schema:
|
|
334
|
+
type: object
|
|
335
|
+
required: [ok, title]
|
|
336
|
+
properties:
|
|
337
|
+
ok:
|
|
338
|
+
type: boolean
|
|
339
|
+
enum: [true]
|
|
340
|
+
title:
|
|
341
|
+
type: string
|
|
276
342
|
/conversations:
|
|
277
343
|
get:
|
|
278
344
|
summary: List the caller's conversations (most-recently-active first)
|
|
@@ -830,9 +896,93 @@ components:
|
|
|
830
896
|
type: boolean
|
|
831
897
|
enum: [true]
|
|
832
898
|
description: "On an answer whose turn was stopped before it was finished (the client closed the stream): `content` is empty. The question before it was left unanswered."
|
|
899
|
+
trace:
|
|
900
|
+
type: array
|
|
901
|
+
description: >
|
|
902
|
+
On an answer: what the person saw of the turn besides its text, in
|
|
903
|
+
the order it happened, as the turn's events showed it (up to the
|
|
904
|
+
stop, for an interrupted one). Absent when there was nothing, and
|
|
905
|
+
on answers archived before it was kept.
|
|
906
|
+
items:
|
|
907
|
+
$ref: "#/components/schemas/TraceEntry"
|
|
908
|
+
pendingNote:
|
|
909
|
+
type: string
|
|
910
|
+
description: >
|
|
911
|
+
On an answer whose model wrote nothing because the turn staged an
|
|
912
|
+
action: the core's stand-in sentence `content` begins with, as the
|
|
913
|
+
turn's `final` event gave it. A UI showing the confirmation from
|
|
914
|
+
the trace hides it.
|
|
915
|
+
confirmation:
|
|
916
|
+
type: object
|
|
917
|
+
description: >
|
|
918
|
+
On the message saying how a staged action was resolved (a token
|
|
919
|
+
sent back, a choice clicked): `content` is the reply the person
|
|
920
|
+
got, empty when the confirmed action's result went back to the
|
|
921
|
+
model (`resume`), whose answer is the next message. `id` is the
|
|
922
|
+
one of the trace's `confirmation` entry it resolves, which says
|
|
923
|
+
what the action was.
|
|
924
|
+
required: [id, status]
|
|
925
|
+
properties:
|
|
926
|
+
id:
|
|
927
|
+
type: string
|
|
928
|
+
status:
|
|
929
|
+
type: string
|
|
930
|
+
enum: [ok, failed, resume]
|
|
931
|
+
choice:
|
|
932
|
+
type: string
|
|
933
|
+
description: The label of the choice taken, when the action was offered as several.
|
|
833
934
|
timestamp:
|
|
834
935
|
type: string
|
|
835
936
|
description: ISO 8601, millisecond precision — the durable ordering key.
|
|
937
|
+
TraceEntry:
|
|
938
|
+
type: object
|
|
939
|
+
description: >
|
|
940
|
+
One thing a turn showed besides its text. `reasoning`: a reasoning
|
|
941
|
+
block in full, `failed` when it was cut. `tool`: a status line, with
|
|
942
|
+
the tool the model called (`name`, absent for Mercury's own lines) and
|
|
943
|
+
how it went (`outcome`, absent when it never finished). `confirmation`:
|
|
944
|
+
an action staged, with the labels of its choices (one empty label for a
|
|
945
|
+
plain confirmation); `id` names it, never its token. `login`: a login
|
|
946
|
+
asked for, by service, never its link.
|
|
947
|
+
required: [type]
|
|
948
|
+
properties:
|
|
949
|
+
type:
|
|
950
|
+
type: string
|
|
951
|
+
enum: [reasoning, tool, confirmation, login]
|
|
952
|
+
text:
|
|
953
|
+
type: string
|
|
954
|
+
description: On `reasoning`.
|
|
955
|
+
failed:
|
|
956
|
+
type: boolean
|
|
957
|
+
enum: [true]
|
|
958
|
+
description: On `reasoning`.
|
|
959
|
+
label:
|
|
960
|
+
type: string
|
|
961
|
+
description: On `tool`.
|
|
962
|
+
detail:
|
|
963
|
+
type: string
|
|
964
|
+
description: On `tool`.
|
|
965
|
+
name:
|
|
966
|
+
type: string
|
|
967
|
+
description: On `tool`.
|
|
968
|
+
outcome:
|
|
969
|
+
type: string
|
|
970
|
+
enum: [success, failed, pending]
|
|
971
|
+
description: On `tool`.
|
|
972
|
+
id:
|
|
973
|
+
type: string
|
|
974
|
+
description: On `confirmation`.
|
|
975
|
+
command:
|
|
976
|
+
type: string
|
|
977
|
+
description: On `confirmation`.
|
|
978
|
+
choices:
|
|
979
|
+
type: array
|
|
980
|
+
items:
|
|
981
|
+
type: string
|
|
982
|
+
description: On `confirmation`.
|
|
983
|
+
service:
|
|
984
|
+
type: string
|
|
985
|
+
description: On `login`.
|
|
836
986
|
ConversationPage:
|
|
837
987
|
type: object
|
|
838
988
|
required: [ok, messages]
|
|
@@ -848,6 +998,9 @@ components:
|
|
|
848
998
|
oneOf:
|
|
849
999
|
- type: string
|
|
850
1000
|
- type: "null"
|
|
1001
|
+
title:
|
|
1002
|
+
type: string
|
|
1003
|
+
description: The conversation's title, Mercury's or the caller's; absent until it has one.
|
|
851
1004
|
ConversationSummary:
|
|
852
1005
|
type: object
|
|
853
1006
|
required: [conversationId, lastTimestamp, preview]
|
|
@@ -859,6 +1012,9 @@ components:
|
|
|
859
1012
|
preview:
|
|
860
1013
|
type: string
|
|
861
1014
|
description: The newest message that shows something, as the person saw it, so the question when the last answer was interrupted.
|
|
1015
|
+
title:
|
|
1016
|
+
type: string
|
|
1017
|
+
description: The conversation's title, Mercury's or the caller's; absent until it has one, when `preview` is what there is to show.
|
|
862
1018
|
ConversationList:
|
|
863
1019
|
type: object
|
|
864
1020
|
required: [ok, conversations]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/channel-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@mercury-fw/channel-types": ">=0.
|
|
21
|
+
"@mercury-fw/channel-types": ">=0.70.0 <1.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@mercury-fw/channel-types": "0.
|
|
25
|
-
"@mercury-fw/plugin-types": "0.
|
|
24
|
+
"@mercury-fw/channel-types": "0.70.0",
|
|
25
|
+
"@mercury-fw/plugin-types": "0.70.0",
|
|
26
26
|
"@mercury-fw/typescript-config": "*",
|
|
27
27
|
"@types/bun": "^1.4.2",
|
|
28
28
|
"typescript": "^6.0.3"
|