@wakeshift/sdk 0.0.0-stage → 0.2.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 +25 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +198 -2
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +195 -0
- package/dist/events.js +523 -0
- package/dist/events.js.map +1 -0
- package/dist/guest.d.ts +119 -0
- package/dist/guest.js +506 -0
- package/dist/guest.js.map +1 -0
- package/dist/harness.d.ts +6 -0
- package/dist/harness.js +78 -0
- package/dist/harness.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/key.d.ts +29 -0
- package/dist/key.js +107 -0
- package/dist/key.js.map +1 -0
- package/dist/limits.d.ts +19 -0
- package/dist/limits.js +105 -0
- package/dist/limits.js.map +1 -0
- package/dist/retry.d.ts +1 -0
- package/dist/retry.js +58 -0
- package/dist/retry.js.map +1 -0
- package/dist/sealed.d.ts +4 -0
- package/dist/sealed.js +38 -0
- package/dist/sealed.js.map +1 -0
- package/dist/spec.d.ts +28 -0
- package/dist/spec.js +43 -0
- package/dist/spec.js.map +1 -0
- package/dist/wakeshift.d.ts +415 -0
- package/dist/wakeshift.js +481 -0
- package/dist/wakeshift.js.map +1 -0
- package/package.json +48 -4
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { WakeshiftError } from "./errors.js";
|
|
3
|
+
import { WakeEvent, attachEvents } from "./events.js";
|
|
4
|
+
import { parseKey, signAssertion } from "./key.js";
|
|
5
|
+
import { retryRefused } from "./retry.js";
|
|
6
|
+
/** Wakeshift's production core, the default for every key but an older wst_test_ one. Override with `core` or WAKESHIFT_CORE. */
|
|
7
|
+
export const DEFAULT_CORE = "https://app.wakeshift.ai";
|
|
8
|
+
/** The tool argument an agent passes its session key in (a result gives it one). */
|
|
9
|
+
export const SESSION_ARG = "session_key";
|
|
10
|
+
/** The tool argument an agent says which program it runs in with. */
|
|
11
|
+
export const HARNESS_ARG = "harness";
|
|
12
|
+
/**
|
|
13
|
+
* The tool argument an agent passes its own ID for the conversation in,
|
|
14
|
+
* when its program gives it one (Cursor and Grok Bot: CURSOR_CONVERSATION_ID).
|
|
15
|
+
*/
|
|
16
|
+
export const CONVERSATION_ARG = "conversation_id";
|
|
17
|
+
// What the agent is told each argument is (the core's own words:
|
|
18
|
+
// internal/harnesses, whose tests check these match).
|
|
19
|
+
const SESSION_DESCRIPTION = "The session key an earlier result gave you, if it did. It's a secret: keep it to this conversation; don't share or log it";
|
|
20
|
+
const HARNESS_DESCRIPTION = "The program you're running in: codex, claude-code, cursor (local CLI or IDE), cursor-cloud (Cursor cloud agent), grok-bot, copilot-cli, opencode, antigravity, openclaw (even when it runs your turns in Codex), hermes, chatgpt, or your program's name if it isn't listed";
|
|
21
|
+
const CONVERSATION_DESCRIPTION = "Your own ID for this conversation, if your program gives you one. Cursor and Grok Bot: the output of `printenv CURSOR_CONVERSATION_ID`. Codex and Antigravity send it automatically; others leave it empty";
|
|
22
|
+
/** What the SDK says, once, when Wakeshift recognised no harness in a tool call. */
|
|
23
|
+
export const UNKNOWN_CLIENT_WARNING = "wakeshift: Wakeshift couldn't tell which agent made a tool call (no MCP client name, nothing it knows in the " +
|
|
24
|
+
"call's _meta or User-Agent, and no harness argument), so it treats the agent as one it can't wake (its messages are held " +
|
|
25
|
+
"for its next call to the tool). The name comes from the client's initialize request, which a server created per request " +
|
|
26
|
+
"(stateless Streamable HTTP) never sees; Claude Code, Codex, Antigravity, Copilot CLI, opencode and Cursor are recognised " +
|
|
27
|
+
"without it. Spread ...wake.args into the tool's input and pass args to wake.call, so the agent can say (harness); for " +
|
|
28
|
+
"other clients, keep one McpServer per MCP session (a stateful transport), or pass the name yourself: " +
|
|
29
|
+
"wake.call(extra, { client }) or wake.callOf({ clientName }).";
|
|
30
|
+
// userAgentOf is the User-Agent of the HTTP request a tool call came in
|
|
31
|
+
// ("" if there's none, e.g. over stdio).
|
|
32
|
+
function userAgentOf(extra) {
|
|
33
|
+
const headers = extra?.requestInfo?.headers;
|
|
34
|
+
if (!headers)
|
|
35
|
+
return "";
|
|
36
|
+
const v = headers["user-agent"] ?? headers["User-Agent"];
|
|
37
|
+
return (Array.isArray(v) ? v[0] : v) ?? "";
|
|
38
|
+
}
|
|
39
|
+
export function clientNameOf(s) {
|
|
40
|
+
const info = "getClientVersion" in s ? s.getClientVersion() : s.server.getClientVersion();
|
|
41
|
+
return info?.name ?? "";
|
|
42
|
+
}
|
|
43
|
+
/** Adds Wakeshift's text and _meta to a tool result (a copy). */
|
|
44
|
+
export function applyAddition(a, base) {
|
|
45
|
+
let out;
|
|
46
|
+
if (typeof base === "string") {
|
|
47
|
+
const text = (base.trim() + "\n\n" + (a.text ?? "")).trim();
|
|
48
|
+
out = { content: text ? [{ type: "text", text }] : [] };
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
out = { ...base, content: [...(base.content ?? [])] };
|
|
52
|
+
if (a.text)
|
|
53
|
+
out.content.push({ type: "text", text: a.text });
|
|
54
|
+
}
|
|
55
|
+
if (a.meta && Object.keys(a.meta).length > 0)
|
|
56
|
+
out._meta = { ...(out._meta ?? {}), ...a.meta };
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
const argsShape = {
|
|
60
|
+
[SESSION_ARG]: z.string().optional().describe(SESSION_DESCRIPTION),
|
|
61
|
+
[HARNESS_ARG]: z.string().optional().describe(HARNESS_DESCRIPTION),
|
|
62
|
+
[CONVERSATION_ARG]: z.string().optional().describe(CONVERSATION_DESCRIPTION),
|
|
63
|
+
};
|
|
64
|
+
// str is a tool argument's value if it's a string.
|
|
65
|
+
function str(args, name) {
|
|
66
|
+
return typeof args?.[name] === "string" ? args[name] : undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The Wakeshift client for an app: subscribe the conversations that call
|
|
70
|
+
* your tools to topics, and publish to them.
|
|
71
|
+
*/
|
|
72
|
+
export class Wakeshift {
|
|
73
|
+
/**
|
|
74
|
+
* An older key's mode (wst_test_…, wst_live_…); null for a current key.
|
|
75
|
+
* @deprecated Modes mean nothing now, and current keys have none.
|
|
76
|
+
*/
|
|
77
|
+
mode;
|
|
78
|
+
core;
|
|
79
|
+
key;
|
|
80
|
+
// The app's ID: from the key, if it names the app, or else from app().
|
|
81
|
+
knownAppId;
|
|
82
|
+
appInfo;
|
|
83
|
+
fetchFn;
|
|
84
|
+
timeoutMs;
|
|
85
|
+
warnFn;
|
|
86
|
+
warned = new Set();
|
|
87
|
+
constructor(opts) {
|
|
88
|
+
if (!opts?.key)
|
|
89
|
+
throw new Error("wakeshift: no app key (WAKESHIFT_KEY?)");
|
|
90
|
+
this.key = parseKey(opts.key);
|
|
91
|
+
this.knownAppId = this.key.appId;
|
|
92
|
+
this.mode = this.key.mode;
|
|
93
|
+
// Test keys come from staging and local consoles, and one sent to
|
|
94
|
+
// production fails in a way that's hard to read: ask for the core.
|
|
95
|
+
const core = opts.core || process.env.WAKESHIFT_CORE || (this.mode === "test" ? "" : DEFAULT_CORE);
|
|
96
|
+
if (!core) {
|
|
97
|
+
throw new Error("wakeshift: no core URL for this test key: set WAKESHIFT_CORE, or pass { core }, to the address of the console the key came from (for example https://app.staging.wakeshift.ai)");
|
|
98
|
+
}
|
|
99
|
+
this.core = core.replace(/\/+$/, "");
|
|
100
|
+
// A request the load balancer refused during a rollout is sent again (retry.ts).
|
|
101
|
+
this.fetchFn = retryRefused(opts.fetch ?? globalThis.fetch);
|
|
102
|
+
this.timeoutMs = opts.timeoutMs ?? 20_000;
|
|
103
|
+
this.warnFn = opts.onWarning ?? ((m) => console.warn(m));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The app's ID. An older key (wst_test_…, wst_live_…) names it; for a
|
|
107
|
+
* current key, which doesn't, this throws until `await wake.app()` has
|
|
108
|
+
* asked Wakeshift.
|
|
109
|
+
*/
|
|
110
|
+
get appId() {
|
|
111
|
+
if (!this.knownAppId)
|
|
112
|
+
throw new Error("wakeshift: call await wake.app() first: this key doesn't name its app");
|
|
113
|
+
return this.knownAppId;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Which app the key is, as Wakeshift says: its ID and name. Asks once
|
|
117
|
+
* (GET /api/apps/v1/app) and remembers the answer; afterwards
|
|
118
|
+
* `wake.appId` works for any key.
|
|
119
|
+
*/
|
|
120
|
+
app() {
|
|
121
|
+
this.appInfo ??= this.request("GET", "/app")
|
|
122
|
+
.then((r) => {
|
|
123
|
+
if (typeof r.id !== "string" || !r.id) {
|
|
124
|
+
throw new WakeshiftError(200, "bad_response", `Wakeshift at ${this.core} didn't say which app this key is`);
|
|
125
|
+
}
|
|
126
|
+
const id = this.knownAppId ?? r.id;
|
|
127
|
+
this.knownAppId = id;
|
|
128
|
+
return { id, name: typeof r.name === "string" ? r.name : "" };
|
|
129
|
+
})
|
|
130
|
+
.catch((err) => {
|
|
131
|
+
this.appInfo = undefined; // a later call asks again
|
|
132
|
+
throw err;
|
|
133
|
+
});
|
|
134
|
+
return this.appInfo;
|
|
135
|
+
}
|
|
136
|
+
/** @internal The app's ID if it's known yet (the key names it, or app() has answered), else null. */
|
|
137
|
+
knownApp() {
|
|
138
|
+
return this.knownAppId;
|
|
139
|
+
}
|
|
140
|
+
// A fresh assertion of the app's identity, naming the app once it's known.
|
|
141
|
+
assertion() {
|
|
142
|
+
return signAssertion(this.key, this.core, Date.now(), this.knownAppId);
|
|
143
|
+
}
|
|
144
|
+
/** @internal Reports a problem through onWarning, every time. */
|
|
145
|
+
warn(message) {
|
|
146
|
+
this.warnFn(message);
|
|
147
|
+
}
|
|
148
|
+
/** Reports a warning, once per kind. */
|
|
149
|
+
warnOnce(kind, message) {
|
|
150
|
+
if (this.warned.has(kind))
|
|
151
|
+
return;
|
|
152
|
+
this.warned.add(kind);
|
|
153
|
+
this.warnFn(message);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The arguments the agent passes Wakeshift's part of the call in, as a
|
|
157
|
+
* zod shape for your subscribe tool's input (and any other tool whose
|
|
158
|
+
* results you annotate): `inputSchema: { repo: z.string(), ...wake.args }`.
|
|
159
|
+
* session_key (the key a result gives the conversation), harness (the
|
|
160
|
+
* program the agent runs in) and conversation_id (its own ID for the
|
|
161
|
+
* conversation, where its program gives one). Wakeshift adds nothing to
|
|
162
|
+
* tools you don't spread it into.
|
|
163
|
+
*/
|
|
164
|
+
get args() {
|
|
165
|
+
return argsShape;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Packs the tool call from an MCP tool callback's `extra`: its _meta and
|
|
169
|
+
* User-Agent, the client's name (from `server`, or pass `client`), the
|
|
170
|
+
* user, and session_key, harness and conversation_id from `args`.
|
|
171
|
+
*/
|
|
172
|
+
call(extra, opts = {}) {
|
|
173
|
+
let client = opts.client;
|
|
174
|
+
if (client === undefined) {
|
|
175
|
+
if (!opts.server)
|
|
176
|
+
throw new Error("wakeshift: wake.call needs the MCP client's name: pass { server } (your McpServer), or { client }");
|
|
177
|
+
client = clientNameOf(opts.server);
|
|
178
|
+
}
|
|
179
|
+
const args = opts.args;
|
|
180
|
+
return this.callOf({
|
|
181
|
+
meta: extra?._meta,
|
|
182
|
+
clientName: client,
|
|
183
|
+
user: opts.user,
|
|
184
|
+
session: opts.session ?? str(args, SESSION_ARG),
|
|
185
|
+
userAgent: userAgentOf(extra),
|
|
186
|
+
harness: opts.harness ?? str(args, HARNESS_ARG),
|
|
187
|
+
conversation: opts.conversation ?? str(args, CONVERSATION_ARG),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/** Packs a tool call without an MCP framework. */
|
|
191
|
+
callOf(c) {
|
|
192
|
+
const out = { client: c.clientName ?? "" };
|
|
193
|
+
if (c.user)
|
|
194
|
+
out.user = c.user;
|
|
195
|
+
if (c.meta && Object.keys(c.meta).length > 0)
|
|
196
|
+
out.meta = c.meta;
|
|
197
|
+
if (c.session)
|
|
198
|
+
out.session = c.session;
|
|
199
|
+
if (c.userAgent)
|
|
200
|
+
out.user_agent = c.userAgent;
|
|
201
|
+
if (c.harness?.trim())
|
|
202
|
+
out.harness = c.harness.trim();
|
|
203
|
+
if (c.conversation?.trim())
|
|
204
|
+
out.conversation = c.conversation.trim();
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
// recognised reads what the core recognised the caller as, and warns
|
|
208
|
+
// once if it was nothing and the agent didn't say (an older core that
|
|
209
|
+
// doesn't say: no warning).
|
|
210
|
+
recognised(agent) {
|
|
211
|
+
if (agent && !agent.harness && !agent.claimed)
|
|
212
|
+
this.warnOnce("unknown_client", UNKNOWN_CLIENT_WARNING);
|
|
213
|
+
return { harness: agent?.harness ?? null, via: agent?.via ?? null, claimed: agent?.claimed ?? null };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Subscribes the calling conversation to topic (again: refreshes it).
|
|
217
|
+
* Check the caller may see the topic first: Wakeshift only refuses another
|
|
218
|
+
* user's u/<user>/… topics.
|
|
219
|
+
*/
|
|
220
|
+
async subscribe(call, topic, opts = {}) {
|
|
221
|
+
const body = { call, topic, once: !!opts.once };
|
|
222
|
+
if (opts.filter && Object.keys(opts.filter).length > 0)
|
|
223
|
+
body.filter = opts.filter;
|
|
224
|
+
if (opts.replay !== undefined)
|
|
225
|
+
body.replay = opts.replay;
|
|
226
|
+
if (opts.ttl !== undefined)
|
|
227
|
+
body.ttl = opts.ttl;
|
|
228
|
+
const out = await this.request("POST", "/topics/subscribe", body);
|
|
229
|
+
const agent = this.recognised(out.agent);
|
|
230
|
+
const addition = out.addition ?? {};
|
|
231
|
+
const s = out.subscription;
|
|
232
|
+
return {
|
|
233
|
+
id: s.id,
|
|
234
|
+
topic: s.topic,
|
|
235
|
+
filter: s.filter,
|
|
236
|
+
once: s.once,
|
|
237
|
+
expiresAt: new Date(s.expires_at),
|
|
238
|
+
session: out.session,
|
|
239
|
+
addition,
|
|
240
|
+
agent,
|
|
241
|
+
result: (text) => applyAddition(addition, text),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** Ends the conversation's subscription to topic, or to all the app's topics. Returns how many ended. */
|
|
245
|
+
async unsubscribe(call, topic) {
|
|
246
|
+
const out = await this.request("POST", "/topics/unsubscribe", topic ? { call, topic } : { call });
|
|
247
|
+
return out.unsubscribed;
|
|
248
|
+
}
|
|
249
|
+
/** Wakes every conversation subscribed to one of topics whose filter the event passes (each once). */
|
|
250
|
+
async publish(topics, event) {
|
|
251
|
+
const out = await this.request("POST", "/topics/publish", { topics: typeof topics === "string" ? [topics] : topics, event });
|
|
252
|
+
return { matched: out.matched, duplicate: !!out.duplicate };
|
|
253
|
+
}
|
|
254
|
+
/** Wakes exactly one subscription's conversation, whatever its filter. */
|
|
255
|
+
async send(subscriptionId, event) {
|
|
256
|
+
const out = await this.request("POST", `/subscriptions/${encodeURIComponent(subscriptionId)}/send`, { event });
|
|
257
|
+
return out.status;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* How many conversations are subscribed to topic, and how many are
|
|
261
|
+
* connected now, and how. Only reads: nothing is delivered, minted or ended.
|
|
262
|
+
*/
|
|
263
|
+
async topicStatus(topic) {
|
|
264
|
+
const out = await this.request("GET", `/topics/status?topic=${encodeURIComponent(topic)}`);
|
|
265
|
+
return { topic: out.topic, subscriptions: out.subscriptions, connected: out.connected, via: { stream: 0, poll: 0, hooks: 0, webhook: 0, ...out.via } };
|
|
266
|
+
}
|
|
267
|
+
/** Whether a subscription is active and its conversation connected now, and how. Only reads. */
|
|
268
|
+
async subscriptionStatus(subscriptionId) {
|
|
269
|
+
const out = await this.request("GET", `/subscriptions/${encodeURIComponent(subscriptionId)}`);
|
|
270
|
+
const s = { id: out.id, topic: out.topic, status: out.status, connected: out.connected };
|
|
271
|
+
if (out.via)
|
|
272
|
+
s.via = out.via;
|
|
273
|
+
if (out.expires_at)
|
|
274
|
+
s.expiresAt = new Date(out.expires_at);
|
|
275
|
+
return s;
|
|
276
|
+
}
|
|
277
|
+
/** A capability URL that publishes to topic: a POST's body becomes an event. */
|
|
278
|
+
async publishUrl(topic) {
|
|
279
|
+
return this.request("POST", "/topics/publish-urls", { topic });
|
|
280
|
+
}
|
|
281
|
+
/** Stops a publish URL working, a topic's or an event's (410 from then on). */
|
|
282
|
+
async revokePublishUrl(id) {
|
|
283
|
+
await this.request("DELETE", `/events/publish-urls/${encodeURIComponent(id)}`);
|
|
284
|
+
}
|
|
285
|
+
/** What Wakeshift would add to any other tool result for this conversation (creates nothing). */
|
|
286
|
+
async addition(call) {
|
|
287
|
+
const out = await this.request("POST", "/topics/annotate", { call });
|
|
288
|
+
return { ...out, agent: this.recognised(out.agent) };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Adds what Wakeshift has for the calling conversation to a tool result
|
|
292
|
+
* of yours (a copy): what's waiting for a harness that can't be woken, a
|
|
293
|
+
* fresh connector beacon while a Codex thread's connector isn't running.
|
|
294
|
+
* Only results you pass here (and a subscription's result()) change.
|
|
295
|
+
*/
|
|
296
|
+
async annotate(call, result) {
|
|
297
|
+
return applyAddition(await this.addition(call), result);
|
|
298
|
+
}
|
|
299
|
+
/** Ends every subscription to topic; each conversation is told. Returns how many ended. */
|
|
300
|
+
async closeTopic(topic) {
|
|
301
|
+
return (await this.request("POST", "/topics/close", { topic })).closed;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Ends one subscription by its id, telling its conversation that the app
|
|
305
|
+
* ended it. Returns false if it had already ended (or there's no such
|
|
306
|
+
* subscription of the app's).
|
|
307
|
+
*/
|
|
308
|
+
async endSubscription(subscriptionId) {
|
|
309
|
+
return (await this.request("POST", `/subscriptions/${encodeURIComponent(subscriptionId)}/end`, {})).ended;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Ends the subscriptions of every conversation of one of the app's users
|
|
313
|
+
* to topic, or to all of the app's topics (they lost access to something),
|
|
314
|
+
* telling each conversation. Returns how many ended.
|
|
315
|
+
*/
|
|
316
|
+
async unsubscribeUser(user, topic) {
|
|
317
|
+
return (await this.request("POST", "/topics/unsubscribe", topic ? { user, topic } : { user })).unsubscribed;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Deletes everything Wakeshift keeps for one of the app's users: their
|
|
321
|
+
* conversations, subscriptions, events and deliveries, recent publishes to
|
|
322
|
+
* their u/<user>/… topics and publish URLs for them, and their ID. Open
|
|
323
|
+
* streams close. Returns false if there was nothing to forget.
|
|
324
|
+
*/
|
|
325
|
+
async forgetUser(user) {
|
|
326
|
+
return (await this.request("POST", "/users/forget", { user })).forgotten;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Tells Wakeshift what the app may do, replacing what it said before.
|
|
330
|
+
* The events you defined (defineEvent) are added unless m has events of
|
|
331
|
+
* its own. Returns the manifest as Wakeshift keeps it.
|
|
332
|
+
*/
|
|
333
|
+
async publishManifest(m) {
|
|
334
|
+
const body = m.events === undefined && this.events.size > 0 ? { ...m, events: [...this.events.values()].map((e) => e.manifest) } : m;
|
|
335
|
+
const out = await this.request("PUT", "/manifest", body);
|
|
336
|
+
this.manifestBase = m;
|
|
337
|
+
this.published = Promise.resolve();
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
// ── Events (MCP Events, natively or bridged) ──────────────────────────
|
|
341
|
+
events = new Map();
|
|
342
|
+
manifestBase = {};
|
|
343
|
+
published = null;
|
|
344
|
+
/**
|
|
345
|
+
* Defines one of the app's event types: what a subscriber picks (args),
|
|
346
|
+
* what each occurrence carries (payload), and the line an agent reads
|
|
347
|
+
* first (text). Returns the event, whose emit sends occurrences. Define
|
|
348
|
+
* every event before attach.
|
|
349
|
+
*
|
|
350
|
+
* const message = wake.defineEvent("chat.message", {
|
|
351
|
+
* description: "A new message in a channel",
|
|
352
|
+
* args: z.object({ channel: z.string() }),
|
|
353
|
+
* payload: z.object({ from: z.string(), text: z.string() }),
|
|
354
|
+
* text: (m) => `${m.from}: ${m.text}`,
|
|
355
|
+
* })
|
|
356
|
+
* await message.emit({ channel: "eng" }, { from: "ada", text: "shipped" })
|
|
357
|
+
*/
|
|
358
|
+
defineEvent(name, opts) {
|
|
359
|
+
if (this.events.has(name))
|
|
360
|
+
throw new Error(`wakeshift: event ${name} is defined twice`);
|
|
361
|
+
const ev = new WakeEvent(this, name, opts);
|
|
362
|
+
this.events.set(name, ev);
|
|
363
|
+
this.published = null; // the manifest changed
|
|
364
|
+
return ev;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Attaches Wakeshift to your MCP server, before you connect it: the one
|
|
368
|
+
* call that connects the two. Today it serves the defined events: MCP
|
|
369
|
+
* Events (events/list, events/subscribe, events/unsubscribe) for clients
|
|
370
|
+
* that speak it, such as ChatGPT, and the subscribe_events tool for
|
|
371
|
+
* everyone else, whom Wakeshift wakes its own ways; plus
|
|
372
|
+
* unsubscribe_events and list_subscriptions for every client, so a person
|
|
373
|
+
* can always see and stop what they're subscribed to.
|
|
374
|
+
*
|
|
375
|
+
* wake.attach(server, { identify: (extra) => ({ user: guestOf(extra)?.user }) })
|
|
376
|
+
*/
|
|
377
|
+
attach(server, opts = {}) {
|
|
378
|
+
attachEvents(this, server, [...this.events.values()], opts);
|
|
379
|
+
}
|
|
380
|
+
/** @internal Publishes the manifest with the defined events, once (again after a defineEvent). */
|
|
381
|
+
ensureEvents() {
|
|
382
|
+
if (!this.published) {
|
|
383
|
+
const p = this.publishManifest(this.manifestBase).then(() => undefined);
|
|
384
|
+
this.published = p;
|
|
385
|
+
p.catch(() => {
|
|
386
|
+
if (this.published === p)
|
|
387
|
+
this.published = null; // try again next time
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
return this.published;
|
|
391
|
+
}
|
|
392
|
+
/** @internal An event's emit. */
|
|
393
|
+
async emitEvent(e) {
|
|
394
|
+
await this.ensureEvents();
|
|
395
|
+
const body = { name: e.name, arguments: e.arguments, payload: e.payload, text: e.text };
|
|
396
|
+
if (e.id)
|
|
397
|
+
body.id = e.id;
|
|
398
|
+
if (e.to)
|
|
399
|
+
body.to = e.to;
|
|
400
|
+
const out = await this.request("POST", "/events/emit", body);
|
|
401
|
+
return { matched: out.matched, duplicate: !!out.duplicate };
|
|
402
|
+
}
|
|
403
|
+
/** @internal A call to the app API (the events machinery's), after the manifest with the defined events is published. */
|
|
404
|
+
async requestApp(method, path, body) {
|
|
405
|
+
await this.ensureEvents();
|
|
406
|
+
return this.request(method, path, body);
|
|
407
|
+
}
|
|
408
|
+
// ── Guests' workspaces (GuestAuth) ─────────────────────────────────────
|
|
409
|
+
/**
|
|
410
|
+
* The workspace Wakeshift keeps for one of the app's users (user: the
|
|
411
|
+
* app's own ID for them, as in wake.call's user), creating it on first use.
|
|
412
|
+
* Topics keep that user's conversations there too. GuestAuth calls this;
|
|
413
|
+
* most apps never need to.
|
|
414
|
+
*/
|
|
415
|
+
async workspace(user, name) {
|
|
416
|
+
return (await this.request("POST", "/workspaces", { external_id: user, name })).workspace;
|
|
417
|
+
}
|
|
418
|
+
/** Creates an agent in one of the app's workspaces (if new), and its grant for the app's manifest scope. Returns the grant's ID. */
|
|
419
|
+
async addAgent(workspace, agent, displayName) {
|
|
420
|
+
return (await this.request("POST", `/workspaces/${encodeURIComponent(workspace)}/agents`, { agent, display_name: displayName })).grant;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Checks that one of the app's agents still has a live grant, asking
|
|
424
|
+
* Wakeshift each time. Throws a WakeshiftError: no_grant once the grant is
|
|
425
|
+
* revoked, unknown_workspace once the workspace is forgotten.
|
|
426
|
+
*/
|
|
427
|
+
async checkAgent(workspace, agent) {
|
|
428
|
+
await this.request("POST", `/workspaces/${encodeURIComponent(workspace)}/agents/${encodeURIComponent(agent)}/token`);
|
|
429
|
+
}
|
|
430
|
+
/** @internal The app key's seed: what GuestAuth keys its signed values from. Never send or log it. */
|
|
431
|
+
secretSeed() {
|
|
432
|
+
return this.key.seed;
|
|
433
|
+
}
|
|
434
|
+
async request(method, path, body) {
|
|
435
|
+
const headers = { Authorization: "Bearer " + this.assertion() };
|
|
436
|
+
if (body !== undefined)
|
|
437
|
+
headers["Content-Type"] = "application/json";
|
|
438
|
+
let resp;
|
|
439
|
+
let text;
|
|
440
|
+
try {
|
|
441
|
+
resp = await this.fetchFn(this.core + "/api/apps/v1" + path, {
|
|
442
|
+
method,
|
|
443
|
+
headers,
|
|
444
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
445
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
446
|
+
});
|
|
447
|
+
text = await resp.text();
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
throw this.unreachable(err);
|
|
451
|
+
}
|
|
452
|
+
let json;
|
|
453
|
+
try {
|
|
454
|
+
json = text ? JSON.parse(text) : undefined;
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
json = undefined;
|
|
458
|
+
}
|
|
459
|
+
if (!resp.ok) {
|
|
460
|
+
const e = json?.error ?? {};
|
|
461
|
+
throw new WakeshiftError(resp.status, e.code ?? "", e.message ?? (text.slice(0, 200) || resp.statusText), {
|
|
462
|
+
reason: typeof e.reason === "string" ? e.reason : undefined,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
// A 2xx that isn't Wakeshift's JSON: a proxy's page, or a wrong core.
|
|
466
|
+
if (json === null || typeof json !== "object") {
|
|
467
|
+
throw new WakeshiftError(resp.status, "bad_response", `Wakeshift at ${this.core} answered ${resp.status} without a JSON object. Check that WAKESHIFT_CORE, or the core option, is the address of the console your key came from`);
|
|
468
|
+
}
|
|
469
|
+
return json;
|
|
470
|
+
}
|
|
471
|
+
// A request that got no answer. Name the core it went to: a wrong or
|
|
472
|
+
// missing WAKESHIFT_CORE is the usual cause.
|
|
473
|
+
unreachable(err) {
|
|
474
|
+
const e = err;
|
|
475
|
+
const why = e?.name === "TimeoutError" || e?.name === "AbortError"
|
|
476
|
+
? `no answer within ${this.timeoutMs / 1000}s`
|
|
477
|
+
: [e?.message, e?.cause?.code ?? e?.cause?.message].filter(Boolean).join(": ") || String(err);
|
|
478
|
+
return new WakeshiftError(0, "unreachable", `can't reach Wakeshift at ${this.core} (${why}). Check that WAKESHIFT_CORE, or the core option, is the address of the console your key came from`, { cause: err });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
//# sourceMappingURL=wakeshift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wakeshift.js","sourceRoot":"","sources":["../src/wakeshift.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { AnySchema } from \"@modelcontextprotocol/sdk/server/zod-compat.js\";\nimport { WakeshiftError } from \"./errors.js\";\nimport { WakeEvent, attachEvents, type AttachOptions, type EventOptions, type EventTypeManifest } from \"./events.js\";\nimport { parseKey, signAssertion, type AppKey } from \"./key.js\";\nimport { retryRefused } from \"./retry.js\";\n\n/** Wakeshift's production core, the default for every key but an older wst_test_ one. Override with `core` or WAKESHIFT_CORE. */\nexport const DEFAULT_CORE = \"https://app.wakeshift.ai\";\n\n/** The tool argument an agent passes its session key in (a result gives it one). */\nexport const SESSION_ARG = \"session_key\";\n\n/** The tool argument an agent says which program it runs in with. */\nexport const HARNESS_ARG = \"harness\";\n\n/**\n * The tool argument an agent passes its own ID for the conversation in,\n * when its program gives it one (Cursor and Grok Bot: CURSOR_CONVERSATION_ID).\n */\nexport const CONVERSATION_ARG = \"conversation_id\";\n\n// What the agent is told each argument is (the core's own words:\n// internal/harnesses, whose tests check these match).\nconst SESSION_DESCRIPTION = \"The session key an earlier result gave you, if it did. It's a secret: keep it to this conversation; don't share or log it\";\nconst HARNESS_DESCRIPTION =\n \"The program you're running in: codex, claude-code, cursor (local CLI or IDE), cursor-cloud (Cursor cloud agent), grok-bot, copilot-cli, opencode, antigravity, openclaw (even when it runs your turns in Codex), hermes, chatgpt, or your program's name if it isn't listed\";\nconst CONVERSATION_DESCRIPTION =\n \"Your own ID for this conversation, if your program gives you one. Cursor and Grok Bot: the output of `printenv CURSOR_CONVERSATION_ID`. Codex and Antigravity send it automatically; others leave it empty\";\n\nexport interface WakeshiftOptions {\n /** The app key from the console: wst_ and 50 characters (or an older wst_test_… or wst_live_… key). */\n key: string;\n /**\n * Wakeshift's base URL: the console you got the key from. Default:\n * WAKESHIFT_CORE from the environment, else https://app.wakeshift.ai.\n * An older wst_test_… key has no default: it needs one of the two.\n */\n core?: string;\n /** Default: the global fetch. */\n fetch?: typeof fetch;\n /** Per-request timeout in milliseconds. Default 20000. */\n timeoutMs?: number;\n /**\n * Called with a problem that doesn't fail the call but makes it work less\n * well, for example a tool call Wakeshift recognised no harness in. Each kind is\n * reported once. Default: console.warn.\n */\n onWarning?: (message: string) => void;\n}\n\n/** Which app a key is, as Wakeshift says (wake.app()). */\nexport interface AppInfo {\n id: string;\n name: string;\n}\n\n/** What the SDK says, once, when Wakeshift recognised no harness in a tool call. */\nexport const UNKNOWN_CLIENT_WARNING =\n \"wakeshift: Wakeshift couldn't tell which agent made a tool call (no MCP client name, nothing it knows in the \" +\n \"call's _meta or User-Agent, and no harness argument), so it treats the agent as one it can't wake (its messages are held \" +\n \"for its next call to the tool). The name comes from the client's initialize request, which a server created per request \" +\n \"(stateless Streamable HTTP) never sees; Claude Code, Codex, Antigravity, Copilot CLI, opencode and Cursor are recognised \" +\n \"without it. Spread ...wake.args into the tool's input and pass args to wake.call, so the agent can say (harness); for \" +\n \"other clients, keep one McpServer per MCP session (a stateful transport), or pass the name yourself: \" +\n \"wake.call(extra, { client }) or wake.callOf({ clientName }).\";\n\n/**\n * Who called one of the app's tools, as Wakeshift needs it. Make one with\n * `wake.call(extra, …)` inside an MCP tool callback, or `wake.callOf(…)`.\n */\nexport interface Call {\n /** The app's own ID for the person (optional). */\n user?: string;\n /**\n * The MCP client's name (initialize's clientInfo.name). A stateless\n * server has none; Wakeshift then tells the harness from meta, else\n * user_agent.\n */\n client: string;\n /** The tool call's _meta (Codex sends its thread there). */\n meta?: Record<string, unknown>;\n /** The session handle an earlier result gave the agent. */\n session?: string;\n /** The MCP request's HTTP User-Agent. */\n user_agent?: string;\n /** The conversation's ID as the agent passed it (conversation_id: Cursor's CURSOR_CONVERSATION_ID). */\n conversation?: string;\n /** The program the agent says it runs in (harness): a hint, never a credential. */\n harness?: string;\n}\n\n/**\n * What Wakeshift recognised the calling conversation's harness as, and by\n * which signal (\"claim\": only the agent's harness argument named it); both\n * null when nothing named one. claimed is what the agent said (normalized:\n * \"Grok Bot\" is grok-bot), null if it didn't; a claimed that differs from\n * harness was ignored, since it contradicted what the call was recognised\n * as (the claim only refines it: Cursor's calls may be Grok Bot's).\n */\nexport interface Recognised {\n harness: string | null;\n via: \"client\" | \"meta\" | \"user_agent\" | \"claim\" | null;\n claimed: string | null;\n}\n\n/**\n * Seconds, or a string like \"90s\", \"5m\", \"2h\", \"30d\". Wakeshift checks the\n * value: days must be whole (\"1.5d\" is a bad_request; use \"36h\").\n */\nexport type Duration = number | `${number}${\"s\" | \"m\" | \"h\" | \"d\"}`;\n\nexport interface SubscribeOptions {\n /** Wake only for events whose data has these top-level fields equal to these values. */\n filter?: Record<string, string | number | boolean | null>;\n /** End after the first delivery. */\n once?: boolean;\n /** Deliver the latest event published within this long before subscribing (at most 24 hours). */\n replay?: Duration;\n /** End after this long without a delivery or a fresh subscribe (1 minute to 365 days; default 30 days). */\n ttl?: Duration;\n}\n\n/** What Wakeshift adds to a tool result: text for the agent, _meta for its harness. */\nexport interface Addition {\n text?: string;\n meta?: Record<string, unknown>;\n /** What Wakeshift recognised the caller's harness as (not added to the result). */\n agent?: Recognised;\n}\n\nexport interface Subscription {\n /** For `send`. */\n id: string;\n topic: string;\n filter?: Record<string, unknown>;\n once: boolean;\n expiresAt: Date;\n /**\n * The conversation's session key (the agent passes it back as session_key;\n * the result says so). It's a secret that acts as the conversation: don't\n * show, store or log it. To name the conversation, use id, or an HMAC of it.\n */\n session: string;\n /** What the tool result must carry for the caller's harness. */\n addition: Addition;\n /** What Wakeshift recognised the caller's harness as. */\n agent: Recognised;\n /**\n * The tool result to return: your text (or result) followed by\n * Wakeshift's, with its _meta.\n */\n result(text: string | CallToolResult): CallToolResult;\n}\n\nexport interface TopicEvent {\n /** What the agent reads first (at most 1000 bytes). */\n text: string;\n /** A JSON object, within the 64 KB request limit; filters match its top-level fields. */\n data?: Record<string, unknown>;\n /** Makes retries publish once for a day (at most 200 characters); after a day, the same id publishes again. */\n id?: string;\n}\n\nexport interface Published {\n /** Conversations it woke (or will). */\n matched: number;\n /** An earlier publish had this event's id. */\n duplicate: boolean;\n}\n\n/** delivered: on its way to a connected conversation; queued: waits for it; closed: the subscription ended. */\nexport type SendStatus = \"delivered\" | \"queued\" | \"closed\";\n\n/**\n * How a conversation is connected: stream (a stream held open, e.g.\n * Cursor's background command), poll (long polls or waits, e.g. Claude\n * Code's Monitor, in the last minute) or hooks (Wakeshift's hooks or\n * connector on the user's machine).\n */\nexport type Via = \"stream\" | \"poll\" | \"hooks\" | \"webhook\";\n\nexport interface TopicStatus {\n topic: string;\n /** Conversations subscribed (ended or expired subscriptions don't count). */\n subscriptions: number;\n /** Of those, connected now. */\n connected: number;\n /** How many are connected each way. */\n via: Record<Via, number>;\n}\n\nexport interface SubscriptionStatus {\n id: string;\n topic: string;\n status: \"active\" | \"closed\";\n connected: boolean;\n via?: Via;\n expiresAt?: Date;\n}\n\n/** A publish URL: `topic` for a topic's (`wake.publishUrl`), `event` for an event's (`event.publishUrl`). */\nexport interface PublishUrl {\n id: string;\n topic?: string;\n event?: string;\n url: string;\n}\n\n/** An event pattern a manifest asks for, and what the person is told it's for. */\nexport interface ScopeEvent {\n pattern: string;\n description: string;\n}\n\n/** What the app may do with the agents in the workspaces it keeps (manifest.agent). */\nexport interface AgentScope {\n wake: ScopeEvent[];\n post: ScopeEvent[];\n /** Days the person's consent lasts. */\n ttl_days: number;\n /** At most max wake-ups in any seconds, on top of Wakeshift's own limits. */\n wake_limits?: { seconds: number; max: number }[];\n /** What the person should say to their agent once it's connected; at most 120 characters. */\n start_prompt?: string;\n groups?: boolean;\n}\n\n/** What the app tells Wakeshift it may do (see wake.publishManifest). */\nexport interface Manifest {\n description?: string;\n agent?: AgentScope;\n /** The app's event types: defineEvent fills this in for you. */\n events?: EventTypeManifest[];\n}\n\n/** The parts of an MCP tool callback's `extra` that Wakeshift reads. */\nexport interface ToolExtra {\n _meta?: Record<string, unknown>;\n requestInfo?: { headers?: Record<string, string | string[] | undefined> };\n}\n\n// userAgentOf is the User-Agent of the HTTP request a tool call came in\n// (\"\" if there's none, e.g. over stdio).\nfunction userAgentOf(extra: ToolExtra | undefined): string {\n const headers = extra?.requestInfo?.headers;\n if (!headers) return \"\";\n const v = headers[\"user-agent\"] ?? headers[\"User-Agent\"];\n return (Array.isArray(v) ? v[0] : v) ?? \"\";\n}\n\n/** An McpServer or a low-level Server: anything that knows its client's name. */\nexport type ClientInfoSource =\n | { getClientVersion(): { name: string } | undefined }\n | { server: { getClientVersion(): { name: string } | undefined } };\n\nexport interface CallOptions {\n /** The MCP server the call came to, for the client's name (or pass client). */\n server?: ClientInfoSource;\n /** The client's name, if you know it (instead of server). */\n client?: string;\n /** The app's own ID for the person. */\n user?: string;\n /** The tool's arguments: session_key, harness and conversation_id (wake.args) are read from them. */\n args?: { [SESSION_ARG]?: string; [HARNESS_ARG]?: string; [CONVERSATION_ARG]?: string } | Record<string, unknown>;\n /** The session key, if you read it yourself. */\n session?: string;\n /** The program the agent says it runs in (harness), if you read it yourself. */\n harness?: string;\n /** The conversation's ID the agent passed (conversation_id), if you read it yourself. */\n conversation?: string;\n}\n\nexport function clientNameOf(s: ClientInfoSource): string {\n const info = \"getClientVersion\" in s ? s.getClientVersion() : s.server.getClientVersion();\n return info?.name ?? \"\";\n}\n\n/** Adds Wakeshift's text and _meta to a tool result (a copy). */\nexport function applyAddition(a: Addition, base: string | CallToolResult): CallToolResult {\n let out: CallToolResult;\n if (typeof base === \"string\") {\n const text = (base.trim() + \"\\n\\n\" + (a.text ?? \"\")).trim();\n out = { content: text ? [{ type: \"text\", text }] : [] };\n } else {\n out = { ...base, content: [...(base.content ?? [])] };\n if (a.text) out.content.push({ type: \"text\", text: a.text });\n }\n if (a.meta && Object.keys(a.meta).length > 0) out._meta = { ...(out._meta ?? {}), ...a.meta };\n return out;\n}\n\nconst argsShape = {\n [SESSION_ARG]: z.string().optional().describe(SESSION_DESCRIPTION),\n [HARNESS_ARG]: z.string().optional().describe(HARNESS_DESCRIPTION),\n [CONVERSATION_ARG]: z.string().optional().describe(CONVERSATION_DESCRIPTION),\n};\n\n// str is a tool argument's value if it's a string.\nfunction str(args: Record<string, unknown> | undefined, name: string): string | undefined {\n return typeof args?.[name] === \"string\" ? (args[name] as string) : undefined;\n}\n\n/**\n * The Wakeshift client for an app: subscribe the conversations that call\n * your tools to topics, and publish to them.\n */\nexport class Wakeshift {\n /**\n * An older key's mode (wst_test_…, wst_live_…); null for a current key.\n * @deprecated Modes mean nothing now, and current keys have none.\n */\n readonly mode: \"test\" | \"live\" | null;\n readonly core: string;\n private readonly key: AppKey;\n // The app's ID: from the key, if it names the app, or else from app().\n private knownAppId: string | null;\n private appInfo?: Promise<AppInfo>;\n private readonly fetchFn: typeof fetch;\n private readonly timeoutMs: number;\n private readonly warnFn: (message: string) => void;\n private readonly warned = new Set<string>();\n\n constructor(opts: WakeshiftOptions) {\n if (!opts?.key) throw new Error(\"wakeshift: no app key (WAKESHIFT_KEY?)\");\n this.key = parseKey(opts.key);\n this.knownAppId = this.key.appId;\n this.mode = this.key.mode;\n // Test keys come from staging and local consoles, and one sent to\n // production fails in a way that's hard to read: ask for the core.\n const core = opts.core || process.env.WAKESHIFT_CORE || (this.mode === \"test\" ? \"\" : DEFAULT_CORE);\n if (!core) {\n throw new Error(\n \"wakeshift: no core URL for this test key: set WAKESHIFT_CORE, or pass { core }, to the address of the console the key came from (for example https://app.staging.wakeshift.ai)\",\n );\n }\n this.core = core.replace(/\\/+$/, \"\");\n // A request the load balancer refused during a rollout is sent again (retry.ts).\n this.fetchFn = retryRefused(opts.fetch ?? globalThis.fetch);\n this.timeoutMs = opts.timeoutMs ?? 20_000;\n this.warnFn = opts.onWarning ?? ((m) => console.warn(m));\n }\n\n /**\n * The app's ID. An older key (wst_test_…, wst_live_…) names it; for a\n * current key, which doesn't, this throws until `await wake.app()` has\n * asked Wakeshift.\n */\n get appId(): string {\n if (!this.knownAppId) throw new Error(\"wakeshift: call await wake.app() first: this key doesn't name its app\");\n return this.knownAppId;\n }\n\n /**\n * Which app the key is, as Wakeshift says: its ID and name. Asks once\n * (GET /api/apps/v1/app) and remembers the answer; afterwards\n * `wake.appId` works for any key.\n */\n app(): Promise<AppInfo> {\n this.appInfo ??= this.request<{ id?: unknown; name?: unknown }>(\"GET\", \"/app\")\n .then((r) => {\n if (typeof r.id !== \"string\" || !r.id) {\n throw new WakeshiftError(200, \"bad_response\", `Wakeshift at ${this.core} didn't say which app this key is`);\n }\n const id = this.knownAppId ?? r.id;\n this.knownAppId = id;\n return { id, name: typeof r.name === \"string\" ? r.name : \"\" };\n })\n .catch((err: unknown) => {\n this.appInfo = undefined; // a later call asks again\n throw err;\n });\n return this.appInfo;\n }\n\n /** @internal The app's ID if it's known yet (the key names it, or app() has answered), else null. */\n knownApp(): string | null {\n return this.knownAppId;\n }\n\n // A fresh assertion of the app's identity, naming the app once it's known.\n private assertion(): string {\n return signAssertion(this.key, this.core, Date.now(), this.knownAppId);\n }\n\n /** @internal Reports a problem through onWarning, every time. */\n warn(message: string): void {\n this.warnFn(message);\n }\n\n /** Reports a warning, once per kind. */\n private warnOnce(kind: string, message: string): void {\n if (this.warned.has(kind)) return;\n this.warned.add(kind);\n this.warnFn(message);\n }\n\n /**\n * The arguments the agent passes Wakeshift's part of the call in, as a\n * zod shape for your subscribe tool's input (and any other tool whose\n * results you annotate): `inputSchema: { repo: z.string(), ...wake.args }`.\n * session_key (the key a result gives the conversation), harness (the\n * program the agent runs in) and conversation_id (its own ID for the\n * conversation, where its program gives one). Wakeshift adds nothing to\n * tools you don't spread it into.\n */\n get args(): typeof argsShape {\n return argsShape;\n }\n\n /**\n * Packs the tool call from an MCP tool callback's `extra`: its _meta and\n * User-Agent, the client's name (from `server`, or pass `client`), the\n * user, and session_key, harness and conversation_id from `args`.\n */\n call(extra: ToolExtra | undefined, opts: CallOptions = {}): Call {\n let client = opts.client;\n if (client === undefined) {\n if (!opts.server) throw new Error(\"wakeshift: wake.call needs the MCP client's name: pass { server } (your McpServer), or { client }\");\n client = clientNameOf(opts.server);\n }\n const args = opts.args as Record<string, unknown> | undefined;\n return this.callOf({\n meta: extra?._meta,\n clientName: client,\n user: opts.user,\n session: opts.session ?? str(args, SESSION_ARG),\n userAgent: userAgentOf(extra),\n harness: opts.harness ?? str(args, HARNESS_ARG),\n conversation: opts.conversation ?? str(args, CONVERSATION_ARG),\n });\n }\n\n /** Packs a tool call without an MCP framework. */\n callOf(c: {\n meta?: Record<string, unknown>;\n clientName: string;\n user?: string;\n session?: string;\n userAgent?: string;\n harness?: string;\n conversation?: string;\n }): Call {\n const out: Call = { client: c.clientName ?? \"\" };\n if (c.user) out.user = c.user;\n if (c.meta && Object.keys(c.meta).length > 0) out.meta = c.meta;\n if (c.session) out.session = c.session;\n if (c.userAgent) out.user_agent = c.userAgent;\n if (c.harness?.trim()) out.harness = c.harness.trim();\n if (c.conversation?.trim()) out.conversation = c.conversation.trim();\n return out;\n }\n\n // recognised reads what the core recognised the caller as, and warns\n // once if it was nothing and the agent didn't say (an older core that\n // doesn't say: no warning).\n private recognised(agent: Partial<Recognised> | undefined): Recognised {\n if (agent && !agent.harness && !agent.claimed) this.warnOnce(\"unknown_client\", UNKNOWN_CLIENT_WARNING);\n return { harness: agent?.harness ?? null, via: agent?.via ?? null, claimed: agent?.claimed ?? null };\n }\n\n /**\n * Subscribes the calling conversation to topic (again: refreshes it).\n * Check the caller may see the topic first: Wakeshift only refuses another\n * user's u/<user>/… topics.\n */\n async subscribe(call: Call, topic: string, opts: SubscribeOptions = {}): Promise<Subscription> {\n const body: Record<string, unknown> = { call, topic, once: !!opts.once };\n if (opts.filter && Object.keys(opts.filter).length > 0) body.filter = opts.filter;\n if (opts.replay !== undefined) body.replay = opts.replay;\n if (opts.ttl !== undefined) body.ttl = opts.ttl;\n const out = await this.request<{\n subscription: { id: string; topic: string; filter?: Record<string, unknown>; once: boolean; expires_at: string };\n session: string;\n addition: Addition;\n agent?: Recognised;\n }>(\"POST\", \"/topics/subscribe\", body);\n const agent = this.recognised(out.agent);\n const addition = out.addition ?? {};\n const s = out.subscription;\n return {\n id: s.id,\n topic: s.topic,\n filter: s.filter,\n once: s.once,\n expiresAt: new Date(s.expires_at),\n session: out.session,\n addition,\n agent,\n result: (text) => applyAddition(addition, text),\n };\n }\n\n /** Ends the conversation's subscription to topic, or to all the app's topics. Returns how many ended. */\n async unsubscribe(call: Call, topic?: string): Promise<number> {\n const out = await this.request<{ unsubscribed: number }>(\"POST\", \"/topics/unsubscribe\", topic ? { call, topic } : { call });\n return out.unsubscribed;\n }\n\n /** Wakes every conversation subscribed to one of topics whose filter the event passes (each once). */\n async publish(topics: string | string[], event: TopicEvent): Promise<Published> {\n const out = await this.request<Published>(\"POST\", \"/topics/publish\", { topics: typeof topics === \"string\" ? [topics] : topics, event });\n return { matched: out.matched, duplicate: !!out.duplicate };\n }\n\n /** Wakes exactly one subscription's conversation, whatever its filter. */\n async send(subscriptionId: string, event: TopicEvent): Promise<SendStatus> {\n const out = await this.request<{ status: SendStatus }>(\"POST\", `/subscriptions/${encodeURIComponent(subscriptionId)}/send`, { event });\n return out.status;\n }\n\n /**\n * How many conversations are subscribed to topic, and how many are\n * connected now, and how. Only reads: nothing is delivered, minted or ended.\n */\n async topicStatus(topic: string): Promise<TopicStatus> {\n const out = await this.request<Omit<TopicStatus, \"via\"> & { via?: Partial<Record<Via, number>> }>(\"GET\", `/topics/status?topic=${encodeURIComponent(topic)}`);\n return { topic: out.topic, subscriptions: out.subscriptions, connected: out.connected, via: { stream: 0, poll: 0, hooks: 0, webhook: 0, ...out.via } };\n }\n\n /** Whether a subscription is active and its conversation connected now, and how. Only reads. */\n async subscriptionStatus(subscriptionId: string): Promise<SubscriptionStatus> {\n const out = await this.request<{ id: string; topic: string; status: \"active\" | \"closed\"; connected: boolean; via?: Via; expires_at?: string }>(\n \"GET\",\n `/subscriptions/${encodeURIComponent(subscriptionId)}`,\n );\n const s: SubscriptionStatus = { id: out.id, topic: out.topic, status: out.status, connected: out.connected };\n if (out.via) s.via = out.via;\n if (out.expires_at) s.expiresAt = new Date(out.expires_at);\n return s;\n }\n\n /** A capability URL that publishes to topic: a POST's body becomes an event. */\n async publishUrl(topic: string): Promise<PublishUrl> {\n return this.request<PublishUrl>(\"POST\", \"/topics/publish-urls\", { topic });\n }\n\n /** Stops a publish URL working, a topic's or an event's (410 from then on). */\n async revokePublishUrl(id: string): Promise<void> {\n await this.request(\"DELETE\", `/events/publish-urls/${encodeURIComponent(id)}`);\n }\n\n /** What Wakeshift would add to any other tool result for this conversation (creates nothing). */\n async addition(call: Call): Promise<Addition> {\n const out = await this.request<Addition>(\"POST\", \"/topics/annotate\", { call });\n return { ...out, agent: this.recognised(out.agent) };\n }\n\n /**\n * Adds what Wakeshift has for the calling conversation to a tool result\n * of yours (a copy): what's waiting for a harness that can't be woken, a\n * fresh connector beacon while a Codex thread's connector isn't running.\n * Only results you pass here (and a subscription's result()) change.\n */\n async annotate(call: Call, result: CallToolResult): Promise<CallToolResult> {\n return applyAddition(await this.addition(call), result);\n }\n\n /** Ends every subscription to topic; each conversation is told. Returns how many ended. */\n async closeTopic(topic: string): Promise<number> {\n return (await this.request<{ closed: number }>(\"POST\", \"/topics/close\", { topic })).closed;\n }\n\n /**\n * Ends one subscription by its id, telling its conversation that the app\n * ended it. Returns false if it had already ended (or there's no such\n * subscription of the app's).\n */\n async endSubscription(subscriptionId: string): Promise<boolean> {\n return (await this.request<{ ended: boolean }>(\"POST\", `/subscriptions/${encodeURIComponent(subscriptionId)}/end`, {})).ended;\n }\n\n /**\n * Ends the subscriptions of every conversation of one of the app's users\n * to topic, or to all of the app's topics (they lost access to something),\n * telling each conversation. Returns how many ended.\n */\n async unsubscribeUser(user: string, topic?: string): Promise<number> {\n return (await this.request<{ unsubscribed: number }>(\"POST\", \"/topics/unsubscribe\", topic ? { user, topic } : { user })).unsubscribed;\n }\n\n /**\n * Deletes everything Wakeshift keeps for one of the app's users: their\n * conversations, subscriptions, events and deliveries, recent publishes to\n * their u/<user>/… topics and publish URLs for them, and their ID. Open\n * streams close. Returns false if there was nothing to forget.\n */\n async forgetUser(user: string): Promise<boolean> {\n return (await this.request<{ forgotten: boolean }>(\"POST\", \"/users/forget\", { user })).forgotten;\n }\n\n /**\n * Tells Wakeshift what the app may do, replacing what it said before.\n * The events you defined (defineEvent) are added unless m has events of\n * its own. Returns the manifest as Wakeshift keeps it.\n */\n async publishManifest(m: Manifest): Promise<Manifest> {\n const body: Manifest = m.events === undefined && this.events.size > 0 ? { ...m, events: [...this.events.values()].map((e) => e.manifest) } : m;\n const out = await this.request<Manifest>(\"PUT\", \"/manifest\", body);\n this.manifestBase = m;\n this.published = Promise.resolve();\n return out;\n }\n\n // ── Events (MCP Events, natively or bridged) ──────────────────────────\n\n private readonly events = new Map<string, WakeEvent>();\n private manifestBase: Manifest = {};\n private published: Promise<void> | null = null;\n\n /**\n * Defines one of the app's event types: what a subscriber picks (args),\n * what each occurrence carries (payload), and the line an agent reads\n * first (text). Returns the event, whose emit sends occurrences. Define\n * every event before attach.\n *\n * const message = wake.defineEvent(\"chat.message\", {\n * description: \"A new message in a channel\",\n * args: z.object({ channel: z.string() }),\n * payload: z.object({ from: z.string(), text: z.string() }),\n * text: (m) => `${m.from}: ${m.text}`,\n * })\n * await message.emit({ channel: \"eng\" }, { from: \"ada\", text: \"shipped\" })\n */\n defineEvent<A extends AnySchema, P extends AnySchema>(name: string, opts: EventOptions<A, P>): WakeEvent<A, P> {\n if (this.events.has(name)) throw new Error(`wakeshift: event ${name} is defined twice`);\n const ev = new WakeEvent<A, P>(this, name, opts);\n this.events.set(name, ev as unknown as WakeEvent);\n this.published = null; // the manifest changed\n return ev;\n }\n\n /**\n * Attaches Wakeshift to your MCP server, before you connect it: the one\n * call that connects the two. Today it serves the defined events: MCP\n * Events (events/list, events/subscribe, events/unsubscribe) for clients\n * that speak it, such as ChatGPT, and the subscribe_events tool for\n * everyone else, whom Wakeshift wakes its own ways; plus\n * unsubscribe_events and list_subscriptions for every client, so a person\n * can always see and stop what they're subscribed to.\n *\n * wake.attach(server, { identify: (extra) => ({ user: guestOf(extra)?.user }) })\n */\n attach(server: McpServer, opts: AttachOptions = {}): void {\n attachEvents(this, server, [...this.events.values()], opts);\n }\n\n /** @internal Publishes the manifest with the defined events, once (again after a defineEvent). */\n ensureEvents(): Promise<void> {\n if (!this.published) {\n const p = this.publishManifest(this.manifestBase).then(() => undefined);\n this.published = p;\n p.catch(() => {\n if (this.published === p) this.published = null; // try again next time\n });\n }\n return this.published;\n }\n\n /** @internal An event's emit. */\n async emitEvent(e: { name: string; arguments: unknown; payload: unknown; text: string; id?: string; to?: string }): Promise<Published> {\n await this.ensureEvents();\n const body: Record<string, unknown> = { name: e.name, arguments: e.arguments, payload: e.payload, text: e.text };\n if (e.id) body.id = e.id;\n if (e.to) body.to = e.to;\n const out = await this.request<Published>(\"POST\", \"/events/emit\", body);\n return { matched: out.matched, duplicate: !!out.duplicate };\n }\n\n /** @internal A call to the app API (the events machinery's), after the manifest with the defined events is published. */\n async requestApp<T>(method: string, path: string, body?: unknown): Promise<T> {\n await this.ensureEvents();\n return this.request<T>(method, path, body);\n }\n\n // ── Guests' workspaces (GuestAuth) ─────────────────────────────────────\n\n /**\n * The workspace Wakeshift keeps for one of the app's users (user: the\n * app's own ID for them, as in wake.call's user), creating it on first use.\n * Topics keep that user's conversations there too. GuestAuth calls this;\n * most apps never need to.\n */\n async workspace(user: string, name: string): Promise<string> {\n return (await this.request<{ workspace: string }>(\"POST\", \"/workspaces\", { external_id: user, name })).workspace;\n }\n\n /** Creates an agent in one of the app's workspaces (if new), and its grant for the app's manifest scope. Returns the grant's ID. */\n async addAgent(workspace: string, agent: string, displayName: string): Promise<string> {\n return (await this.request<{ grant: string }>(\"POST\", `/workspaces/${encodeURIComponent(workspace)}/agents`, { agent, display_name: displayName })).grant;\n }\n\n /**\n * Checks that one of the app's agents still has a live grant, asking\n * Wakeshift each time. Throws a WakeshiftError: no_grant once the grant is\n * revoked, unknown_workspace once the workspace is forgotten.\n */\n async checkAgent(workspace: string, agent: string): Promise<void> {\n await this.request(\"POST\", `/workspaces/${encodeURIComponent(workspace)}/agents/${encodeURIComponent(agent)}/token`);\n }\n\n /** @internal The app key's seed: what GuestAuth keys its signed values from. Never send or log it. */\n secretSeed(): Buffer {\n return this.key.seed;\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const headers: Record<string, string> = { Authorization: \"Bearer \" + this.assertion() };\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n let resp: Response;\n let text: string;\n try {\n resp = await this.fetchFn(this.core + \"/api/apps/v1\" + path, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n text = await resp.text();\n } catch (err) {\n throw this.unreachable(err);\n }\n let json: any;\n try {\n json = text ? JSON.parse(text) : undefined;\n } catch {\n json = undefined;\n }\n if (!resp.ok) {\n const e = json?.error ?? {};\n throw new WakeshiftError(resp.status, e.code ?? \"\", e.message ?? (text.slice(0, 200) || resp.statusText), {\n reason: typeof e.reason === \"string\" ? e.reason : undefined,\n });\n }\n // A 2xx that isn't Wakeshift's JSON: a proxy's page, or a wrong core.\n if (json === null || typeof json !== \"object\") {\n throw new WakeshiftError(\n resp.status,\n \"bad_response\",\n `Wakeshift at ${this.core} answered ${resp.status} without a JSON object. Check that WAKESHIFT_CORE, or the core option, is the address of the console your key came from`,\n );\n }\n return json as T;\n }\n\n // A request that got no answer. Name the core it went to: a wrong or\n // missing WAKESHIFT_CORE is the usual cause.\n private unreachable(err: unknown): WakeshiftError {\n const e = err as { name?: string; message?: string; cause?: { code?: string; message?: string } } | undefined;\n const why =\n e?.name === \"TimeoutError\" || e?.name === \"AbortError\"\n ? `no answer within ${this.timeoutMs / 1000}s`\n : [e?.message, e?.cause?.code ?? e?.cause?.message].filter(Boolean).join(\": \") || String(err);\n return new WakeshiftError(\n 0,\n \"unreachable\",\n `can't reach Wakeshift at ${this.core} (${why}). Check that WAKESHIFT_CORE, or the core option, is the address of the console your key came from`,\n { cause: err },\n );\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wakeshift/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Let agents subscribe to things in your app: Wakeshift wakes them when you publish.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://github.com/wakeshift/wakeshift-node#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/wakeshift/wakeshift-node.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/wakeshift/wakeshift-node/issues"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"NOTICE"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|
|
34
|
+
"test": "tsc -p tsconfig.test.json && node --test 'build-test/test/*.test.js' && npm run examples",
|
|
35
|
+
"examples": "node scripts/check-examples.mjs",
|
|
36
|
+
"e2e": "sh scripts/e2e.sh",
|
|
37
|
+
"prepack": "npm run build",
|
|
38
|
+
"pack-check": "sh scripts/pack-check.sh"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
42
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
46
|
+
"@types/node": "^22.20.5",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"zod": "^4.6.7"
|
|
49
|
+
}
|
|
50
|
+
}
|