@mercury-fw/channel-http 0.11.0 → 0.12.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 +17 -0
- package/README.md +8 -2
- package/dist/http-provider.d.ts +3 -1
- package/dist/http-server.d.ts +9 -4
- package/http-provider.ts +4 -1
- package/http-server.ts +45 -11
- package/index.ts +1 -0
- package/openapi.yaml +65 -5
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @mercury-fw/channel-http
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1126106: Every turn tells the model when and where the person wrote it.
|
|
8
|
+
|
|
9
|
+
- Each message reaches the model after a line with the weekday, date, time and time zone it was written in (`[Wednesday 2026-10-21 14:32 Europe/Rome]`), and the system prompt says what that line is. The conversation's archive and what a UI shows of it keep the message as written.
|
|
10
|
+
- A resumed conversation's messages come back stamped from the archive's timestamps.
|
|
11
|
+
- The Layer 1 summary keeps when things were said; the episodic summary quotes a date only from those lines, never guessing one. The semantic-fact extractor and the capture card's preview leave the line out.
|
|
12
|
+
- The time zone, always an IANA name (`Europe/Rome`), is the one the person saved, else the one their client is in when the channel passes it on, else the new `i18n.timeZone` in `mercury.config.ts`, else the machine's. A `timeZone` that isn't a zone's name stops the start.
|
|
13
|
+
- The person saves theirs by telling the assistant (the new `set_time_zone` tool), or from a channel's settings.
|
|
14
|
+
- Channel contract: `InboundTurn.timeZoneHint`, and `ctx.settings.setTimeZone` for a channel with a settings area; `reads.me` also returns the saved `timeZone`. Both are optional additions: the contract version stays 6.
|
|
15
|
+
- HTTP channel: the `X-Time-Zone` header on `POST /turn` (allowed by CORS), `timeZone` in `GET /me`, and `PATCH /me` with `{ timeZone }` to save it. Needs the framework at 0.56 or later; an older core ignores the header and offers no `PATCH /me`.
|
|
16
|
+
- Google Chat channel: the client's time zone (`common.timeZone`), when the event carries it, on messages and on the turn a click resumes.
|
|
17
|
+
|
|
18
|
+
To set a default for an app's people, add `timeZone: "Europe/Rome"` (their zone) to `i18n` in `mercury.config.ts`; without it the machine's zone is used, often UTC inside a container.
|
|
19
|
+
|
|
3
20
|
## 0.11.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Gives a [Mercury](https://github.com/lucabro81/mercury-fw) agent an HTTP surface to build a custom UI on: one conversational turn per request, streamed back as Server-Sent Events, plus confirmation and read-only routes over what the agent holds (conversations, the wiki, memory, its plugins).
|
|
4
4
|
|
|
5
|
-
This version is written for version 6 of the channel contract, the one that brings the channels' texts in the person's language, and needs the framework at 0.54 or later: a core with an older contract skips it, and the log says why.
|
|
5
|
+
This version is written for version 6 of the channel contract, the one that brings the channels' texts in the person's language, and needs the framework at 0.54 or later: a core with an older contract skips it, and the log says why. `X-Time-Zone` and `PATCH /me` need the framework at 0.56 or later: an older core ignores the header and offers no `PATCH /me`.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
bun add @mercury-fw/channel-http
|
|
@@ -56,6 +56,8 @@ Runs one conversational turn; the reply streams back as Server-Sent Events.
|
|
|
56
56
|
|
|
57
57
|
**Languages.** Every text a person reads that isn't the model's (a refusal, a tool's status line, a choice's label, a confirmation's reply, the login page) is in their language: the one they asked the assistant for, else the one the request's `Accept-Language` header asks for (what browsers send on their own), else the app's default. The responses of `POST /turn` and `POST /confirm`, a refusal and the login page say which with `Content-Language` (as it stood when the response started: a language picked during a turn counts from the next one).
|
|
58
58
|
|
|
59
|
+
**Time zone.** The model reads each message with the time the person wrote it, in their time zone: the one they saved, else the one the request's `X-Time-Zone` header names (an IANA name such as `Europe/Rome`, what `Intl.DateTimeFormat().resolvedOptions().timeZone` gives in a browser), else the app's. A UI saves the person's with `PATCH /me`.
|
|
60
|
+
|
|
59
61
|
Reasoning and answer text arrive as **incremental deltas** — never one finished block — so a UI renders them live; treat the `final` event as the authoritative text. **To cancel** a turn (e.g. a generation that's diverging), close the connection: Mercury aborts the in-flight generation.
|
|
60
62
|
|
|
61
63
|
**SSE events** — each is `event: <name>` followed by `data: <json>`:
|
|
@@ -89,6 +91,10 @@ Whoever opens the link and accepts decides which account gets linked: a link bel
|
|
|
89
91
|
|
|
90
92
|
A code for linking another of the caller's accounts to them, for the UI to show: `{ ok: true, code, expiresAt }`, or `400` with the reason when there's none. The person sends the code from the other account, on any channel, twice: the first time Mercury says whom the account would be linked to, the second time it links it, and from then on both accounts are one person (one private area, the same roles). The code lasts ten minutes, works once, and only the caller sees it; the model never does. It belongs to the first account that sends it, so it should be sent in a one-to-one conversation, never in a shared space where someone else could send it first. Over HTTP only an account the instance already admits can send it (anyone else is refused before the turn), so an account a closed instance doesn't know links from a chat.
|
|
91
93
|
|
|
94
|
+
## `PATCH /me`
|
|
95
|
+
|
|
96
|
+
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.
|
|
97
|
+
|
|
92
98
|
## `POST /confirm`
|
|
93
99
|
|
|
94
100
|
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.
|
|
@@ -103,7 +109,7 @@ All `GET`, all JSON, all reporting state already held in-process. They need an a
|
|
|
103
109
|
| `GET /conversations?limit=<n>` | `{ conversations: [{ conversationId, lastTimestamp, preview }] }` — your conversations, most-recently-active first |
|
|
104
110
|
| `GET /manifest` | `{ manifest: { coreApiVersion, plugins: [{ name, apiVersion, active, skills, hasBuild, customStatus }], activeClis, skills } }` |
|
|
105
111
|
| `GET /confirmations` | `{ pending: [{ sessionKey, summary, expiresAt }] }` — your pending confirmations; tokens are deliberately never included |
|
|
106
|
-
| `GET /me` | `{ name, language }` — how you asked the assistant to call you
|
|
112
|
+
| `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 |
|
|
107
113
|
| `GET /tool-log` | `{ entries: [...] }` — the tool calls of your recent turns |
|
|
108
114
|
| `GET /health` | `{ uptimeSeconds, memory, qdrantReachable, ollamaReachable }` |
|
|
109
115
|
| `GET /wiki/list` | `{ files: [...] }` — `curated/...` and `personal/...` paths |
|
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 } from "@mercury-fw/channel-types";
|
|
14
|
+
import type { Authenticate, Provider, ChannelHostReads, ChannelI18n, ChannelLinking, ChannelSettings } from "@mercury-fw/channel-types";
|
|
15
15
|
export type HttpProviderDeps = {
|
|
16
16
|
port: number;
|
|
17
17
|
/** Bare-token interception before the model. */
|
|
@@ -34,6 +34,8 @@ export type HttpProviderDeps = {
|
|
|
34
34
|
};
|
|
35
35
|
/** Linking another account to the caller (`POST /link`). */
|
|
36
36
|
linking?: ChannelLinking;
|
|
37
|
+
/** The person's own settings (`PATCH /me`). */
|
|
38
|
+
settings?: ChannelSettings;
|
|
37
39
|
/** The surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
|
|
38
40
|
resumeMaxChars?: number;
|
|
39
41
|
/** 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 } 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 } 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,9 +124,11 @@ 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. */
|
|
127
|
+
/** A single read route: its `GET` handler plus the shared `OPTIONS` preflight, and on `/me` the `PATCH` saving a setting. */
|
|
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`). */
|
|
131
|
+
PATCH?: (req: Request) => Promise<Response>;
|
|
130
132
|
OPTIONS: () => Response;
|
|
131
133
|
};
|
|
132
134
|
/**
|
|
@@ -138,9 +140,10 @@ type ReadRoute = {
|
|
|
138
140
|
* their data only; a conversation is named by its conversationId, as on
|
|
139
141
|
* `/turn`, and its session key is built from the caller.
|
|
140
142
|
* `json` JSON-encodes with the CORS headers merged in; `badRequest` does the
|
|
141
|
-
* same for a 400.
|
|
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.
|
|
142
145
|
*/
|
|
143
|
-
export declare function readRoutes(reads: ChannelHostReads, authenticate: Authenticate, corsOrigin?: string, admit?: AdmitFn): Record<string, ReadRoute>;
|
|
146
|
+
export declare function readRoutes(reads: ChannelHostReads, authenticate: Authenticate, corsOrigin?: string, admit?: AdmitFn, settings?: ChannelSettings): Record<string, ReadRoute>;
|
|
144
147
|
export type HttpServerDeps = TurnRequestDeps & {
|
|
145
148
|
port: number;
|
|
146
149
|
reads?: ChannelHostReads;
|
|
@@ -150,6 +153,8 @@ export type HttpServerDeps = TurnRequestDeps & {
|
|
|
150
153
|
completeLogin?: CompleteLoginFn;
|
|
151
154
|
/** Linking another account to the caller; `/link` is mounted only with it. */
|
|
152
155
|
linking?: ChannelLinking;
|
|
156
|
+
/** The person's own settings; `PATCH /me` is mounted only with it. */
|
|
157
|
+
settings?: ChannelSettings;
|
|
153
158
|
/** What the pages people read call the assistant. */
|
|
154
159
|
assistantName?: string;
|
|
155
160
|
};
|
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 } from "@mercury-fw/channel-types";
|
|
14
|
+
import type { Authenticate, Provider, HandleTurn, ChannelHostReads, ChannelI18n, ChannelLinking, ChannelSettings } from "@mercury-fw/channel-types";
|
|
15
15
|
|
|
16
16
|
export type HttpProviderDeps = {
|
|
17
17
|
port: number;
|
|
@@ -31,6 +31,8 @@ export type HttpProviderDeps = {
|
|
|
31
31
|
logins?: { callbackUrl: string; accept: (callbackUrl: string) => void; complete: CompleteLoginFn };
|
|
32
32
|
/** Linking another account to the caller (`POST /link`). */
|
|
33
33
|
linking?: ChannelLinking;
|
|
34
|
+
/** The person's own settings (`PATCH /me`). */
|
|
35
|
+
settings?: ChannelSettings;
|
|
34
36
|
/** The surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
|
|
35
37
|
resumeMaxChars?: number;
|
|
36
38
|
/** What the pages people read call the assistant. */
|
|
@@ -54,6 +56,7 @@ export function createHttpProvider(deps: HttpProviderDeps): Provider & { stop():
|
|
|
54
56
|
corsOrigin: deps.corsOrigin,
|
|
55
57
|
completeLogin: deps.logins?.complete,
|
|
56
58
|
...(deps.linking === undefined ? {} : { linking: deps.linking }),
|
|
59
|
+
...(deps.settings === undefined ? {} : { settings: deps.settings }),
|
|
57
60
|
...(deps.resumeMaxChars === undefined ? {} : { resumeMaxChars: deps.resumeMaxChars }),
|
|
58
61
|
...(deps.assistantName === undefined ? {} : { assistantName: deps.assistantName }),
|
|
59
62
|
...(deps.i18n === undefined ? {} : { i18n: deps.i18n }),
|
package/http-server.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type Principal,
|
|
36
36
|
type Admission,
|
|
37
37
|
type ChannelLinking,
|
|
38
|
+
type ChannelSettings,
|
|
38
39
|
} from "@mercury-fw/channel-types";
|
|
39
40
|
import { englishHttp } from "./messages.ts";
|
|
40
41
|
|
|
@@ -65,6 +66,12 @@ function languageHint(req: Request): string | undefined {
|
|
|
65
66
|
return value ? value : undefined;
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/** The time zone the caller's client says it's in (`X-Time-Zone`, an IANA name), when it says; the core ignores a name that isn't a zone. */
|
|
70
|
+
function timeZoneHint(req: Request): string | undefined {
|
|
71
|
+
const value = req.headers.get("x-time-zone")?.trim();
|
|
72
|
+
return value ? value : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
/** `Content-Language` for a response to `principal` asking for `hint`, when the core says which language its texts are in. */
|
|
69
76
|
async function contentLanguage(i18n: ChannelI18n | undefined, principal: Principal | null, hint: string | undefined): Promise<Record<string, string>> {
|
|
70
77
|
return i18n ? { "content-language": (await i18n.forRequest(principal, hint)).locale } : {};
|
|
@@ -104,8 +111,8 @@ const SSE_HEADERS = {
|
|
|
104
111
|
function corsHeaders(origin: string): Record<string, string> {
|
|
105
112
|
return {
|
|
106
113
|
"access-control-allow-origin": origin,
|
|
107
|
-
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
108
|
-
"access-control-allow-headers": "content-type, authorization, accept-language",
|
|
114
|
+
"access-control-allow-methods": "GET, POST, PATCH, OPTIONS",
|
|
115
|
+
"access-control-allow-headers": "content-type, authorization, accept-language, x-time-zone",
|
|
109
116
|
};
|
|
110
117
|
}
|
|
111
118
|
|
|
@@ -207,6 +214,7 @@ export async function handleTurnRequest(req: Request, deps: TurnRequestDeps): Pr
|
|
|
207
214
|
}
|
|
208
215
|
const sessionKey = `${principal.id}:${conversationId}`;
|
|
209
216
|
const hint = languageHint(req);
|
|
217
|
+
const zone = timeZoneHint(req);
|
|
210
218
|
const language = await contentLanguage(deps.i18n, principal, hint);
|
|
211
219
|
|
|
212
220
|
// Aborted when the client disconnects (see the stream's `cancel` below) so
|
|
@@ -280,6 +288,7 @@ export async function handleTurnRequest(req: Request, deps: TurnRequestDeps): Pr
|
|
|
280
288
|
...(opening ? { opening: true as const } : {}),
|
|
281
289
|
...(resuming ? { resumeAfterConfirm: true as const } : {}),
|
|
282
290
|
...(hint === undefined ? {} : { languageHint: hint }),
|
|
291
|
+
...(zone === undefined ? {} : { timeZoneHint: zone }),
|
|
283
292
|
},
|
|
284
293
|
sink,
|
|
285
294
|
);
|
|
@@ -449,9 +458,11 @@ function loginRoutes(
|
|
|
449
458
|
};
|
|
450
459
|
}
|
|
451
460
|
|
|
452
|
-
/** A single read route: its `GET` handler plus the shared `OPTIONS` preflight. */
|
|
461
|
+
/** A single read route: its `GET` handler plus the shared `OPTIONS` preflight, and on `/me` the `PATCH` saving a setting. */
|
|
453
462
|
type ReadRoute = {
|
|
454
463
|
GET: (req: Request) => Response | Promise<Response>;
|
|
464
|
+
/** Only on `/me`, when the core offers the person's settings (`PATCH /me`). */
|
|
465
|
+
PATCH?: (req: Request) => Promise<Response>;
|
|
455
466
|
OPTIONS: () => Response;
|
|
456
467
|
};
|
|
457
468
|
|
|
@@ -464,13 +475,15 @@ type ReadRoute = {
|
|
|
464
475
|
* their data only; a conversation is named by its conversationId, as on
|
|
465
476
|
* `/turn`, and its session key is built from the caller.
|
|
466
477
|
* `json` JSON-encodes with the CORS headers merged in; `badRequest` does the
|
|
467
|
-
* same for a 400.
|
|
478
|
+
* same for a 400. With `settings`, `/me` also takes a `PATCH` saving the
|
|
479
|
+
* caller's time zone (a UI's settings area), asking `authenticate` first too.
|
|
468
480
|
*/
|
|
469
481
|
export function readRoutes(
|
|
470
482
|
reads: ChannelHostReads,
|
|
471
483
|
authenticate: Authenticate,
|
|
472
484
|
corsOrigin = "*",
|
|
473
485
|
admit?: AdmitFn,
|
|
486
|
+
settings?: ChannelSettings,
|
|
474
487
|
): Record<string, ReadRoute> {
|
|
475
488
|
const cors = corsHeaders(corsOrigin);
|
|
476
489
|
const json = (payload: object, status = 200): Response =>
|
|
@@ -515,12 +528,31 @@ export function readRoutes(
|
|
|
515
528
|
...(reads.me === undefined
|
|
516
529
|
? {}
|
|
517
530
|
: {
|
|
518
|
-
"/me":
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
531
|
+
"/me": {
|
|
532
|
+
...route(async (_req, principal) => {
|
|
533
|
+
// Null when the core no longer admits the caller: still a name, null.
|
|
534
|
+
const me = (await reads.me!(principal)) as { name: string | null; language?: string | null; timeZone?: string | null } | null;
|
|
535
|
+
// An older core says no language or time zone: the reply then has none either.
|
|
536
|
+
return json({
|
|
537
|
+
ok: true,
|
|
538
|
+
name: me?.name ?? null,
|
|
539
|
+
...(me?.language === undefined ? {} : { language: me.language }),
|
|
540
|
+
...(me?.timeZone === undefined ? {} : { timeZone: me.timeZone }),
|
|
541
|
+
});
|
|
542
|
+
}),
|
|
543
|
+
...(settings === undefined
|
|
544
|
+
? {}
|
|
545
|
+
: {
|
|
546
|
+
PATCH: async (req: Request) => {
|
|
547
|
+
const principal = await caller(req, { authenticate, ...(admit === undefined ? {} : { admit }) }, corsOrigin);
|
|
548
|
+
if (principal instanceof Response) return principal;
|
|
549
|
+
const body = (await req.json().catch(() => null)) as { timeZone?: unknown } | null;
|
|
550
|
+
if (typeof body?.timeZone !== "string") return badRequest('the body must be {"timeZone": "<IANA name>"}');
|
|
551
|
+
const saved = await settings.setTimeZone(principal, body.timeZone);
|
|
552
|
+
return saved.ok ? json({ ok: true, timeZone: saved.value }) : badRequest(saved.error);
|
|
553
|
+
},
|
|
554
|
+
}),
|
|
555
|
+
},
|
|
524
556
|
}),
|
|
525
557
|
"/health": route(async () => json({ ok: true, ...(await reads.health() as object) })),
|
|
526
558
|
"/wiki/list": route(async (_req, principal) => json({ ok: true, files: await reads.wikiList(principal) })),
|
|
@@ -556,6 +588,8 @@ export type HttpServerDeps = TurnRequestDeps & {
|
|
|
556
588
|
completeLogin?: CompleteLoginFn;
|
|
557
589
|
/** Linking another account to the caller; `/link` is mounted only with it. */
|
|
558
590
|
linking?: ChannelLinking;
|
|
591
|
+
/** The person's own settings; `PATCH /me` is mounted only with it. */
|
|
592
|
+
settings?: ChannelSettings;
|
|
559
593
|
/** What the pages people read call the assistant. */
|
|
560
594
|
assistantName?: string;
|
|
561
595
|
};
|
|
@@ -589,7 +623,7 @@ export function startHttpServer(deps: HttpServerDeps): ReturnType<typeof Bun.ser
|
|
|
589
623
|
OPTIONS: () => preflight(origin),
|
|
590
624
|
},
|
|
591
625
|
"/openapi.yaml": { GET: () => openApiResponse(origin), OPTIONS: () => preflight(origin) },
|
|
592
|
-
...(deps.reads ? readRoutes(deps.reads, deps.authenticate, origin, deps.admit) : {}),
|
|
626
|
+
...(deps.reads ? readRoutes(deps.reads, deps.authenticate, origin, deps.admit, deps.settings) : {}),
|
|
593
627
|
...(deps.completeLogin ? loginRoutes(deps.completeLogin, deps.assistantName, deps.i18n) : {}),
|
|
594
628
|
...(deps.linking ? linkRoutes({ ...deps, start: deps.linking.start, corsOrigin: origin }) : {}),
|
|
595
629
|
},
|
package/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ export const httpChannel: ChannelPlugin = {
|
|
|
48
48
|
admit: ctx.admit,
|
|
49
49
|
i18n: ctx.i18n,
|
|
50
50
|
...(ctx.linking === undefined ? {} : { linking: ctx.linking }),
|
|
51
|
+
...(ctx.settings === undefined ? {} : { settings: ctx.settings }),
|
|
51
52
|
...(resumeMaxChars === undefined ? {} : { resumeMaxChars }),
|
|
52
53
|
...(ctx.assistantName === undefined ? {} : { assistantName: ctx.assistantName }),
|
|
53
54
|
...(logins ? { logins: { callbackUrl: `${publicUrl}/login/callback`, accept: logins.accept, complete: logins.complete } } : {}),
|
package/openapi.yaml
CHANGED
|
@@ -104,9 +104,12 @@ paths:
|
|
|
104
104
|
an empty text. Every text the person reads that isn't the model's (a
|
|
105
105
|
refusal, a status line, a confirmation's reply) is in their language:
|
|
106
106
|
the one they saved, else the one `Accept-Language` asks for, else the
|
|
107
|
-
instance's default.
|
|
107
|
+
instance's default. The model reads the person's message after when
|
|
108
|
+
and where they wrote it: in the time zone they saved, else the one
|
|
109
|
+
`X-Time-Zone` says, else the instance's.
|
|
108
110
|
parameters:
|
|
109
111
|
- $ref: "#/components/parameters/AcceptLanguage"
|
|
112
|
+
- $ref: "#/components/parameters/TimeZone"
|
|
110
113
|
requestBody:
|
|
111
114
|
required: true
|
|
112
115
|
content:
|
|
@@ -344,9 +347,9 @@ paths:
|
|
|
344
347
|
description: >
|
|
345
348
|
How the caller asked to be called, `null` when they never said: a UI
|
|
346
349
|
asks for an opening turn after a sign-in only then. Also the language
|
|
347
|
-
they asked the assistant to use
|
|
348
|
-
when
|
|
349
|
-
which can't say.
|
|
350
|
+
they asked the assistant to use and the time zone they saved, `null`
|
|
351
|
+
when they never said (missing on a core that doesn't know them). Not
|
|
352
|
+
there on an older core, which can't say.
|
|
350
353
|
responses:
|
|
351
354
|
"401":
|
|
352
355
|
$ref: "#/components/responses/Unauthorized"
|
|
@@ -355,7 +358,7 @@ paths:
|
|
|
355
358
|
"503":
|
|
356
359
|
$ref: "#/components/responses/NotAdmitted"
|
|
357
360
|
"200":
|
|
358
|
-
description: The caller's name and
|
|
361
|
+
description: The caller's name, language and time zone, or null.
|
|
359
362
|
content:
|
|
360
363
|
application/json:
|
|
361
364
|
schema:
|
|
@@ -369,6 +372,52 @@ paths:
|
|
|
369
372
|
language:
|
|
370
373
|
type: [string, "null"]
|
|
371
374
|
description: A language tag such as `it` or `pt-br`, one the instance ships.
|
|
375
|
+
timeZone:
|
|
376
|
+
type: [string, "null"]
|
|
377
|
+
description: The time zone's IANA name, such as `Europe/Rome`.
|
|
378
|
+
patch:
|
|
379
|
+
summary: Saves the caller's time zone
|
|
380
|
+
description: >
|
|
381
|
+
The time zone the caller is in, by its IANA name, for a UI's settings:
|
|
382
|
+
from then on their turns are stamped with the time there, whatever
|
|
383
|
+
`X-Time-Zone` says. Saved as the IANA list writes it (`europe/rome` is
|
|
384
|
+
`Europe/Rome`). Not there on a core without settings.
|
|
385
|
+
requestBody:
|
|
386
|
+
required: true
|
|
387
|
+
content:
|
|
388
|
+
application/json:
|
|
389
|
+
schema:
|
|
390
|
+
type: object
|
|
391
|
+
required: [timeZone]
|
|
392
|
+
properties:
|
|
393
|
+
timeZone:
|
|
394
|
+
type: string
|
|
395
|
+
description: An IANA name such as `Europe/Rome`; a fixed offset (`+02:00`) isn't one.
|
|
396
|
+
responses:
|
|
397
|
+
"400":
|
|
398
|
+
description: The body has no `timeZone`, or it isn't a zone's name.
|
|
399
|
+
content:
|
|
400
|
+
application/json:
|
|
401
|
+
schema:
|
|
402
|
+
$ref: "#/components/schemas/Error"
|
|
403
|
+
"401":
|
|
404
|
+
$ref: "#/components/responses/Unauthorized"
|
|
405
|
+
"403":
|
|
406
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
407
|
+
"503":
|
|
408
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
409
|
+
"200":
|
|
410
|
+
description: The time zone as saved.
|
|
411
|
+
content:
|
|
412
|
+
application/json:
|
|
413
|
+
schema:
|
|
414
|
+
type: object
|
|
415
|
+
required: [ok, timeZone]
|
|
416
|
+
properties:
|
|
417
|
+
ok:
|
|
418
|
+
type: boolean
|
|
419
|
+
timeZone:
|
|
420
|
+
type: string
|
|
372
421
|
/tool-log:
|
|
373
422
|
get:
|
|
374
423
|
summary: The tool calls made in the caller's recent turns
|
|
@@ -564,6 +613,17 @@ components:
|
|
|
564
613
|
ship is passed over.
|
|
565
614
|
schema:
|
|
566
615
|
type: string
|
|
616
|
+
TimeZone:
|
|
617
|
+
name: X-Time-Zone
|
|
618
|
+
in: header
|
|
619
|
+
required: false
|
|
620
|
+
description: >
|
|
621
|
+
The time zone the client is in, by its IANA name, as a browser gives
|
|
622
|
+
it (`Intl.DateTimeFormat().resolvedOptions().timeZone`). Used for the
|
|
623
|
+
turn's time when the person saved no time zone of their own; a name
|
|
624
|
+
that isn't a zone is passed over.
|
|
625
|
+
schema:
|
|
626
|
+
type: string
|
|
567
627
|
headers:
|
|
568
628
|
ContentLanguage:
|
|
569
629
|
description: >
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/channel-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"@mercury-fw/channel-types": ">=0.54.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.56.0",
|
|
25
|
+
"@mercury-fw/plugin-types": "0.56.0",
|
|
26
26
|
"@mercury-fw/typescript-config": "*",
|
|
27
27
|
"@types/bun": "^1.4.2",
|
|
28
28
|
"typescript": "^6.0.3"
|