@mercury-fw/channel-http 0.15.1 → 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 CHANGED
@@ -1,5 +1,14 @@
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
+
3
12
  ## 0.15.1
4
13
 
5
14
  ### Patch 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?, trace?, pendingNote?, confirmation?, 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. 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 |
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.
@@ -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. */
@@ -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` saving a setting. */
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
- /** Only on `/me`, when the core offers the person's settings (`PATCH /me`). */
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` saving a setting. */
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
- /** Only on `/me`, when the core offers the person's settings (`PATCH /me`). */
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": route(async (req, principal) => {
504
- const url = new URL(req.url);
505
- const id = url.searchParams.get("id");
506
- if (!id) return badRequest("missing ?id");
507
- if (!CONVERSATION_ID.test(id)) return badRequest("invalid conversationId");
508
- const limit = Number(url.searchParams.get("limit") ?? "200");
509
- const offset = url.searchParams.get("offset") ?? undefined;
510
- return json({ ok: true, ...(await reads.conversation(principal, `${principal.id}:${id}`, limit, offset) as object) });
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) => ({ conversationId: c.sessionKey.slice(prefix.length), lastTimestamp: c.lastTimestamp, preview: c.preview })),
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
@@ -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)
@@ -932,6 +998,9 @@ components:
932
998
  oneOf:
933
999
  - type: string
934
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.
935
1004
  ConversationSummary:
936
1005
  type: object
937
1006
  required: [conversationId, lastTimestamp, preview]
@@ -943,6 +1012,9 @@ components:
943
1012
  preview:
944
1013
  type: string
945
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.
946
1018
  ConversationList:
947
1019
  type: object
948
1020
  required: [ok, conversations]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercury-fw/channel-http",
3
- "version": "0.15.1",
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.68.0 <1.0.0"
21
+ "@mercury-fw/channel-types": ">=0.70.0 <1.0.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@mercury-fw/channel-types": "0.69.0",
25
- "@mercury-fw/plugin-types": "0.69.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"