@mercury-fw/channel-http 0.7.0 → 0.8.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 +12 -0
- package/README.md +2 -1
- package/dist/http-provider.d.ts +2 -0
- package/dist/http-server.d.ts +2 -0
- package/http-provider.ts +3 -0
- package/http-server.ts +6 -4
- package/index.ts +10 -0
- package/openapi.yaml +8 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @mercury-fw/channel-http
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 812c9c5: - A conversation reopened on the HTTP surface after Mercury freed its history comes back to the model: the summaries of its older part plus its newest messages from the archive, within a budget.
|
|
8
|
+
- What a resumed conversation brings back isn't captured to long-term memory again, only the new messages are.
|
|
9
|
+
- `SESSION_RESUME_MAX_CHARS` sets how much comes back (default 30000, `0` turns it off); `HTTP_SURFACE_RESUME_MAX_CHARS` sets it for the HTTP surface only.
|
|
10
|
+
- `InboundTurn.resume` lets a channel mark a conversation its client reopens.
|
|
11
|
+
- The episodic collection gets a `sessionKey` index.
|
|
12
|
+
- `mfw create` lists `HTTP_SURFACE_RESUME_MAX_CHARS` for the HTTP channel.
|
|
13
|
+
- Core's README explains how Mercury remembers a conversation.
|
|
14
|
+
|
|
3
15
|
## 0.7.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ It needs an auth provider next to it: [`@mercury-fw/auth-oidc`](../../auth/auth-
|
|
|
21
21
|
| `HTTP_SURFACE_PORT` | The port (default `4100`). |
|
|
22
22
|
| `HTTP_SURFACE_CORS_ORIGIN` | The origin a browser UI calls from (default `*`). |
|
|
23
23
|
| `HTTP_SURFACE_PUBLIC_URL` | The address people's browsers reach the surface at (e.g. `https://mercury.example.com`). With it, people log in here to the services plugins act on as them (see [`GET /login/callback`](#get-logincallback)); without it, nobody can. |
|
|
24
|
+
| `HTTP_SURFACE_RESUME_MAX_CHARS` | How many characters of a reopened conversation come back to the model once Mercury has freed its history (default: the core's `SESSION_RESUME_MAX_CHARS`; `0` turns resuming off on this surface). See [how Mercury remembers a conversation](../../libs/core/README.md#how-mercury-remembers-a-conversation). |
|
|
24
25
|
|
|
25
26
|
## API
|
|
26
27
|
|
|
@@ -45,7 +46,7 @@ Runs one conversational turn; the reply streams back as Server-Sent Events.
|
|
|
45
46
|
| field | type | required | description |
|
|
46
47
|
|---|---|---|---|
|
|
47
48
|
| `text` | string | yes | The user message. A bare confirmation token here confirms a staged action (see the `pending` event) without invoking the model. |
|
|
48
|
-
| `conversationId` | string | no | Opaque, client-owned id that continues one of your conversations: letters, digits, `-` and `_`, up to 128 characters (a UUID fits) once surrounding whitespace is trimmed, `400` otherwise. Omitted ⇒ a fresh one-off session. |
|
|
49
|
+
| `conversationId` | string | no | Opaque, client-owned id that continues one of your conversations: letters, digits, `-` and `_`, up to 128 characters (a UUID fits) once surrounding whitespace is trimmed, `400` otherwise. Sent again, however long after, it continues that conversation, rebuilt from the archive if Mercury had freed it (see `HTTP_SURFACE_RESUME_MAX_CHARS`). Omitted ⇒ a fresh one-off session that can't be continued. |
|
|
49
50
|
|
|
50
51
|
**Responses**: `200 text/event-stream` (the events below); `400` if `text` is missing, the body isn't JSON, or `conversationId` has other characters than the ones above.
|
|
51
52
|
|
package/dist/http-provider.d.ts
CHANGED
|
@@ -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 surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
|
|
38
|
+
resumeMaxChars?: number;
|
|
37
39
|
};
|
|
38
40
|
export declare function createHttpProvider(deps: HttpProviderDeps): Provider & {
|
|
39
41
|
stop(): Promise<void>;
|
package/dist/http-server.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export type TurnRequestDeps = {
|
|
|
40
40
|
corsOrigin?: string;
|
|
41
41
|
/** Test seam for the ephemeral session key when the client sends no conversationId. */
|
|
42
42
|
newSessionKey?: () => string;
|
|
43
|
+
/** How much of a reopened conversation comes back once the core let its history go (`HTTP_SURFACE_RESUME_MAX_CHARS`); absent, the core's own. */
|
|
44
|
+
resumeMaxChars?: number;
|
|
43
45
|
};
|
|
44
46
|
/**
|
|
45
47
|
* Serves the OpenAPI document that describes this surface — the single source
|
package/http-provider.ts
CHANGED
|
@@ -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 surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
|
|
35
|
+
resumeMaxChars?: number;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export function createHttpProvider(deps: HttpProviderDeps): Provider & { stop(): Promise<void> } {
|
|
@@ -48,6 +50,7 @@ export function createHttpProvider(deps: HttpProviderDeps): Provider & { stop():
|
|
|
48
50
|
corsOrigin: deps.corsOrigin,
|
|
49
51
|
completeLogin: deps.logins?.complete,
|
|
50
52
|
...(deps.linking === undefined ? {} : { linking: deps.linking }),
|
|
53
|
+
...(deps.resumeMaxChars === undefined ? {} : { resumeMaxChars: deps.resumeMaxChars }),
|
|
51
54
|
});
|
|
52
55
|
deps.logins?.accept(deps.logins.callbackUrl);
|
|
53
56
|
},
|
package/http-server.ts
CHANGED
|
@@ -69,6 +69,8 @@ export type TurnRequestDeps = {
|
|
|
69
69
|
corsOrigin?: string;
|
|
70
70
|
/** Test seam for the ephemeral session key when the client sends no conversationId. */
|
|
71
71
|
newSessionKey?: () => string;
|
|
72
|
+
/** How much of a reopened conversation comes back once the core let its history go (`HTTP_SURFACE_RESUME_MAX_CHARS`); absent, the core's own. */
|
|
73
|
+
resumeMaxChars?: number;
|
|
72
74
|
};
|
|
73
75
|
|
|
74
76
|
const SSE_HEADERS = {
|
|
@@ -161,10 +163,9 @@ export async function handleTurnRequest(req: Request, deps: TurnRequestDeps): Pr
|
|
|
161
163
|
return Response.json({ ok: false, error: "missing text" }, { status: 400, headers: cors });
|
|
162
164
|
}
|
|
163
165
|
const text = body.text;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
: (deps.newSessionKey ?? (() => crypto.randomUUID()))();
|
|
166
|
+
// A conversation the client names is one it reopens; an ephemeral one never comes back.
|
|
167
|
+
const named = typeof body.conversationId === "string" && body.conversationId.trim().length > 0;
|
|
168
|
+
const conversationId = named ? (body.conversationId as string).trim() : (deps.newSessionKey ?? (() => crypto.randomUUID()))();
|
|
168
169
|
if (!CONVERSATION_ID.test(conversationId)) {
|
|
169
170
|
return Response.json({ ok: false, error: "invalid conversationId" }, { status: 400, headers: cors });
|
|
170
171
|
}
|
|
@@ -234,6 +235,7 @@ export async function handleTurnRequest(req: Request, deps: TurnRequestDeps): Pr
|
|
|
234
235
|
// The caller's id comes from the auth provider, unchecked here.
|
|
235
236
|
logPrefix: `[http:${forLog(sessionKey)}] `,
|
|
236
237
|
abortSignal: abort.signal,
|
|
238
|
+
...(named ? { resume: deps.resumeMaxChars === undefined ? {} : { maxChars: deps.resumeMaxChars } } : {}),
|
|
237
239
|
},
|
|
238
240
|
sink,
|
|
239
241
|
);
|
package/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ export const httpChannel: ChannelPlugin = {
|
|
|
35
35
|
// the address their browser reaches the surface at.
|
|
36
36
|
const publicUrl = ctx.env.HTTP_SURFACE_PUBLIC_URL?.trim().replace(/\/+$/, "");
|
|
37
37
|
const logins = publicUrl ? ctx.logins : undefined;
|
|
38
|
+
const resumeMaxChars = parseResumeMaxChars(ctx.env.HTTP_SURFACE_RESUME_MAX_CHARS);
|
|
38
39
|
return createHttpProvider({
|
|
39
40
|
port: Number(ctx.env.HTTP_SURFACE_PORT ?? "4100"),
|
|
40
41
|
corsOrigin: ctx.env.HTTP_SURFACE_CORS_ORIGIN ?? "*",
|
|
@@ -44,7 +45,16 @@ export const httpChannel: ChannelPlugin = {
|
|
|
44
45
|
authenticate: ctx.authenticate,
|
|
45
46
|
admit: ctx.admit,
|
|
46
47
|
...(ctx.linking === undefined ? {} : { linking: ctx.linking }),
|
|
48
|
+
...(resumeMaxChars === undefined ? {} : { resumeMaxChars }),
|
|
47
49
|
...(logins ? { logins: { callbackUrl: `${publicUrl}/login/callback`, accept: logins.accept, complete: logins.complete } } : {}),
|
|
48
50
|
});
|
|
49
51
|
},
|
|
50
52
|
};
|
|
53
|
+
|
|
54
|
+
/** `HTTP_SURFACE_RESUME_MAX_CHARS`: unset or empty leaves the core's budget, otherwise a whole number from 0 up (0 turns resuming off on this surface); throws on anything else. */
|
|
55
|
+
function parseResumeMaxChars(text: string | undefined): number | undefined {
|
|
56
|
+
const trimmed = text?.trim() ?? "";
|
|
57
|
+
if (trimmed === "") return undefined;
|
|
58
|
+
if (!/^\d+$/.test(trimmed)) throw new Error(`HTTP_SURFACE_RESUME_MAX_CHARS must be a whole number from 0 up, got "${trimmed}"`);
|
|
59
|
+
return Number(trimmed);
|
|
60
|
+
}
|
package/openapi.yaml
CHANGED
|
@@ -49,6 +49,13 @@ info:
|
|
|
49
49
|
by the verbatim archive; they degrade to empty when the vector store is
|
|
50
50
|
unreachable).
|
|
51
51
|
|
|
52
|
+
A conversation never ends: sent again with the same `conversationId`, a
|
|
53
|
+
turn continues it however long after. Mercury frees a conversation's
|
|
54
|
+
in-memory history once it has been idle a while; the next turn rebuilds
|
|
55
|
+
it from the archive (a summary of its older part and its newest messages,
|
|
56
|
+
within `HTTP_SURFACE_RESUME_MAX_CHARS`), so the model still knows what the
|
|
57
|
+
person sees on screen.
|
|
58
|
+
|
|
52
59
|
|
|
53
60
|
## Streaming and cancellation
|
|
54
61
|
|
|
@@ -520,7 +527,7 @@ components:
|
|
|
520
527
|
conversationId:
|
|
521
528
|
type: string
|
|
522
529
|
pattern: "^[A-Za-z0-9_-]{1,128}$"
|
|
523
|
-
description: Opaque, client-owned; letters, digits, `-` and `_` (a UUID fits). Omit to start a fresh ephemeral conversation.
|
|
530
|
+
description: Opaque, client-owned; letters, digits, `-` and `_` (a UUID fits). Omit to start a fresh ephemeral conversation, which can't be continued.
|
|
524
531
|
TurnEvent:
|
|
525
532
|
type: object
|
|
526
533
|
description: The shape carried by each SSE `data:` line, per event type.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/channel-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"@mercury-fw/channel-types": ">=0.36.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.45.0",
|
|
25
|
+
"@mercury-fw/plugin-types": "0.45.0",
|
|
26
26
|
"@mercury-fw/typescript-config": "*",
|
|
27
27
|
"@types/bun": "^1.4.2",
|
|
28
28
|
"typescript": "^6.0.3"
|