@mercury-fw/channel-http 0.8.2 → 0.8.4

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,25 @@
1
1
  # @mercury-fw/channel-http
2
2
 
3
+ ## 0.8.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 62bb3a3: - `persona.name` is the assistant's name, Mercury when left out. A name that's blank, longer than 40 characters or on more than one line stops the app at startup.
8
+ - Every text the model reads refers to the name as `{{assistant}}`, and the core puts the persona's name there: the persona's identity and tone, the system prompt, plugin fragments, skills and tool descriptions. The model no longer takes "Mercury" for its own name when the app calls its assistant something else.
9
+ - Where the one acting is the system around the model, the texts say "the system" or "this instance" instead of Mercury: the wiki tools, the cli-engine's errors, the jira skill and its issue lists.
10
+ - Plugins get `assistantName` in their `build()` context, and channels in their runtime context, for texts they build while running. The login messages, the linking reply and the HTTP login page name the assistant that way.
11
+ - The tools a person runs as the assistant itself are `<tool>AsSelf`, previously `<tool>AsMercury`. The `mercury.act-as-self` roles are unchanged.
12
+ - `mfw create` writes the assistant's name as `persona.name`, with `persona/identity.md` and `persona/tone.md` referring to it. An existing app keeps working as it is: to rename its assistant everywhere, add `name` to its persona and write `{{assistant}}` in its persona files.
13
+
14
+ ## 0.8.3
15
+
16
+ ### Patch Changes
17
+
18
+ - 3d73e59: - `auth` in `mercury.config.ts` takes a list of providers as well as one: they're tried in order and the first that recognizes the caller decides who they are. One that fails to load, or fails on a request, is logged and the others still serve; a name taken twice is refused.
19
+ - `createOidcAuth({ name })` is an OIDC provider of its own (`oidc-<name>`), configured by `OIDC_<NAME>_ISSUER` and `OIDC_<NAME>_AUDIENCE`, so an instance can accept several issuers. `oidcAuth` is unchanged.
20
+ - A principal's provider can be `oidc-<name>`; each provider's people keep their own keys.
21
+ - `zitadelDirectory` takes `ZITADEL_AUTH_PROVIDER`, the auth provider whose subjects are its users (default `oidc`).
22
+
3
23
  ## 0.8.2
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -14,7 +14,7 @@ channels: [httpChannel],
14
14
  auth: oidcAuth,
15
15
  ```
16
16
 
17
- It needs an auth provider next to it: [`@mercury-fw/auth-oidc`](../../auth/auth-oidc) for real users, [`@mercury-fw/auth-static`](../../auth/auth-static) for the test bed and e2e tests. Without one, or with one that fails to load, the channel doesn't start and the log says why.
17
+ It needs an auth provider next to it, or several (`auth: [a, b]`, tried in order): [`@mercury-fw/auth-oidc`](../../auth/auth-oidc) for real users, [`@mercury-fw/auth-static`](../../auth/auth-static) for the test bed and e2e tests. Without one, or when none of them loads, the channel doesn't start and the log says why.
18
18
 
19
19
  | Variable | |
20
20
  |---|---|
@@ -27,7 +27,7 @@ It needs an auth provider next to it: [`@mercury-fw/auth-oidc`](../../auth/auth-
27
27
 
28
28
  It's active whenever it's declared in `mercury.config.ts`'s `channels` with an auth provider; remove it there to turn the surface off. It listens on `HTTP_SURFACE_PORT` (default `4100`). Base URL `http://<host>:<port>`.
29
29
 
30
- **Authentication.** Every route except `GET /openapi.yaml`, `GET /login/callback` and the `OPTIONS` preflights needs `Authorization: Bearer <token>`, and the auth provider decides who the token belongs to. A missing or refused token gets `401` with `WWW-Authenticate: Bearer` before anything runs. Then the core decides whether it talks to that person at all: someone the app's directory doesn't know gets `403` (unless the app lets unknown people in), and `503` when the directory can't be reached, both with `{ ok: false, error, reason }`, where `error` is what to tell them.
30
+ **Authentication.** Every route except `GET /openapi.yaml`, `GET /login/callback` and the `OPTIONS` preflights needs `Authorization: Bearer <token>`, and the auth providers decide who the token belongs to: the first that recognizes it. A missing or refused token gets `401` with `WWW-Authenticate: Bearer` before anything runs. Then the core decides whether it talks to that person at all: someone the app's directory doesn't know gets `403` (unless the app lets unknown people in), and `503` when the directory can't be reached, both with `{ ok: false, error, reason }`, where `error` is what to tell them.
31
31
 
