@mercury-fw/channel-http 0.5.0 → 0.6.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 +22 -0
- package/README.md +2 -2
- package/dist/http-provider.d.ts +3 -1
- package/dist/http-server.d.ts +6 -2
- package/http-provider.ts +4 -1
- package/http-server.ts +44 -12
- package/index.ts +2 -1
- package/openapi.yaml +77 -3
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @mercury-fw/channel-http
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a7f2f13: A user directory decides who people are and what they may make Mercury do.
|
|
8
|
+
|
|
9
|
+
- An app can declare a `directory` in `mercury.config.ts`. Given whoever a channel says is talking, it returns the person with their roles. The person's key is `<directory name>:<id>`, so one person reached from several channels keeps one private area, one memory and one set of confirmations.
|
|
10
|
+
- With a directory the instance is closed. Someone it doesn't know gets a refusal and nothing runs, unless `access.unknown` is `"allow"`; `access.unknownMessage` sets what they're told. A directory that can't be reached, or fails to load, refuses everyone but the terminal. Answers are cached for five minutes, so a revoked role stops counting within that time.
|
|
11
|
+
- `mercury.act-as-self` (every plugin) or `mercury.act-as-self.<plugin>` (one) lets a person make Mercury act with its own identity.
|
|
12
|
+
- A plugin acting as Mercury, such as atlassian-admin, is offered only to those people, and on the terminal.
|
|
13
|
+
- A plugin acting as the person also gives them each tool a second time, as `<tool>AsMercury`.
|
|
14
|
+
- Every call made as Mercury for someone is logged with who asked.
|
|
15
|
+
- `access.plugins` restricts a plugin to some roles. Prompts, skills and the manifest follow what each person is offered.
|
|
16
|
+
- New package `@mercury-fw/directory-static`: people, their identities on each channel and their roles, listed in `DIRECTORY_STATIC_PEOPLE`.
|
|
17
|
+
- The channel contract is at version 5:
|
|
18
|
+
- `ChannelRuntimeContext.admit` says whether the core talks to a caller;
|
|
19
|
+
- the reads are per person and async, the manifest included;
|
|
20
|
+
- `DirectoryPlugin` is the directory contract;
|
|
21
|
+
- `Principal` no longer carries `roles`.
|
|
22
|
+
- `channel-http` answers `403` to a caller the directory doesn't know and `503` when it can't be reached, on every route.
|
|
23
|
+
- `auth-static` refuses `roles` in `AUTH_STATIC_TOKENS`: roles come from the directory.
|
|
24
|
+
|
|
3
25
|
## 0.5.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ It needs an auth provider next to it: [`@mercury-fw/auth-oidc`](../../auth/auth-
|
|
|
26
26
|
|
|
27
27
|
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>`.
|
|
28
28
|
|
|
29
|
-
**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.
|
|
29
|
+
**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
30
|
|
|
31
31
|
**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.
|
|
32
32
|
|
|
@@ -84,7 +84,7 @@ Explicit alternative to re-sending a token as `/turn` `text`. Body `{ token, con
|
|
|
84
84
|
|
|
85
85
|
## Read-only introspection
|
|
86
86
|
|
|
87
|
-
All `GET`, all JSON, all reporting state already held in-process. They need an authenticated caller and show only what belongs to them: their conversations, confirmations, tool calls and memory, and the wiki as they see it (the team's `curated/` plus their own notes as `personal/`). Someone else's data doesn't exist as far as a caller can tell
|
|
87
|
+
All `GET`, all JSON, all reporting state already held in-process. They need an authenticated caller and show only what belongs to them: their conversations, confirmations, tool calls and memory, and the wiki as they see it (the team's `curated/` plus their own notes as `personal/`). Someone else's data doesn't exist as far as a caller can tell. `/manifest` lists what the caller is offered (a plugin acting as Mercury only for someone allowed to use it), and only `/health` is the same for everyone.
|
|
88
88
|
|
|
89
89
|
| endpoint | `data` on success |
|
|
90
90
|
|---|---|
|
package/dist/http-provider.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* (that stays with Google Chat). `stop` closes the socket on shutdown. Confirm is
|
|
11
11
|
* injected (`confirm`/`resolveConfirmation`), so this package never imports the app.
|
|
12
12
|
*/
|
|
13
|
-
import { type CompleteLoginFn, type ConfirmFn, type ResolveConfirmationFn } from "./http-server.ts";
|
|
13
|
+
import { type AdmitFn, type CompleteLoginFn, type ConfirmFn, type ResolveConfirmationFn } from "./http-server.ts";
|
|
14
14
|
import type { Authenticate, Provider, ChannelHostReads } from "@mercury-fw/channel-types";
|
|
15
15
|
export type HttpProviderDeps = {
|
|
16
16
|
port: number;
|
|
@@ -21,6 +21,8 @@ export type HttpProviderDeps = {
|
|
|
21
21
|
reads?: ChannelHostReads;
|
|
22
22
|
/** Who is calling, from the app's auth provider. */
|
|
23
23
|
authenticate: Authenticate;
|
|
24
|
+
/** Whether the core talks to them, from the core. */
|
|
25
|
+
admit?: AdmitFn;
|
|
24
26
|
/** Allowed CORS origin echoed to a browser UI; defaults to `*` in the server. */
|
|
25
27
|
corsOrigin?: string;
|
|
26
28
|
/** People's logins, when the surface has a public URL to be sent back to:
|
package/dist/http-server.d.ts
CHANGED
|
@@ -22,15 +22,18 @@
|
|
|
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 ConfirmOutcome, type LoginOutcome, type Principal } from "@mercury-fw/channel-types";
|
|
25
|
+
import { type HandleTurn, type Authenticate, type ChannelHostReads, type ConfirmOutcome, type LoginOutcome, type Principal, type Admission } from "@mercury-fw/channel-types";
|
|
26
26
|
/** Resolves a bare confirmation token to a reply string, or `null` if the input isn't a token. Injected by the core (`ctx.confirm`). */
|
|
27
27
|
export type ConfirmFn = (token: string, sessionKey: string, principal: Principal) => Promise<string | null>;
|
|
28
28
|
/** The structured sibling of {@link ConfirmFn}, for the `/confirm` `resolved` flag. Injected by the core (`ctx.resolveConfirmation`). */
|
|
29
29
|
export type ResolveConfirmationFn = (token: string, sessionKey: string, principal: Principal) => Promise<ConfirmOutcome>;
|
|
30
|
+
/** Whether the core talks to an authenticated caller at all, injected by the core (`ctx.admit`); everyone when absent. */
|
|
31
|
+
export type AdmitFn = (principal: Principal) => Promise<Admission>;
|
|
30
32
|
export type TurnRequestDeps = {
|
|
31
33
|
handleTurn: HandleTurn;
|
|
32
34
|
/** Who is calling, injected from the app's auth provider; `null` = refused. */
|
|
33
35
|
authenticate: Authenticate;
|
|
36
|
+
admit?: AdmitFn;
|
|
34
37
|
/** Bare-token interception before the model, injected by the core. */
|
|
35
38
|
confirm: ConfirmFn;
|
|
36
39
|
/** Allowed CORS origin echoed back to a browser UI; defaults to `*`. */
|
|
@@ -59,6 +62,7 @@ export declare function openApiResponse(corsOrigin?: string): Response;
|
|
|
59
62
|
export declare function handleTurnRequest(req: Request, deps: TurnRequestDeps): Promise<Response>;
|
|
60
63
|
export type ConfirmRequestDeps = {
|
|
61
64
|
authenticate: Authenticate;
|
|
65
|
+
admit?: AdmitFn;
|
|
62
66
|
resolveConfirmation: ResolveConfirmationFn;
|
|
63
67
|
corsOrigin?: string;
|
|
64
68
|
};
|
|
@@ -103,7 +107,7 @@ type ReadRoute = {
|
|
|
103
107
|
* `json` JSON-encodes with the CORS headers merged in; `badRequest` does the
|
|
104
108
|
* same for a 400.
|
|
105
109
|
*/
|
|
106
|
-
export declare function readRoutes(reads: ChannelHostReads, authenticate: Authenticate, corsOrigin?: string): Record<string, ReadRoute>;
|
|
110
|
+
export declare function readRoutes(reads: ChannelHostReads, authenticate: Authenticate, corsOrigin?: string, admit?: AdmitFn): Record<string, ReadRoute>;
|
|
107
111
|
export type HttpServerDeps = TurnRequestDeps & {
|
|
108
112
|
port: number;
|
|
109
113
|
reads?: ChannelHostReads;
|
package/http-provider.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* (that stays with Google Chat). `stop` closes the socket on shutdown. Confirm is
|
|
11
11
|
* injected (`confirm`/`resolveConfirmation`), so this package never imports the app.
|
|
12
12
|
*/
|
|
13
|
-
import { startHttpServer, type CompleteLoginFn, type ConfirmFn, type ResolveConfirmationFn } from "./http-server.ts";
|
|
13
|
+
import { startHttpServer, type AdmitFn, type CompleteLoginFn, type ConfirmFn, type ResolveConfirmationFn } from "./http-server.ts";
|
|
14
14
|
import type { Authenticate, Provider, HandleTurn, ChannelHostReads } from "@mercury-fw/channel-types";
|
|
15
15
|
|
|
16
16
|
export type HttpProviderDeps = {
|
|
@@ -22,6 +22,8 @@ export type HttpProviderDeps = {
|
|
|
22
22
|
reads?: ChannelHostReads;
|
|
23
23
|
/** Who is calling, from the app's auth provider. */
|
|
24
24
|
authenticate: Authenticate;
|
|
25
|
+
/** Whether the core talks to them, from the core. */
|
|
26
|
+
admit?: AdmitFn;
|
|
25
27
|
/** Allowed CORS origin echoed to a browser UI; defaults to `*` in the server. */
|
|
26
28
|
corsOrigin?: string;
|
|
27
29
|
/** People's logins, when the surface has a public URL to be sent back to:
|
|
@@ -40,6 +42,7 @@ export function createHttpProvider(deps: HttpProviderDeps): Provider & { stop():
|
|
|
40
42
|
resolveConfirmation: deps.resolveConfirmation,
|
|
41
43
|
reads: deps.reads,
|
|
42
44
|
authenticate: deps.authenticate,
|
|
45
|
+
...(deps.admit === undefined ? {} : { admit: deps.admit }),
|
|
43
46
|
corsOrigin: deps.corsOrigin,
|
|
44
47
|
completeLogin: deps.logins?.complete,
|
|
45
48
|
});
|
package/http-server.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
type ConfirmOutcome,
|
|
34
34
|
type LoginOutcome,
|
|
35
35
|
type Principal,
|
|
36
|
+
type Admission,
|
|
36
37
|
} from "@mercury-fw/channel-types";
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -53,10 +54,14 @@ export type ConfirmFn = (token: string, sessionKey: string, principal: Principal
|
|
|
53
54
|
/** The structured sibling of {@link ConfirmFn}, for the `/confirm` `resolved` flag. Injected by the core (`ctx.resolveConfirmation`). */
|
|
54
55
|
export type ResolveConfirmationFn = (token: string, sessionKey: string, principal: Principal) => Promise<ConfirmOutcome>;
|
|
55
56
|
|
|
57
|
+
/** Whether the core talks to an authenticated caller at all, injected by the core (`ctx.admit`); everyone when absent. */
|
|
58
|
+
export type AdmitFn = (principal: Principal) => Promise<Admission>;
|
|
59
|
+
|
|
56
60
|
export type TurnRequestDeps = {
|
|
57
61
|
handleTurn: HandleTurn;
|
|
58
62
|
/** Who is calling, injected from the app's auth provider; `null` = refused. */
|
|
59
63
|
authenticate: Authenticate;
|
|
64
|
+
admit?: AdmitFn;
|
|
60
65
|
/** Bare-token interception before the model, injected by the core. */
|
|
61
66
|
confirm: ConfirmFn;
|
|
62
67
|
/** Allowed CORS origin echoed back to a browser UI; defaults to `*`. */
|
|
@@ -95,6 +100,22 @@ function unauthorized(origin: string): Response {
|
|
|
95
100
|
);
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
/** The core's refusal of an authenticated caller: 403 for someone it doesn't know, 503 when it couldn't tell, with what to tell them. */
|
|
104
|
+
function notAdmitted(admission: Exclude<Admission, { ok: true }>, origin: string): Response {
|
|
105
|
+
return Response.json(
|
|
106
|
+
{ ok: false, error: admission.message, reason: admission.reason },
|
|
107
|
+
{ status: admission.reason === "unknown" ? 403 : 503, headers: corsHeaders(origin) },
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The caller of `req`, authenticated and admitted, or the response refusing them. */
|
|
112
|
+
async function caller(req: Request, deps: { authenticate: Authenticate; admit?: AdmitFn }, origin: string): Promise<Principal | Response> {
|
|
113
|
+
const principal = await deps.authenticate(req);
|
|
114
|
+
if (principal === null) return unauthorized(origin);
|
|
115
|
+
const admission = deps.admit ? await deps.admit(principal) : { ok: true as const };
|
|
116
|
+
return admission.ok ? principal : notAdmitted(admission, origin);
|
|
117
|
+
}
|
|
118
|
+
|
|
98
119
|
/** 204 preflight response for an `OPTIONS` request, carrying only CORS headers. */
|
|
99
120
|
function preflight(origin: string): Response {
|
|
100
121
|
return new Response(null, { status: 204, headers: corsHeaders(origin) });
|
|
@@ -127,8 +148,8 @@ export function openApiResponse(corsOrigin = "*"): Response {
|
|
|
127
148
|
export async function handleTurnRequest(req: Request, deps: TurnRequestDeps): Promise<Response> {
|
|
128
149
|
const origin = deps.corsOrigin ?? "*";
|
|
129
150
|
const cors = corsHeaders(origin);
|
|
130
|
-
const principal = await
|
|
131
|
-
if (principal
|
|
151
|
+
const principal = await caller(req, deps, origin);
|
|
152
|
+
if (principal instanceof Response) return principal;
|
|
132
153
|
let body: { text?: unknown; conversationId?: unknown };
|
|
133
154
|
try {
|
|
134
155
|
body = (await req.json()) as typeof body;
|
|
@@ -236,6 +257,7 @@ export async function handleTurnRequest(req: Request, deps: TurnRequestDeps): Pr
|
|
|
236
257
|
|
|
237
258
|
export type ConfirmRequestDeps = {
|
|
238
259
|
authenticate: Authenticate;
|
|
260
|
+
admit?: AdmitFn;
|
|
239
261
|
resolveConfirmation: ResolveConfirmationFn;
|
|
240
262
|
corsOrigin?: string;
|
|
241
263
|
};
|
|
@@ -255,8 +277,8 @@ export type ConfirmRequestDeps = {
|
|
|
255
277
|
export async function handleConfirmRequest(req: Request, deps: ConfirmRequestDeps): Promise<Response> {
|
|
256
278
|
const origin = deps.corsOrigin ?? "*";
|
|
257
279
|
const cors = corsHeaders(origin);
|
|
258
|
-
const principal = await
|
|
259
|
-
if (principal
|
|
280
|
+
const principal = await caller(req, deps, origin);
|
|
281
|
+
if (principal instanceof Response) return principal;
|
|
260
282
|
let body: { token?: unknown; conversationId?: unknown };
|
|
261
283
|
try {
|
|
262
284
|
body = (await req.json()) as typeof body;
|
|
@@ -340,7 +362,12 @@ type ReadRoute = {
|
|
|
340
362
|
* `json` JSON-encodes with the CORS headers merged in; `badRequest` does the
|
|
341
363
|
* same for a 400.
|
|
342
364
|
*/
|
|
343
|
-
export function readRoutes(
|
|
365
|
+
export function readRoutes(
|
|
366
|
+
reads: ChannelHostReads,
|
|
367
|
+
authenticate: Authenticate,
|
|
368
|
+
corsOrigin = "*",
|
|
369
|
+
admit?: AdmitFn,
|
|
370
|
+
): Record<string, ReadRoute> {
|
|
344
371
|
const cors = corsHeaders(corsOrigin);
|
|
345
372
|
const json = (payload: object, status = 200): Response =>
|
|
346
373
|
Response.json(payload, { status, headers: cors });
|
|
@@ -349,14 +376,14 @@ export function readRoutes(reads: ChannelHostReads, authenticate: Authenticate,
|
|
|
349
376
|
const options = () => preflight(corsOrigin);
|
|
350
377
|
const route = (GET: (req: Request, principal: Principal) => Response | Promise<Response>): ReadRoute => ({
|
|
351
378
|
GET: async (req) => {
|
|
352
|
-
const principal = await authenticate(
|
|
353
|
-
return principal
|
|
379
|
+
const principal = await caller(req, { authenticate, ...(admit === undefined ? {} : { admit }) }, corsOrigin);
|
|
380
|
+
return principal instanceof Response ? principal : GET(req, principal);
|
|
354
381
|
},
|
|
355
382
|
OPTIONS: options,
|
|
356
383
|
});
|
|
357
384
|
return {
|
|
358
|
-
"/manifest": route(() => json({ ok: true, manifest: reads.manifest() })),
|
|
359
|
-
"/confirmations": route((_req, principal) => json({ ok: true, pending: reads.pendingConfirmations(principal) })),
|
|
385
|
+
"/manifest": route(async (_req, principal) => json({ ok: true, manifest: await reads.manifest(principal) })),
|
|
386
|
+
"/confirmations": route(async (_req, principal) => json({ ok: true, pending: await reads.pendingConfirmations(principal) })),
|
|
360
387
|
"/conversation": route(async (req, principal) => {
|
|
361
388
|
const url = new URL(req.url);
|
|
362
389
|
const id = url.searchParams.get("id");
|
|
@@ -379,7 +406,7 @@ export function readRoutes(reads: ChannelHostReads, authenticate: Authenticate,
|
|
|
379
406
|
.map((c) => ({ conversationId: c.sessionKey.slice(prefix.length), lastTimestamp: c.lastTimestamp, preview: c.preview })),
|
|
380
407
|
});
|
|
381
408
|
}),
|
|
382
|
-
"/tool-log": route((_req, principal) => json({ ok: true, entries: reads.toolLog(principal) })),
|
|
409
|
+
"/tool-log": route(async (_req, principal) => json({ ok: true, entries: await reads.toolLog(principal) })),
|
|
383
410
|
"/health": route(async () => json({ ok: true, ...(await reads.health() as object) })),
|
|
384
411
|
"/wiki/list": route(async (_req, principal) => json({ ok: true, files: await reads.wikiList(principal) })),
|
|
385
412
|
"/wiki/read": route(async (req, principal) => {
|
|
@@ -433,11 +460,16 @@ export function startHttpServer(deps: HttpServerDeps): ReturnType<typeof Bun.ser
|
|
|
433
460
|
"/turn": { POST: (req) => handleTurnRequest(req, deps), OPTIONS: () => preflight(origin) },
|
|
434
461
|
"/confirm": {
|
|
435
462
|
POST: (req) =>
|
|
436
|
-
handleConfirmRequest(req, {
|
|
463
|
+
handleConfirmRequest(req, {
|
|
464
|
+
authenticate: deps.authenticate,
|
|
465
|
+
...(deps.admit === undefined ? {} : { admit: deps.admit }),
|
|
466
|
+
resolveConfirmation: deps.resolveConfirmation,
|
|
467
|
+
corsOrigin: origin,
|
|
468
|
+
}),
|
|
437
469
|
OPTIONS: () => preflight(origin),
|
|
438
470
|
},
|
|
439
471
|
"/openapi.yaml": { GET: () => openApiResponse(origin), OPTIONS: () => preflight(origin) },
|
|
440
|
-
...(deps.reads ? readRoutes(deps.reads, deps.authenticate, origin) : {}),
|
|
472
|
+
...(deps.reads ? readRoutes(deps.reads, deps.authenticate, origin, deps.admit) : {}),
|
|
441
473
|
...(deps.completeLogin ? loginRoutes(deps.completeLogin) : {}),
|
|
442
474
|
},
|
|
443
475
|
error: (err) =>
|
package/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { createHttpProvider, type HttpProviderDeps } from "./http-provider.ts";
|
|
|
22
22
|
export const httpChannel: ChannelPlugin = {
|
|
23
23
|
// The contract this channel is written for, as a literal: importing
|
|
24
24
|
// CHANNEL_API_VERSION would report whichever contract is installed.
|
|
25
|
-
apiVersion:
|
|
25
|
+
apiVersion: 5,
|
|
26
26
|
name: "http",
|
|
27
27
|
build: (ctx) => {
|
|
28
28
|
if (!ctx.resolveConfirmation || !ctx.reads) {
|
|
@@ -42,6 +42,7 @@ export const httpChannel: ChannelPlugin = {
|
|
|
42
42
|
resolveConfirmation: ctx.resolveConfirmation,
|
|
43
43
|
reads: ctx.reads,
|
|
44
44
|
authenticate: ctx.authenticate,
|
|
45
|
+
admit: ctx.admit,
|
|
45
46
|
...(logins ? { logins: { callbackUrl: `${publicUrl}/login/callback`, accept: logins.accept, complete: logins.complete } } : {}),
|
|
46
47
|
});
|
|
47
48
|
},
|
package/openapi.yaml
CHANGED
|
@@ -20,14 +20,20 @@ info:
|
|
|
20
20
|
missing or refused one gets `401` with `WWW-Authenticate: Bearer`, before
|
|
21
21
|
anything runs.
|
|
22
22
|
|
|
23
|
+
Then the core decides whether it talks to that person at all: someone the
|
|
24
|
+
app's directory doesn't know gets `403` (unless the app lets unknown people
|
|
25
|
+
in), and `503` when the directory can't be reached. Both carry
|
|
26
|
+
`{ ok: false, error, reason }`, `error` being what to tell them.
|
|
27
|
+
|
|
23
28
|
|
|
24
29
|
## What a caller sees
|
|
25
30
|
|
|
26
31
|
Every read is scoped to the caller: their own conversations, pending
|
|
27
32
|
confirmations, tool calls and memory, and the wiki as they see it, the
|
|
28
33
|
team's common area (`curated/`) plus their own notes (`personal/`).
|
|
29
|
-
Someone else's data doesn't exist as far as a caller can tell.
|
|
30
|
-
`/manifest` and `/health`
|
|
34
|
+
Someone else's data doesn't exist as far as a caller can tell.
|
|
35
|
+
`/manifest` lists what the caller is offered, and only `/health` is the
|
|
36
|
+
same for everyone.
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
## Conversations
|
|
@@ -82,6 +88,10 @@ paths:
|
|
|
82
88
|
responses:
|
|
83
89
|
"401":
|
|
84
90
|
$ref: "#/components/responses/Unauthorized"
|
|
91
|
+
"403":
|
|
92
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
93
|
+
"503":
|
|
94
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
85
95
|
"200":
|
|
86
96
|
description: >
|
|
87
97
|
An SSE stream. Each event is one of the types in `TurnEvent`:
|
|
@@ -117,6 +127,10 @@ paths:
|
|
|
117
127
|
responses:
|
|
118
128
|
"401":
|
|
119
129
|
$ref: "#/components/responses/Unauthorized"
|
|
130
|
+
"403":
|
|
131
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
132
|
+
"503":
|
|
133
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
120
134
|
"200":
|
|
121
135
|
description: >
|
|
122
136
|
`resolved: true` (with the reply `text`) when the token was a pending
|
|
@@ -158,6 +172,10 @@ paths:
|
|
|
158
172
|
responses:
|
|
159
173
|
"401":
|
|
160
174
|
$ref: "#/components/responses/Unauthorized"
|
|
175
|
+
"403":
|
|
176
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
177
|
+
"503":
|
|
178
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
161
179
|
"200":
|
|
162
180
|
description: The conversation's messages in chronological order; none when it isn't one of the caller's.
|
|
163
181
|
content:
|
|
@@ -183,6 +201,10 @@ paths:
|
|
|
183
201
|
responses:
|
|
184
202
|
"401":
|
|
185
203
|
$ref: "#/components/responses/Unauthorized"
|
|
204
|
+
"403":
|
|
205
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
206
|
+
"503":
|
|
207
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
186
208
|
"200":
|
|
187
209
|
description: The conversation list.
|
|
188
210
|
content:
|
|
@@ -195,6 +217,10 @@ paths:
|
|
|
195
217
|
responses:
|
|
196
218
|
"401":
|
|
197
219
|
$ref: "#/components/responses/Unauthorized"
|
|
220
|
+
"403":
|
|
221
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
222
|
+
"503":
|
|
223
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
198
224
|
"200":
|
|
199
225
|
description: Health snapshot.
|
|
200
226
|
content:
|
|
@@ -203,10 +229,14 @@ paths:
|
|
|
203
229
|
type: object
|
|
204
230
|
/manifest:
|
|
205
231
|
get:
|
|
206
|
-
summary: The
|
|
232
|
+
summary: The plugins, tools and skills the caller is offered
|
|
207
233
|
responses:
|
|
208
234
|
"401":
|
|
209
235
|
$ref: "#/components/responses/Unauthorized"
|
|
236
|
+
"403":
|
|
237
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
238
|
+
"503":
|
|
239
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
210
240
|
"200":
|
|
211
241
|
description: Plugin manifest.
|
|
212
242
|
content:
|
|
@@ -219,6 +249,10 @@ paths:
|
|
|
219
249
|
responses:
|
|
220
250
|
"401":
|
|
221
251
|
$ref: "#/components/responses/Unauthorized"
|
|
252
|
+
"403":
|
|
253
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
254
|
+
"503":
|
|
255
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
222
256
|
"200":
|
|
223
257
|
description: Pending confirmations.
|
|
224
258
|
content:
|
|
@@ -231,6 +265,10 @@ paths:
|
|
|
231
265
|
responses:
|
|
232
266
|
"401":
|
|
233
267
|
$ref: "#/components/responses/Unauthorized"
|
|
268
|
+
"403":
|
|
269
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
270
|
+
"503":
|
|
271
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
234
272
|
"200":
|
|
235
273
|
description: Tool log.
|
|
236
274
|
content:
|
|
@@ -243,6 +281,10 @@ paths:
|
|
|
243
281
|
responses:
|
|
244
282
|
"401":
|
|
245
283
|
$ref: "#/components/responses/Unauthorized"
|
|
284
|
+
"403":
|
|
285
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
286
|
+
"503":
|
|
287
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
246
288
|
"200":
|
|
247
289
|
description: File list.
|
|
248
290
|
content:
|
|
@@ -262,6 +304,10 @@ paths:
|
|
|
262
304
|
responses:
|
|
263
305
|
"401":
|
|
264
306
|
$ref: "#/components/responses/Unauthorized"
|
|
307
|
+
"403":
|
|
308
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
309
|
+
"503":
|
|
310
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
265
311
|
"200":
|
|
266
312
|
description: File content.
|
|
267
313
|
content:
|
|
@@ -293,6 +339,10 @@ paths:
|
|
|
293
339
|
responses:
|
|
294
340
|
"401":
|
|
295
341
|
$ref: "#/components/responses/Unauthorized"
|
|
342
|
+
"403":
|
|
343
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
344
|
+
"503":
|
|
345
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
296
346
|
"200":
|
|
297
347
|
description: Matches.
|
|
298
348
|
content:
|
|
@@ -329,6 +379,10 @@ paths:
|
|
|
329
379
|
responses:
|
|
330
380
|
"401":
|
|
331
381
|
$ref: "#/components/responses/Unauthorized"
|
|
382
|
+
"403":
|
|
383
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
384
|
+
"503":
|
|
385
|
+
$ref: "#/components/responses/NotAdmitted"
|
|
332
386
|
"200":
|
|
333
387
|
description: A page of points.
|
|
334
388
|
content:
|
|
@@ -396,6 +450,26 @@ components:
|
|
|
396
450
|
application/json:
|
|
397
451
|
schema:
|
|
398
452
|
$ref: "#/components/schemas/Error"
|
|
453
|
+
NotAdmitted:
|
|
454
|
+
description: >
|
|
455
|
+
An authenticated caller the core won't talk to: `403` when the app's
|
|
456
|
+
directory doesn't know them, `503` when it can't be reached. Nothing
|
|
457
|
+
behind the route runs.
|
|
458
|
+
content:
|
|
459
|
+
application/json:
|
|
460
|
+
schema:
|
|
461
|
+
type: object
|
|
462
|
+
required: [ok, error, reason]
|
|
463
|
+
properties:
|
|
464
|
+
ok:
|
|
465
|
+
type: boolean
|
|
466
|
+
enum: [false]
|
|
467
|
+
error:
|
|
468
|
+
type: string
|
|
469
|
+
description: What to tell the caller.
|
|
470
|
+
reason:
|
|
471
|
+
type: string
|
|
472
|
+
enum: [unknown, unavailable]
|
|
399
473
|
schemas:
|
|
400
474
|
TurnRequest:
|
|
401
475
|
type: object
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/channel-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.42.0",
|
|
25
|
+
"@mercury-fw/plugin-types": "0.42.0",
|
|
26
26
|
"@mercury-fw/typescript-config": "*",
|
|
27
27
|
"@types/bun": "^1.4.2",
|
|
28
28
|
"typescript": "^6.0.3"
|