@mercury-fw/channel-types 0.25.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 +7 -0
- package/README.md +5 -0
- package/dist/index.d.ts +181 -0
- package/index.ts +194 -0
- package/package.json +36 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# @mercury-fw/channel-types
|
|
2
|
+
|
|
3
|
+
The contract a [Mercury](https://github.com/lucabro81/mercury-fw) channel implements: `ChannelPlugin`, `CHANNEL_API_VERSION`, the provider and sink a channel drives a turn through, and the confirmation helpers the core injects. Channel authors get it through [`@mercury-fw/kit`](https://www.npmjs.com/package/@mercury-fw/kit).
|
|
4
|
+
|
|
5
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contract between the core and every channel plugin: the turn shape
|
|
3
|
+
* (`Provider`/`InboundTurn`/`TurnSink`/`HandleTurn`/`Notifier`), the pure
|
|
4
|
+
* confirmation pieces a channel uses to build its own UI
|
|
5
|
+
* (`detectPendingConfirmation`, `PENDING_CONFIRMATION_NOTE`, `NO_REPLY`), and
|
|
6
|
+
* the channel-plugin system (`ChannelPlugin`/`ChannelRuntimeContext`/
|
|
7
|
+
* `CHANNEL_API_VERSION`). The stateful half of confirmation (`ConfirmationStore`,
|
|
8
|
+
* `tryConfirm`) stays in the core and reaches a channel via `ctx.confirm`.
|
|
9
|
+
*/
|
|
10
|
+
import type { StepInfo } from "@mercury-fw/plugin-types";
|
|
11
|
+
/** Settled outcome of a tool call (or guard/capture-ping) reported via `onToolFinish`. `pending` = action deferred behind a token, not run. */
|
|
12
|
+
export type ToolOutcome = "success" | "failed" | "pending";
|
|
13
|
+
/** A confirm-required staging found in a step: the token to send back and a summary of what will run. */
|
|
14
|
+
export type PendingConfirmation = {
|
|
15
|
+
token: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Returns the first confirm-required staging in `step` (in tool-call order), or
|
|
20
|
+
* `null`. Keys on the `pendingConfirmation` flag a tool sets on its result, not
|
|
21
|
+
* on a tool name. Used by the core to stop the loop and by channels to build
|
|
22
|
+
* their confirm UI.
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectPendingConfirmation(step: StepInfo): PendingConfirmation | null;
|
|
25
|
+
/** Text shown when a turn ends with no text because it staged an action: stands in for the answer, not model-generated (no token to leak). Every channel suppresses it and shows its own confirm UI. */
|
|
26
|
+
export declare const PENDING_CONFIRMATION_NOTE = "Azione in sospeso, in attesa di conferma.";
|
|
27
|
+
/** Sentinel the model returns for "not addressed to me" in a multi-person space (see `buildSystemPrompt`'s multiUser block). A multi-user channel suppresses it in `finalize`. */
|
|
28
|
+
export declare const NO_REPLY = "NO_REPLY";
|
|
29
|
+
/** One inbound message, already resolved by its provider into the shape the shared layer needs. */
|
|
30
|
+
export type InboundTurn = {
|
|
31
|
+
/** Stable provider id for the tool log — a free string, each provider supplies its own. */
|
|
32
|
+
channel: string;
|
|
33
|
+
/** Whether the conversation can carry more than one person — selects the NO_REPLY system-prompt variant. */
|
|
34
|
+
multiUser: boolean;
|
|
35
|
+
/** Text handed to the model, already provider-decorated (e.g. Google Chat's "[Da: X]" marker). */
|
|
36
|
+
text: string;
|
|
37
|
+
/** Opaque session key. Derived by the provider, never parsed above it. */
|
|
38
|
+
sessionKey: string;
|
|
39
|
+
/** Opaque per-person id for Layer-3 capture. `undefined` = provider with no real per-user identity (terminal today), session not tracked. */
|
|
40
|
+
userId?: string;
|
|
41
|
+
/** Opaque, already path-safe per-person id for `inferred/users/<id>` scoping. */
|
|
42
|
+
wikiUserId: string;
|
|
43
|
+
/** stderr log prefix, e.g. `[chat:spaces/x:users/y] ` — empty for the terminal. */
|
|
44
|
+
logPrefix: string;
|
|
45
|
+
/** When set, aborting it cancels the in-flight turn. Only the HTTP surface supplies one today (client disconnect); other channels leave it undefined. */
|
|
46
|
+
abortSignal?: AbortSignal;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* A provider's output for one turn. Each optional member maps 1:1 onto an
|
|
50
|
+
* optional `runTurn` dep: supplying `onTextChunk` *or* `onReasoningChunk` puts
|
|
51
|
+
* `runTurn` on its `streamText` path. Google Chat supplies only
|
|
52
|
+
* `onReasoningChunk` (never `onTextChunk`), so *answer* streaming never starts there.
|
|
53
|
+
*/
|
|
54
|
+
export type TurnSink = {
|
|
55
|
+
/** `onToolStart` for `buildTools`. `detail`/`toolCallId` present for a real tool call or a capture-ping with its own id; both undefined = a caller with no correlation id. */
|
|
56
|
+
onToolStart: (label: string, detail?: string, toolCallId?: string) => void;
|
|
57
|
+
/** Paired with `onToolStart` via `toolCallId` once the call settles. Optional — only Google Chat implements it (patches its status card). */
|
|
58
|
+
onToolFinish?: (toolCallId: string, outcome: ToolOutcome) => void;
|
|
59
|
+
/** Present ⇒ `runTurn` uses `streamText`. Must stay undefined for Google Chat's own *answer* delivery. */
|
|
60
|
+
onTextChunk?: (chunk: string) => void;
|
|
61
|
+
/** Reasoning-token delta (Ollama extended thinking, behind OLLAMA_THINK). Present ⇒ `streamText`. Never reaches `SessionHistory`. `id` is the reasoning-block id; a turn can reason more than once, each burst with its own id. */
|
|
62
|
+
onReasoningChunk?: (chunk: string, id: string) => void;
|
|
63
|
+
/** Closes a reasoning block that started (even if the turn aborts with it open), so a live display doesn't get stuck. Never for an id `onReasoningChunk` didn't report. `failed` is true only on abort. */
|
|
64
|
+
onReasoningEnd?: (id: string, failed: boolean) => void;
|
|
65
|
+
/** Provider-local per-step bookkeeping (the terminal's `/dump` buffer). */
|
|
66
|
+
onStep?: (step: StepInfo) => void;
|
|
67
|
+
/** Provider-local usage handling (the terminal's prompt indicator, Chat's stderr line). */
|
|
68
|
+
onUsage?: (inputTokens: number | undefined) => void;
|
|
69
|
+
/** Deliver the model's complete final text. */
|
|
70
|
+
finalize: (finalText: string) => Promise<void>;
|
|
71
|
+
/** Release per-turn resources. Idempotent. */
|
|
72
|
+
dispose: () => void;
|
|
73
|
+
};
|
|
74
|
+
/** What a provider calls once it has a real model turn to run. */
|
|
75
|
+
export type HandleTurn = (turn: InboundTurn, sink: TurnSink) => Promise<void>;
|
|
76
|
+
/** Proactive, out-of-band delivery — the slice the cron layer depends on. */
|
|
77
|
+
export type Notifier = {
|
|
78
|
+
/** DMs `userId` (an opaque id the provider knows how to address). Returns the session key of the conversation the message landed in, so a reply continues that same conversation. */
|
|
79
|
+
notify(userId: string, text: string): Promise<{
|
|
80
|
+
sessionKey: string;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* What a provider (terminal, Google Chat, HTTP, future ones) must supply to
|
|
85
|
+
* plug into the turn pipeline and proactive notification. Opaque addressing
|
|
86
|
+
* strings: each provider owns its own addressing model, nothing above this
|
|
87
|
+
* layer parses them.
|
|
88
|
+
*/
|
|
89
|
+
export type Provider = Notifier & {
|
|
90
|
+
/** Runs the provider's inbound driver, calling `handleTurn` once per message that needs the model. Deterministic pre-interception (a confirm token, `/dump`) is the provider's, before this. Resolves when the provider stops. */
|
|
91
|
+
start(handleTurn: HandleTurn): Promise<void>;
|
|
92
|
+
/** Optional lifecycle stop for a channel with a background resource (a Pub/Sub subscription, an HTTP server); the composition root calls it on shutdown. A channel with nothing to release omits it. */
|
|
93
|
+
stop?(): Promise<void>;
|
|
94
|
+
};
|
|
95
|
+
/** Channel-plugin contract version: the loader refuses a channel with a different `apiVersion` fail-soft, like the tool-plugin loader with `PLUGIN_API_VERSION`. Bumped only on a breaking change to this file's shapes. */
|
|
96
|
+
export declare const CHANNEL_API_VERSION = 1;
|
|
97
|
+
/**
|
|
98
|
+
* Structured outcome of resolving a confirmation token, distinguishing cases the
|
|
99
|
+
* string-returning `confirm` collapses together. `not-a-token` = the input isn't
|
|
100
|
+
* token-shaped (run the normal flow); `not-found` = token-shaped but no matching
|
|
101
|
+
* pending confirmation; `ok`/`failed` = the staged action was consumed and run.
|
|
102
|
+
* Returned by `ctx.resolveConfirmation` — the value the core's `resolveConfirmation`
|
|
103
|
+
* produces. A channel that needs to branch on acceptance (the HTTP `/confirm`
|
|
104
|
+
* endpoint's `resolved` flag) uses it; one that only needs a reply string uses `confirm`.
|
|
105
|
+
*/
|
|
106
|
+
export type ConfirmOutcome = {
|
|
107
|
+
status: "not-a-token";
|
|
108
|
+
} | {
|
|
109
|
+
status: "not-found";
|
|
110
|
+
} | {
|
|
111
|
+
status: "ok";
|
|
112
|
+
data: unknown;
|
|
113
|
+
} | {
|
|
114
|
+
status: "failed";
|
|
115
|
+
error: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Read-only introspection getters the core injects for a channel that exposes an
|
|
119
|
+
* API/UI (the HTTP surface today). Every getter reads state that already exists
|
|
120
|
+
* in-process — nothing computes anything new — so these can't come from `env`.
|
|
121
|
+
* Returns are `unknown`/primitive by design, to keep this contract free of any
|
|
122
|
+
* domain types. Tokens are never exposed.
|
|
123
|
+
*/
|
|
124
|
+
export type ChannelHostReads = {
|
|
125
|
+
manifest: () => unknown;
|
|
126
|
+
pendingConfirmations: () => unknown;
|
|
127
|
+
/** A conversation's durable verbatim transcript, chronological, paginated. */
|
|
128
|
+
conversation: (sessionKey: string, limit: number, offset?: string) => Promise<unknown>;
|
|
129
|
+
/** The known conversations, most-recently-active first. */
|
|
130
|
+
conversations: (limit: number) => Promise<unknown>;
|
|
131
|
+
wikiList: () => Promise<unknown>;
|
|
132
|
+
wikiRead: (path: string) => Promise<unknown>;
|
|
133
|
+
wikiGrep: (pattern: string) => Promise<unknown>;
|
|
134
|
+
memoryScroll: (collection: string, limit: number, offset?: string) => Promise<unknown>;
|
|
135
|
+
toolLog: () => unknown;
|
|
136
|
+
health: () => Promise<unknown>;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* The minimal capabilities every channel gets from the core — the intersection
|
|
140
|
+
* across terminal, HTTP and Google Chat, nothing channel-specific. Anything
|
|
141
|
+
* specific (Google Chat's credentials, HTTP's port) the channel reads from `env`
|
|
142
|
+
* in its own `build()`. This is the dependency-inversion seam: the core injects
|
|
143
|
+
* these, the channel imports none of them.
|
|
144
|
+
*
|
|
145
|
+
* `env`, `log` and `confirm` are the floor every channel relies on.
|
|
146
|
+
* `resolveConfirmation` and `reads` are optional in-process capabilities that
|
|
147
|
+
* can't come from `env`: the core populates them, only a channel that needs them
|
|
148
|
+
* (HTTP) reads them, the others ignore them.
|
|
149
|
+
*/
|
|
150
|
+
export type ChannelRuntimeContext = {
|
|
151
|
+
/** The process env, so a channel reads its own config (subscription, credentials, port) without the core knowing which keys it needs. */
|
|
152
|
+
env: Record<string, string | undefined>;
|
|
153
|
+
/** stderr logger for the channel's own lifecycle/errors. */
|
|
154
|
+
log: (msg: string) => void;
|
|
155
|
+
/**
|
|
156
|
+
* Resolves a confirmation token against the core's single `ConfirmationStore`,
|
|
157
|
+
* already bound to this instance's store/vault/writer. The channel calls it
|
|
158
|
+
* and holds no state: staging and the store stay the core's. Returns the reply
|
|
159
|
+
* text to send back, or `null` when the input wasn't token-shaped (see
|
|
160
|
+
* `resolveConfirmation`).
|
|
161
|
+
*/
|
|
162
|
+
confirm: (token: string, sessionKey: string, userId: string) => Promise<string | null>;
|
|
163
|
+
/** The structured sibling of `confirm` (see `ConfirmOutcome`), for a channel that branches on whether the token was accepted. */
|
|
164
|
+
resolveConfirmation?: (token: string, sessionKey: string, userId: string) => Promise<ConfirmOutcome>;
|
|
165
|
+
/** In-process introspection getters for a channel that exposes an API/UI. */
|
|
166
|
+
reads?: ChannelHostReads;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* A channel plugin: the value the core's channel loader consumes, mirroring
|
|
170
|
+
* `@mercury-fw/plugin-types`' `Plugin`. A channel package exports one and does not
|
|
171
|
+
* import the app.
|
|
172
|
+
*
|
|
173
|
+
* - `apiVersion`: contract version (see `CHANNEL_API_VERSION`); the loader refuses a mismatch.
|
|
174
|
+
* - `name`: the channel's id, and the key the loader registers the provider under (the cron layer looks up `"google-chat"` for its `Notifier`).
|
|
175
|
+
* - `build`: builds the `Provider` from the context, or `undefined` when the instance isn't configured for this channel (e.g. no `GOOGLE_CHAT_PUBSUB_SUBSCRIPTION`) → the loader treats it as "present but inert" and never starts it.
|
|
176
|
+
*/
|
|
177
|
+
export type ChannelPlugin = {
|
|
178
|
+
apiVersion: number;
|
|
179
|
+
name: string;
|
|
180
|
+
build: (ctx: ChannelRuntimeContext) => Provider | undefined;
|
|
181
|
+
};
|
package/index.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contract between the core and every channel plugin: the turn shape
|
|
3
|
+
* (`Provider`/`InboundTurn`/`TurnSink`/`HandleTurn`/`Notifier`), the pure
|
|
4
|
+
* confirmation pieces a channel uses to build its own UI
|
|
5
|
+
* (`detectPendingConfirmation`, `PENDING_CONFIRMATION_NOTE`, `NO_REPLY`), and
|
|
6
|
+
* the channel-plugin system (`ChannelPlugin`/`ChannelRuntimeContext`/
|
|
7
|
+
* `CHANNEL_API_VERSION`). The stateful half of confirmation (`ConfirmationStore`,
|
|
8
|
+
* `tryConfirm`) stays in the core and reaches a channel via `ctx.confirm`.
|
|
9
|
+
*/
|
|
10
|
+
import type { StepInfo } from "@mercury-fw/plugin-types";
|
|
11
|
+
|
|
12
|
+
/** Settled outcome of a tool call (or guard/capture-ping) reported via `onToolFinish`. `pending` = action deferred behind a token, not run. */
|
|
13
|
+
export type ToolOutcome = "success" | "failed" | "pending";
|
|
14
|
+
|
|
15
|
+
/** A confirm-required staging found in a step: the token to send back and a summary of what will run. */
|
|
16
|
+
export type PendingConfirmation = { token: string; summary: string };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns the first confirm-required staging in `step` (in tool-call order), or
|
|
20
|
+
* `null`. Keys on the `pendingConfirmation` flag a tool sets on its result, not
|
|
21
|
+
* on a tool name. Used by the core to stop the loop and by channels to build
|
|
22
|
+
* their confirm UI.
|
|
23
|
+
*/
|
|
24
|
+
export function detectPendingConfirmation(step: StepInfo): PendingConfirmation | null {
|
|
25
|
+
for (const call of step.toolCalls) {
|
|
26
|
+
const result = step.toolResults.find((r) => r.toolCallId === call.toolCallId);
|
|
27
|
+
const output = result?.output as { pendingConfirmation?: unknown; token?: unknown; summary?: unknown } | undefined;
|
|
28
|
+
if (!output || output.pendingConfirmation !== true || typeof output.token !== "string") continue;
|
|
29
|
+
|
|
30
|
+
return { token: output.token, summary: typeof output.summary === "string" ? output.summary : "" };
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Text shown when a turn ends with no text because it staged an action: stands in for the answer, not model-generated (no token to leak). Every channel suppresses it and shows its own confirm UI. */
|
|
36
|
+
export const PENDING_CONFIRMATION_NOTE = "Azione in sospeso, in attesa di conferma.";
|
|
37
|
+
|
|
38
|
+
/** Sentinel the model returns for "not addressed to me" in a multi-person space (see `buildSystemPrompt`'s multiUser block). A multi-user channel suppresses it in `finalize`. */
|
|
39
|
+
export const NO_REPLY = "NO_REPLY";
|
|
40
|
+
|
|
41
|
+
/** One inbound message, already resolved by its provider into the shape the shared layer needs. */
|
|
42
|
+
export type InboundTurn = {
|
|
43
|
+
/** Stable provider id for the tool log — a free string, each provider supplies its own. */
|
|
44
|
+
channel: string;
|
|
45
|
+
/** Whether the conversation can carry more than one person — selects the NO_REPLY system-prompt variant. */
|
|
46
|
+
multiUser: boolean;
|
|
47
|
+
/** Text handed to the model, already provider-decorated (e.g. Google Chat's "[Da: X]" marker). */
|
|
48
|
+
text: string;
|
|
49
|
+
/** Opaque session key. Derived by the provider, never parsed above it. */
|
|
50
|
+
sessionKey: string;
|
|
51
|
+
/** Opaque per-person id for Layer-3 capture. `undefined` = provider with no real per-user identity (terminal today), session not tracked. */
|
|
52
|
+
userId?: string;
|
|
53
|
+
/** Opaque, already path-safe per-person id for `inferred/users/<id>` scoping. */
|
|
54
|
+
wikiUserId: string;
|
|
55
|
+
/** stderr log prefix, e.g. `[chat:spaces/x:users/y] ` — empty for the terminal. */
|
|
56
|
+
logPrefix: string;
|
|
57
|
+
/** When set, aborting it cancels the in-flight turn. Only the HTTP surface supplies one today (client disconnect); other channels leave it undefined. */
|
|
58
|
+
abortSignal?: AbortSignal;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A provider's output for one turn. Each optional member maps 1:1 onto an
|
|
63
|
+
* optional `runTurn` dep: supplying `onTextChunk` *or* `onReasoningChunk` puts
|
|
64
|
+
* `runTurn` on its `streamText` path. Google Chat supplies only
|
|
65
|
+
* `onReasoningChunk` (never `onTextChunk`), so *answer* streaming never starts there.
|
|
66
|
+
*/
|
|
67
|
+
export type TurnSink = {
|
|
68
|
+
/** `onToolStart` for `buildTools`. `detail`/`toolCallId` present for a real tool call or a capture-ping with its own id; both undefined = a caller with no correlation id. */
|
|
69
|
+
onToolStart: (label: string, detail?: string, toolCallId?: string) => void;
|
|
70
|
+
/** Paired with `onToolStart` via `toolCallId` once the call settles. Optional — only Google Chat implements it (patches its status card). */
|
|
71
|
+
onToolFinish?: (toolCallId: string, outcome: ToolOutcome) => void;
|
|
72
|
+
/** Present ⇒ `runTurn` uses `streamText`. Must stay undefined for Google Chat's own *answer* delivery. */
|
|
73
|
+
onTextChunk?: (chunk: string) => void;
|
|
74
|
+
/** Reasoning-token delta (Ollama extended thinking, behind OLLAMA_THINK). Present ⇒ `streamText`. Never reaches `SessionHistory`. `id` is the reasoning-block id; a turn can reason more than once, each burst with its own id. */
|
|
75
|
+
onReasoningChunk?: (chunk: string, id: string) => void;
|
|
76
|
+
/** Closes a reasoning block that started (even if the turn aborts with it open), so a live display doesn't get stuck. Never for an id `onReasoningChunk` didn't report. `failed` is true only on abort. */
|
|
77
|
+
onReasoningEnd?: (id: string, failed: boolean) => void;
|
|
78
|
+
/** Provider-local per-step bookkeeping (the terminal's `/dump` buffer). */
|
|
79
|
+
onStep?: (step: StepInfo) => void;
|
|
80
|
+
/** Provider-local usage handling (the terminal's prompt indicator, Chat's stderr line). */
|
|
81
|
+
onUsage?: (inputTokens: number | undefined) => void;
|
|
82
|
+
/** Deliver the model's complete final text. */
|
|
83
|
+
finalize: (finalText: string) => Promise<void>;
|
|
84
|
+
/** Release per-turn resources. Idempotent. */
|
|
85
|
+
dispose: () => void;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** What a provider calls once it has a real model turn to run. */
|
|
89
|
+
export type HandleTurn = (turn: InboundTurn, sink: TurnSink) => Promise<void>;
|
|
90
|
+
|
|
91
|
+
/** Proactive, out-of-band delivery — the slice the cron layer depends on. */
|
|
92
|
+
export type Notifier = {
|
|
93
|
+
/** DMs `userId` (an opaque id the provider knows how to address). Returns the session key of the conversation the message landed in, so a reply continues that same conversation. */
|
|
94
|
+
notify(userId: string, text: string): Promise<{ sessionKey: string }>;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* What a provider (terminal, Google Chat, HTTP, future ones) must supply to
|
|
99
|
+
* plug into the turn pipeline and proactive notification. Opaque addressing
|
|
100
|
+
* strings: each provider owns its own addressing model, nothing above this
|
|
101
|
+
* layer parses them.
|
|
102
|
+
*/
|
|
103
|
+
export type Provider = Notifier & {
|
|
104
|
+
/** Runs the provider's inbound driver, calling `handleTurn` once per message that needs the model. Deterministic pre-interception (a confirm token, `/dump`) is the provider's, before this. Resolves when the provider stops. */
|
|
105
|
+
start(handleTurn: HandleTurn): Promise<void>;
|
|
106
|
+
/** Optional lifecycle stop for a channel with a background resource (a Pub/Sub subscription, an HTTP server); the composition root calls it on shutdown. A channel with nothing to release omits it. */
|
|
107
|
+
stop?(): Promise<void>;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** Channel-plugin contract version: the loader refuses a channel with a different `apiVersion` fail-soft, like the tool-plugin loader with `PLUGIN_API_VERSION`. Bumped only on a breaking change to this file's shapes. */
|
|
111
|
+
export const CHANNEL_API_VERSION = 1;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Structured outcome of resolving a confirmation token, distinguishing cases the
|
|
115
|
+
* string-returning `confirm` collapses together. `not-a-token` = the input isn't
|
|
116
|
+
* token-shaped (run the normal flow); `not-found` = token-shaped but no matching
|
|
117
|
+
* pending confirmation; `ok`/`failed` = the staged action was consumed and run.
|
|
118
|
+
* Returned by `ctx.resolveConfirmation` — the value the core's `resolveConfirmation`
|
|
119
|
+
* produces. A channel that needs to branch on acceptance (the HTTP `/confirm`
|
|
120
|
+
* endpoint's `resolved` flag) uses it; one that only needs a reply string uses `confirm`.
|
|
121
|
+
*/
|
|
122
|
+
export type ConfirmOutcome =
|
|
123
|
+
| { status: "not-a-token" }
|
|
124
|
+
| { status: "not-found" }
|
|
125
|
+
| { status: "ok"; data: unknown }
|
|
126
|
+
| { status: "failed"; error: string };
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Read-only introspection getters the core injects for a channel that exposes an
|
|
130
|
+
* API/UI (the HTTP surface today). Every getter reads state that already exists
|
|
131
|
+
* in-process — nothing computes anything new — so these can't come from `env`.
|
|
132
|
+
* Returns are `unknown`/primitive by design, to keep this contract free of any
|
|
133
|
+
* domain types. Tokens are never exposed.
|
|
134
|
+
*/
|
|
135
|
+
export type ChannelHostReads = {
|
|
136
|
+
manifest: () => unknown;
|
|
137
|
+
pendingConfirmations: () => unknown;
|
|
138
|
+
/** A conversation's durable verbatim transcript, chronological, paginated. */
|
|
139
|
+
conversation: (sessionKey: string, limit: number, offset?: string) => Promise<unknown>;
|
|
140
|
+
/** The known conversations, most-recently-active first. */
|
|
141
|
+
conversations: (limit: number) => Promise<unknown>;
|
|
142
|
+
wikiList: () => Promise<unknown>;
|
|
143
|
+
wikiRead: (path: string) => Promise<unknown>;
|
|
144
|
+
wikiGrep: (pattern: string) => Promise<unknown>;
|
|
145
|
+
memoryScroll: (collection: string, limit: number, offset?: string) => Promise<unknown>;
|
|
146
|
+
toolLog: () => unknown;
|
|
147
|
+
health: () => Promise<unknown>;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The minimal capabilities every channel gets from the core — the intersection
|
|
152
|
+
* across terminal, HTTP and Google Chat, nothing channel-specific. Anything
|
|
153
|
+
* specific (Google Chat's credentials, HTTP's port) the channel reads from `env`
|
|
154
|
+
* in its own `build()`. This is the dependency-inversion seam: the core injects
|
|
155
|
+
* these, the channel imports none of them.
|
|
156
|
+
*
|
|
157
|
+
* `env`, `log` and `confirm` are the floor every channel relies on.
|
|
158
|
+
* `resolveConfirmation` and `reads` are optional in-process capabilities that
|
|
159
|
+
* can't come from `env`: the core populates them, only a channel that needs them
|
|
160
|
+
* (HTTP) reads them, the others ignore them.
|
|
161
|
+
*/
|
|
162
|
+
export type ChannelRuntimeContext = {
|
|
163
|
+
/** The process env, so a channel reads its own config (subscription, credentials, port) without the core knowing which keys it needs. */
|
|
164
|
+
env: Record<string, string | undefined>;
|
|
165
|
+
/** stderr logger for the channel's own lifecycle/errors. */
|
|
166
|
+
log: (msg: string) => void;
|
|
167
|
+
/**
|
|
168
|
+
* Resolves a confirmation token against the core's single `ConfirmationStore`,
|
|
169
|
+
* already bound to this instance's store/vault/writer. The channel calls it
|
|
170
|
+
* and holds no state: staging and the store stay the core's. Returns the reply
|
|
171
|
+
* text to send back, or `null` when the input wasn't token-shaped (see
|
|
172
|
+
* `resolveConfirmation`).
|
|
173
|
+
*/
|
|
174
|
+
confirm: (token: string, sessionKey: string, userId: string) => Promise<string | null>;
|
|
175
|
+
/** The structured sibling of `confirm` (see `ConfirmOutcome`), for a channel that branches on whether the token was accepted. */
|
|
176
|
+
resolveConfirmation?: (token: string, sessionKey: string, userId: string) => Promise<ConfirmOutcome>;
|
|
177
|
+
/** In-process introspection getters for a channel that exposes an API/UI. */
|
|
178
|
+
reads?: ChannelHostReads;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A channel plugin: the value the core's channel loader consumes, mirroring
|
|
183
|
+
* `@mercury-fw/plugin-types`' `Plugin`. A channel package exports one and does not
|
|
184
|
+
* import the app.
|
|
185
|
+
*
|
|
186
|
+
* - `apiVersion`: contract version (see `CHANNEL_API_VERSION`); the loader refuses a mismatch.
|
|
187
|
+
* - `name`: the channel's id, and the key the loader registers the provider under (the cron layer looks up `"google-chat"` for its `Notifier`).
|
|
188
|
+
* - `build`: builds the `Provider` from the context, or `undefined` when the instance isn't configured for this channel (e.g. no `GOOGLE_CHAT_PUBSUB_SUBSCRIPTION`) → the loader treats it as "present but inert" and never starts it.
|
|
189
|
+
*/
|
|
190
|
+
export type ChannelPlugin = {
|
|
191
|
+
apiVersion: number;
|
|
192
|
+
name: string;
|
|
193
|
+
build: (ctx: ChannelRuntimeContext) => Provider | undefined;
|
|
194
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mercury-fw/channel-types",
|
|
3
|
+
"version": "0.25.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/lucabro81/mercury-fw.git",
|
|
8
|
+
"directory": "packages/types/channel-types"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"*.ts",
|
|
12
|
+
"dist",
|
|
13
|
+
"CHANGELOG.md"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"mercury-fw-source": "./index.ts",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./index.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@mercury-fw/plugin-types": "0.25.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@mercury-fw/typescript-config": "*",
|
|
33
|
+
"@types/bun": "^1.4.0",
|
|
34
|
+
"typescript": "^6.0.3"
|
|
35
|
+
}
|
|
36
|
+
}
|