32
32
  **Conversations belong to whoever opened them.** The session key is `<caller id>:<conversationId>`, so the same `conversationId` sent by someone else is a conversation of their own, and a confirmation token staged in your conversation can't be confirmed from theirs. Each authenticated caller also gets their own episodic memory and wiki area, as on Google Chat.
33
33
 
@@ -36,6 +36,8 @@ export type HttpProviderDeps = {
36
36
  linking?: ChannelLinking;
37
37
  /** The surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
38
38
  resumeMaxChars?: number;
39
+ /** What the pages people read call the assistant. */
40
+ assistantName?: string;
39
41
  };
40
42
  export declare function createHttpProvider(deps: HttpProviderDeps): Provider & {
41
43
  stop(): Promise<void>;
@@ -102,10 +102,12 @@ export type CompleteLoginFn = (state: string, code: string) => Promise<LoginOutc
102
102
  * in, with `code` and `state` (or `error`, when they refused). Public: the
103
103
  * person's browser carries no token here, and the single-use `state` the core
104
104
  * issued for them is what ties the request to their login. Answers with a
105
- * plain page telling them how it went.
105
+ * plain page telling them how it went, naming the assistant as the app does
106
+ * (`Mercury` from an older core that doesn't say).
106
107
  */
107
108
  export declare function handleLoginCallback(req: Request, deps: {
108
109
  complete: CompleteLoginFn;
110
+ assistantName?: string;
109
111
  }): Promise<Response>;
110
112
  /** A single read route: its `GET` handler plus the shared `OPTIONS` preflight. */
111
113
  type ReadRoute = {
@@ -133,6 +135,8 @@ export type HttpServerDeps = TurnRequestDeps & {
133
135
  completeLogin?: CompleteLoginFn;
134
136
  /** Linking another account to the caller; `/link` is mounted only with it. */
135
137
  linking?: ChannelLinking;
138
+ /** What the pages people read call the assistant. */
139
+ assistantName?: string;
136
140
  };
137
141
  /** Starts the HTTP surface: `POST /turn` (4a) plus the read-only routes (4b)
138
142
  * when `reads` is supplied. */
package/http-provider.ts CHANGED
@@ -33,6 +33,8 @@ export type HttpProviderDeps = {
33
33
  linking?: ChannelLinking;
34
34
  /** The surface's budget for a reopened conversation (`HTTP_SURFACE_RESUME_MAX_CHARS`). */
35
35
  resumeMaxChars?: number;
36
+ /** What the pages people read call the assistant. */
37
+ assistantName?: string;
36
38
  };
37
39
 
38
40
  export function createHttpProvider(deps: HttpProviderDeps): Provider & { stop(): Promise<void> } {
@@ -51,6 +53,7 @@ export function createHttpProvider(deps: HttpProviderDeps): Provider & { stop():
51
53
  completeLogin: deps.logins?.complete,
52
54
  ...(deps.linking === undefined ? {} : { linking: deps.linking }),
53
55
  ...(deps.resumeMaxChars === undefined ? {} : { resumeMaxChars: deps.resumeMaxChars }),
56
+ ...(deps.assistantName === undefined ? {} : { assistantName: deps.assistantName }),
54
57
  });
55
58
  deps.logins?.accept(deps.logins.callbackUrl);
56
59
  },
package/http-server.ts CHANGED
@@ -352,18 +352,23 @@ function loginPage(text: string, status: number): Response {
352
352
  * in, with `code` and `state` (or `error`, when they refused). Public: the
353
353
  * person's browser carries no token here, and the single-use `state` the core
354
354
  * issued for them is what ties the request to their login. Answers with a
355
- * plain page telling them how it went.
355
+ * plain page telling them how it went, naming the assistant as the app does
356
+ * (`Mercury` from an older core that doesn't say).
356
357
  */
357
- export async function handleLoginCallback(req: Request, deps: { complete: CompleteLoginFn }): Promise<Response> {
358
+ export async function handleLoginCallback(
359
+ req: Request,
360
+ deps: { complete: CompleteLoginFn; assistantName?: string },
361
+ ): Promise<Response> {
362
+ const assistant = deps.assistantName ?? "Mercury";
358
363
  const params = new URL(req.url).searchParams;
359
364
  const error = params.get("error");
360
365
  if (error !== null) {
361
366
  const reason = error.replace(/[^A-Za-z0-9_.-]/g, "").slice(0, 64);
362
- return loginPage(`The login didn't happen (${reason}). Ask Mercury again for a new link.`, 400);
367
+ return loginPage(`The login didn't happen (${reason}). Ask ${assistant} again for a new link.`, 400);
363
368
  }
364
369
  const code = params.get("code");
365
370
  const state = params.get("state");
366
- if (!code || !state) return loginPage("The login link came back without a code or a state. Ask Mercury again for a new link.", 400);
371
+ if (!code || !state) return loginPage(`The login link came back without a code or a state. Ask ${assistant} again for a new link.`, 400);
367
372
  const outcome = await deps.complete(state, code);
368
373
  return outcome.ok
369
374
  ? loginPage(`You're logged in to ${outcome.service}. Go back to the conversation and ask again.`, 200)
@@ -371,8 +376,15 @@ export async function handleLoginCallback(req: Request, deps: { complete: Comple
371
376
  }
372
377
 
373
378
  /** The login callback route, mounted when the server can complete logins. */
374
- function loginRoutes(complete: CompleteLoginFn): Record<string, { GET: (req: Request) => Promise<Response> }> {
375
- return { "/login/callback": { GET: (req) => handleLoginCallback(req, { complete }) } };
379
+ function loginRoutes(
380
+ complete: CompleteLoginFn,
381
+ assistantName: string | undefined,
382
+ ): Record<string, { GET: (req: Request) => Promise<Response> }> {
383
+ return {
384
+ "/login/callback": {
385
+ GET: (req) => handleLoginCallback(req, { complete, ...(assistantName === undefined ? {} : { assistantName }) }),
386
+ },
387
+ };
376
388
  }
377
389
 
378
390
  /** A single read route: its `GET` handler plus the shared `OPTIONS` preflight. */
@@ -471,6 +483,8 @@ export type HttpServerDeps = TurnRequestDeps & {
471
483
  completeLogin?: CompleteLoginFn;
472
484
  /** Linking another account to the caller; `/link` is mounted only with it. */
473
485
  linking?: ChannelLinking;
486
+ /** What the pages people read call the assistant. */
487
+ assistantName?: string;
474
488
  };
475
489
 
476
490
  /** Starts the HTTP surface: `POST /turn` (4a) plus the read-only routes (4b)
@@ -502,7 +516,7 @@ export function startHttpServer(deps: HttpServerDeps): ReturnType<typeof Bun.ser
502
516
  },
503
517
  "/openapi.yaml": { GET: () => openApiResponse(origin), OPTIONS: () => preflight(origin) },
504
518
  ...(deps.reads ? readRoutes(deps.reads, deps.authenticate, origin, deps.admit) : {}),
505
- ...(deps.completeLogin ? loginRoutes(deps.completeLogin) : {}),
519
+ ...(deps.completeLogin ? loginRoutes(deps.completeLogin, deps.assistantName) : {}),
506
520
  ...(deps.linking ? linkRoutes({ ...deps, start: deps.linking.start, corsOrigin: origin }) : {}),
507
521
  },
508
522
  error: (err) =>
package/index.ts CHANGED
@@ -46,6 +46,7 @@ export const httpChannel: ChannelPlugin = {
46
46
  admit: ctx.admit,
47
47
  ...(ctx.linking === undefined ? {} : { linking: ctx.linking }),
48
48
  ...(resumeMaxChars === undefined ? {} : { resumeMaxChars }),
49
+ ...(ctx.assistantName === undefined ? {} : { assistantName: ctx.assistantName }),
49
50
  ...(logins ? { logins: { callbackUrl: `${publicUrl}/login/callback`, accept: logins.accept, complete: logins.complete } } : {}),
50
51
  });
51
52
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercury-fw/channel-http",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,8 +21,8 @@
21
21
  "@mercury-fw/channel-types": ">=0.45.0 <1.0.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@mercury-fw/channel-types": "0.46.1",
25
- "@mercury-fw/plugin-types": "0.46.1",
24
+ "@mercury-fw/channel-types": "0.48.0",
25
+ "@mercury-fw/plugin-types": "0.48.0",
26
26
  "@mercury-fw/typescript-config": "*",
27
27
  "@types/bun": "^1.4.2",
28
28
  "typescript": "^6.0.3"