@wakeshift/sdk 0.3.0 → 0.5.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 +165 -0
- package/README.md +76 -50
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +91 -51
- package/dist/events.js +178 -72
- package/dist/events.js.map +1 -1
- package/dist/harness.js +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/key.d.ts +0 -5
- package/dist/key.js +9 -5
- package/dist/key.js.map +1 -1
- package/dist/retry.js +1 -1
- package/dist/retry.js.map +1 -1
- package/dist/spec.d.ts +2 -2
- package/dist/spec.js +2 -2
- package/dist/spec.js.map +1 -1
- package/dist/toolcall.d.ts +154 -0
- package/dist/toolcall.js +558 -0
- package/dist/toolcall.js.map +1 -0
- package/dist/wakeshift.d.ts +81 -256
- package/dist/wakeshift.js +167 -244
- package/dist/wakeshift.js.map +1 -1
- package/package.json +16 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,170 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
What changed in each release of `@wakeshift/sdk`. 0.4.0 is the first published release: to start with it, read the [README](README.md). Its notes below, and the earlier releases', are for apps that ran the preview builds.
|
|
4
|
+
|
|
5
|
+
## 0.5.0 (2026-10-29)
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
|
|
9
|
+
- **`recognised` is now `recognized`.** The `Recognised` type is `Recognized`, and `Subscription.recognised` and `ToolCall.recognised` are `recognized`. Rename them where you read them: `sub.recognised.harness` becomes `sub.recognized.harness`. What they hold is unchanged.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Processes that share your key no longer remove each other's events.** In 0.4.0 every process sent the events it defined as your app's whole list. With an MCP server and a separate emitter on one key, whichever sent last removed the other's events, and the other's emits failed with `unknown_event` until it restarted. Now only your MCP server, the process that calls `attach`, sends the manifest automatically, and Wakeshift merges it into the one it has: it adds or updates your events and the settings you passed to `publishManifest`, and never removes an event or clears a setting. A process that doesn't call `attach` never sends it, whatever it calls (`emit`, `status`). `publishManifest` still replaces the whole manifest, and is how you remove an event.
|
|
14
|
+
- A process with no events defined never sends the manifest. In 0.4.0 any app call from one (even a status read) sent an empty list, which removed every one of your app's events.
|
|
15
|
+
- If an event is defined only in a process that emits it, define it in your MCP server too (or publish it with `publishManifest`): until a server sends it, emitting it fails with `unknown_event`.
|
|
16
|
+
|
|
17
|
+
## 0.4.0 (2026-10-29)
|
|
18
|
+
|
|
19
|
+
Define your app's events (`defineEvent`), attach Wakeshift to your MCP server (`attach`), subscribe the conversations that call your own tools (`attached.events`, `call.subscribe`), and emit to them (`emit`). The rest of this section is what changed since 0.3.0.
|
|
20
|
+
|
|
21
|
+
### Breaking
|
|
22
|
+
|
|
23
|
+
One way to subscribe from your own tools. Wrap the tool with the handle `attach` returns, and subscribe with `call.subscribe`: Wakeshift adds what the agent needs to be woken to whatever you return, once per call however many subscriptions it makes, and ends the subscriptions of a call that fails. The tool can't leave a conversation unwakeable by a forgotten argument, a wrong tool name or a result it didn't pass through.
|
|
24
|
+
|
|
25
|
+
A join tool:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// before
|
|
29
|
+
server.registerTool("join", { inputSchema: { table: z.string(), ...wake.args } }, async ({ table, ...wakeArgs }, extra) => {
|
|
30
|
+
const sub = await turn.subscribe(extra, { table }, { server, user: myUser(extra), wakeArgs, tool: "join" })
|
|
31
|
+
return sub.result(`You're seated at ${table}.`)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// now
|
|
35
|
+
const attached = wake.attach(server, { identify: (extra) => ({ user: myUser(extra) }) })
|
|
36
|
+
server.registerTool(
|
|
37
|
+
"join",
|
|
38
|
+
{ inputSchema: attached.inputs({ table: z.string() }) },
|
|
39
|
+
attached.events("join", async ({ table }, call) => {
|
|
40
|
+
const sub = await call.subscribe(turn, { table })
|
|
41
|
+
// Keep sub.user and sub.id to reach this conversation: emit's to: [{ user, subscription: id }]
|
|
42
|
+
return `You're seated at ${table}.`
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A tool with structured output: make its `outputSchema` with `attached.output(shape)`, and the wrapper sets its `wakeshift` field to what Wakeshift adds, for hosts that show the model only `structuredContent`. Wakeshift's text stays in `content` too.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// before: sub.result(...) added Wakeshift's text to content only
|
|
51
|
+
// now
|
|
52
|
+
server.registerTool(
|
|
53
|
+
"look",
|
|
54
|
+
{ inputSchema: attached.inputs({ table: z.string() }), outputSchema: attached.output({ seats: z.number() }) },
|
|
55
|
+
attached.events("look", async ({ table }, call) => {
|
|
56
|
+
await call.subscribe(turn, { table })
|
|
57
|
+
return { content: [{ type: "text", text: "3 seats." }], structuredContent: { seats: 3 } }
|
|
58
|
+
}),
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A leave tool:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// before
|
|
66
|
+
server.registerTool("leave", { inputSchema: { table: z.string(), ...wake.args } }, async ({ table, ...wakeArgs }, extra) => {
|
|
67
|
+
await turn.unsubscribe(extra, { table }, { server, user: myUser(extra), wakeArgs, tool: "leave" })
|
|
68
|
+
return { content: [{ type: "text", text: `You left ${table}.` }] }
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// now
|
|
72
|
+
server.registerTool(
|
|
73
|
+
"leave",
|
|
74
|
+
{ inputSchema: attached.inputs({ table: z.string() }) },
|
|
75
|
+
attached.events("leave", async ({ table }, call) => {
|
|
76
|
+
const left = await call.unsubscribe(turn, { table })
|
|
77
|
+
return left ? `You left ${table}.` : `You weren't at ${table}.`
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A tool you used to pass to `wake.annotate` is wrapped the same way, without a subscribe: the wrapper adds what Wakeshift has for the conversation calling it.
|
|
83
|
+
|
|
84
|
+
Topics are gone: use events. Define an event for what you published to a topic, subscribe from a wrapped tool (or let agents use `subscribe_events`), and emit it.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// before
|
|
88
|
+
await wake.publish(`ci/${repo}`, { text: `CI ${run.conclusion} on ${repo}`, data: run, id })
|
|
89
|
+
const { subscriptions, connected } = await wake.topicStatus(`ci/${repo}`)
|
|
90
|
+
|
|
91
|
+
// now
|
|
92
|
+
const ciFinished = wake.defineEvent("ci.finished", {
|
|
93
|
+
description: "CI finished on a repo",
|
|
94
|
+
args: z.object({ repo: z.string() }),
|
|
95
|
+
payload: z.object({ conclusion: z.string() }),
|
|
96
|
+
text: (run, { repo }) => `CI ${run.conclusion} on ${repo}`,
|
|
97
|
+
})
|
|
98
|
+
await ciFinished.emit({ repo }, run, { id })
|
|
99
|
+
const { subscriptions, connected } = await ciFinished.status({ repo })
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`wake.send(id, …)` is an emit to one subscription of a personal event (`to: [{ user, subscription: id }]`), `wake.subscriptionStatus(id)` is `event.statusOf([{ user, subscription: id }])`, `wake.publishUrl(topic)` is `event.publishUrl(args)`, and `wake.endSubscription(id)` and `wake.unsubscribeUser(user, topic)` are `event.end(user, id)` for each subscription you keep.
|
|
103
|
+
|
|
104
|
+
Groups are gone: keep membership in your app and emit to your members' subscriptions. `AgentScope.groups` is removed; store the `{ user, id }` each member's `call.subscribe` returned and emit to them in one call, `to: [{ user, subscription: id }, …]`.
|
|
105
|
+
|
|
106
|
+
Errors the SDK throws before asking Wakeshift have the status and code Wakeshift gives for the same mistake, the same in the Go and Python SDKs:
|
|
107
|
+
|
|
108
|
+
- A payload that doesn't match the event's schema is `bad_arguments`, Wakeshift's code for an emit's payload, not `bad_payload`. `ArgsError`, for arguments or a payload that don't match, is a `WakeshiftError` with code `bad_arguments` (status 400): its `message` starts `wakeshift: bad_arguments (400): `, and `detail` is the message without it.
|
|
109
|
+
- `call.subscribe` or `call.unsubscribe` given another app's event is `unknown_event` (404), not `bad_arguments`.
|
|
110
|
+
- `statusOf` with more than 1,000 subscriptions, or one without its user, is `bad_request`, not `bad_arguments`.
|
|
111
|
+
|
|
112
|
+
**A manifest's `events` add to the ones you define.** `publishManifest`'s `events` used to replace the events `defineEvent` made. Now Wakeshift gets both, the listed ones first, and an event both defined and listed is an error: nothing is sent. List only events defined elsewhere.
|
|
113
|
+
|
|
114
|
+
**A `filter` may not name a field an optional argument filters.** Without a `topic`, `defineEvent` adds a filter for each optional argument to your `filter`; where both named the same payload field, the argument's entry silently replaced yours. Now `defineEvent` throws, naming the field: remove it from `filter`, or pass `topic` and `filter` both.
|
|
115
|
+
|
|
116
|
+
### Removed
|
|
117
|
+
|
|
118
|
+
- `event.subscribe(extra, …)` and `event.unsubscribe(extra, …)`: `call.subscribe` and `call.unsubscribe` in a wrapped tool.
|
|
119
|
+
- `CallerSubscription` and its `result` and `agent`: `Subscription`, with `recognised`; the wrapper adds the block.
|
|
120
|
+
- Topics and their types: `wake.subscribe`, `wake.unsubscribe`, `wake.publish`, `wake.send`, `wake.topicStatus`, `wake.subscriptionStatus`, `wake.publishUrl(topic)`, `wake.closeTopic`, `wake.endSubscription`, `wake.unsubscribeUser`; `TopicSubscription`, `TopicEvent`, `TopicStatus`, `SubscriptionStatus`, `SendStatus`, `SubscribeOptions` and `Published` (its `matched` and `duplicate` are on `Emitted`). `PublishUrl.topic` goes with them.
|
|
121
|
+
- `wake.args`: `attached.inputs(shape)`.
|
|
122
|
+
- `wake.call`, `wake.callOf`, `Call`, `CallOptions`, `ClientInfoSource`, `applyAddition`, `wake.addition`, `wake.annotate` and `Addition`: the wrapper.
|
|
123
|
+
- `UNKNOWN_CLIENT_WARNING`, which only the removed calls gave. A wrapped tool's `call.recognised` says what Wakeshift recognized the agent as.
|
|
124
|
+
- `AgentScope.groups`.
|
|
125
|
+
- `AppKey.mode` and `wake.mode`, which meant nothing since current keys came in. Older keys (`wst_test_…`, `wst_live_…`) still work and name their app; a `wst_test_` key still needs a core.
|
|
126
|
+
- `session` as a hidden alias of `session_key`: a wrapped tool takes `session_key` only, and a `session` argument is dropped like any other the tool doesn't take.
|
|
127
|
+
- Reading a client's top-level `events` capability, the earlier MCP Events sketch's key: a client speaks MCP Events if it declares the extension (`extensions["io.modelcontextprotocol/events"]`) or is ChatGPT. `attach` still advertises both keys, since ChatGPT reads the top-level one.
|
|
128
|
+
- `speaksEvents`: a wrapped tool's `call.native` says whether its caller subscribes to your events itself.
|
|
129
|
+
- `LEGACY_KEY`, the top-level capability key `attach` advertises for ChatGPT. It's no longer exported; `EXTENSION_KEY` is the one a client declares.
|
|
130
|
+
|
|
131
|
+
Kept: `defineEvent`, `emit`, `event.publishUrl`, `event.status` and `event.statusOf`, `event.end`, `attach` with its three tools and native MCP Events, `revokePublishUrl`, `forgetUser`, `publishManifest`, `workspace`, `addAgent`, `checkAgent`, `harnessAgent`, `SESSION_ARG`, `HARNESS_ARG`, `CONVERSATION_ARG`, keys and errors.
|
|
132
|
+
|
|
133
|
+
### Added
|
|
134
|
+
|
|
135
|
+
- `wake.attach` returns a handle, `Attached`. Wrap your own tool's callback with `attached.events("name", handler)` and make its input schema with `attached.inputs(shape)` (a type error without it): the handler gets the tool's own arguments and a `ToolCall` (`user`, `tool`, `native`, `nativeConversation`, `session`, `recognised`, `extra`), subscribes the calling conversation with `await call.subscribe(event, args, { ttl?, reason?, label? })` (a `Subscription`: `{ id, user, created, session, recognised, native }`) and ends it with `call.unsubscribe`. A `ToolCall` used after its handler returned throws `CallEnded`.
|
|
136
|
+
- `call.resolveSession()` finds or starts the conversation's session without subscribing it. `call.subscribe`'s `reason` is shown in the console.
|
|
137
|
+
- `identify` can return the MCP client's name, `{ user, client }`, for a stateless server that never sees `initialize`: Wakeshift reads it to tell which program the agent runs in, and whether the client subscribes to events itself. A name the request carries (`initialize`'s, or its `_meta`'s) wins. You no longer need to patch `server.server.getClientVersion` for it.
|
|
138
|
+
- `attach`'s `tools` option names which of its own tools it adds (`BUILTIN_TOOLS`: `subscribe_events`, `unsubscribe_events`, `list_subscriptions`). All three by default; `[]` adds none.
|
|
139
|
+
- `attached.output(shape)` adds an optional `wakeshift` string to an output schema, which the wrapper fills with the block's text, for hosts that show only `structuredContent`.
|
|
140
|
+
- A personal event's `emit` takes many recipients: `to` is a user, a `Recipient` (`{ user, subscription }`, exactly that subscription; `{ native: user }`, only that user's own MCP Events subscriptions) or a list of them, mixed, up to 1,000. Its result, `Emitted`, adds `unmatched` and `ended`. `publishUrl` still takes exactly one user.
|
|
141
|
+
- `event.status(args, { to? })`: how many conversations are subscribed to an event with these arguments (a personal event: one user's), how many are connected now, and how. `event.statusOf([{ user, subscription }, …])` reads up to 1,000 subscriptions by ID: whether each is live and its conversation connected.
|
|
142
|
+
- `AppInfo.key`: the key's console ID and name, which `wake.app()` returns, as the Go and Python SDKs do.
|
|
143
|
+
- `Manifest.preapprove_tools`: your tools, by name, that agents' programs can run without asking each time (at most 20). When a user connects Claude Code or Cursor CLI, Wakeshift offers to allow exactly these in the same question as its install.
|
|
144
|
+
- `Manifest.others_can_wake`: your app wakes its users' agents with what other people and their agents do there, so the consent screen says agents of people they don't know can wake theirs.
|
|
145
|
+
|
|
146
|
+
### Changed
|
|
147
|
+
|
|
148
|
+
- A wrapped tool's reply carries one Wakeshift block, however many subscriptions the call makes, and only what the conversation needs.
|
|
149
|
+
- A subscription a failed tool call made is ended: a call that throws, returns `isError`, returns output its `outputSchema` refuses, or is canceled.
|
|
150
|
+
- A client that speaks MCP Events is told which event to subscribe to, or that it has: `call.native` is true, and `call.subscribe` makes nothing. The SDK recognizes ChatGPT's `_meta["openai/session"]`, its client name or User-Agent, and clients that declare MCP Events, on stateless servers too; `attach`'s `events.native(extra)` names callers it can't recognize, such as your own agents.
|
|
151
|
+
- `session_key` is the one name for the session argument.
|
|
152
|
+
- `attach`'s own tools (`subscribe_events`, `unsubscribe_events`, `list_subscriptions`) describe `session_key` to the agent as a wrapped tool does.
|
|
153
|
+
- `list_subscriptions` is marked read-only and idempotent (`readOnlyHint: true`, `idempotentHint: true`), so clients that run read-only tools without asking (Codex, by default) don't ask before it.
|
|
154
|
+
- You needn't call `publishManifest`. Wakeshift gets the manifest automatically the first time your app needs it (an emit, a status, a publish URL, a subscription), and again after a `defineEvent`. The automatic send carries everything your app configured: the manifest you last passed to `publishManifest` (even if that call failed), or else the description, agent scope, `others_can_wake` and `preapprove_tools` Wakeshift already has. Before, an automatic send before your first `publishManifest` carried only the events, wiping the rest.
|
|
155
|
+
- An emit's `to` that's an empty string, or only whitespace, is refused (`bad_arguments`: a recipient's user is empty), as an empty user in a list is and as the Go and Python SDKs do. It was taken as no recipient.
|
|
156
|
+
|
|
157
|
+
### Fixed
|
|
158
|
+
|
|
159
|
+
- `statusOf` trims each subscription ID, as it does each user, and refuses one that's only whitespace (`bad_request`): every SDK trims the whitespace around recipients' names.
|
|
160
|
+
- A successful answer that is a JSON array is a `bad_response` error, as it is in the Go and Python SDKs.
|
|
161
|
+
- An error whose `code` isn't a string has an empty code, and one whose `message` is empty takes the body's start, else the status's text.
|
|
162
|
+
- An MCP Events client gets "Wakeshift couldn't be reached" when the core is unreachable, without the core's address; a `callback_failed` error's `data.reason` is always a string.
|
|
163
|
+
- Google's 500 page is recognized by "Server Error" within the body's first 4 KB.
|
|
164
|
+
- A failed tool call's rollback no longer ends a subscription that another call on the same conversation renewed in the meantime (two calls in parallel, or a second call while the first was still running). The call that renewed it told the agent it would be woken; now it is.
|
|
165
|
+
- `defineEvent` no longer adds the default route's filter to the `filter` object you passed: it copies it.
|
|
166
|
+
- A personal event's `emit` without `to` says to pass the subscribers it's for, not one.
|
|
167
|
+
|
|
3
168
|
## 0.3.0 (2026-10-26)
|
|
4
169
|
|
|
5
170
|
### Breaking
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Install the package, not a checkout of this repository (a `file:` dependency): a
|
|
|
14
14
|
|
|
15
15
|
## Quickstart: a CI bot
|
|
16
16
|
|
|
17
|
-
You have an MCP server and a GitHub webhook handler. Register an app in the Wakeshift console (Build), and put its key in `WAKESHIFT_KEY` (`wst_` and 50 characters). The SDK talks to `https://app.wakeshift.ai` unless `WAKESHIFT_CORE` names another address. Load them however you load environment variables, for example `node --env-file=.env server.ts`.
|
|
17
|
+
You have an MCP server and a GitHub webhook handler. Register an app in the Wakeshift console (Build), and put its key in `WAKESHIFT_KEY` (`wst_` and 50 characters). The SDK talks to `https://app.wakeshift.ai` unless `WAKESHIFT_CORE` names another address. Load them however you load environment variables, for example `node --env-file=.env server.ts` (Node 22.18 or later runs a `.ts` file directly).
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
20
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
|
@@ -27,7 +27,7 @@ const ciFinished = wake.defineEvent("ci.finished", {
|
|
|
27
27
|
description: "CI finished on a GitHub repo",
|
|
28
28
|
args: z.object({ owner: z.string(), repo: z.string(), branch: z.string().optional() }),
|
|
29
29
|
payload: z.object({ branch: z.string(), conclusion: z.string(), name: z.string(), url: z.string() }),
|
|
30
|
-
authorize: (subscriber, { owner, repo }) => canSeeRepo(subscriber, `${owner}/${repo}`),
|
|
30
|
+
authorize: (subscriber, { owner, repo }) => canSeeRepo(subscriber.user, `${owner}/${repo}`),
|
|
31
31
|
text: (run, { owner, repo }) => `CI ${run.conclusion} on ${owner}/${repo} ${run.branch}: ${run.name}`,
|
|
32
32
|
})
|
|
33
33
|
|
|
@@ -53,7 +53,7 @@ if (req.headers["x-github-event"] === "workflow_run" && body.action === "complet
|
|
|
53
53
|
That's all. An agent subscribes with the `subscribe_events` tool `attach` adds (ChatGPT subscribes natively), and is woken with the text and payload of each occurrence for its repo, framed as information from your app, not as instructions from its user. A subscriber that names a `branch` only gets that branch's occurrences.
|
|
54
54
|
|
|
55
55
|
- `owner` and `repo` are required, so they route occurrences; their values are lowercase IDs (`a-z 0-9 . _ -`). The optional `branch` filters on the payload's `branch`.
|
|
56
|
-
- `attach` adds `subscribe_events`, `unsubscribe_events` and `list_subscriptions`, and touches no
|
|
56
|
+
- `attach` adds `subscribe_events`, `unsubscribe_events` and `list_subscriptions`, and touches no tool of yours you don't wrap (below).
|
|
57
57
|
- Native subscriptions (ChatGPT) and personal events need a signed-in user: your app signs its users in and passes `identify` to `attach`.
|
|
58
58
|
|
|
59
59
|
## Events
|
|
@@ -84,87 +84,113 @@ const turn = wake.defineEvent("game.turn", {
|
|
|
84
84
|
personal: true,
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
-
// One server per connection; attach before connecting it.
|
|
87
|
+
// One server per connection; attach once, before connecting it.
|
|
88
88
|
const server = new McpServer({ name: "chat", version: "1.0.0" })
|
|
89
89
|
// myUser: your own sign-in's ID for the person behind the request.
|
|
90
|
-
wake.attach(server, { identify: (extra) => ({ user: myUser(extra) }) })
|
|
90
|
+
const attached = wake.attach(server, { identify: (extra) => ({ user: myUser(extra) }) })
|
|
91
91
|
|
|
92
92
|
// When something happens:
|
|
93
93
|
await message.emit({ channel: "eng" }, { from: "ada", text: "shipped it" })
|
|
94
94
|
await turn.emit({ table: "juniper" }, { hole: ["As", "Kd"], toCall: 2 }, { to: "user-123" })
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
- **`wake.defineEvent(name, { description, args, payload, text, authorize?, personal? })`** returns the event. `args` is what a subscriber picks; its required string, integer and boolean fields route occurrences, so their values are lowercase IDs (a-z 0-9 . _ -), and an optional one filters on the payload field of the same name. `authorize(subscriber, args)` decides who may subscribe. Define every event before `attach
|
|
98
|
-
- **`event.emit(args, payload, { id?, to? })`** checks the payload against its schema and sends it to everyone subscribed with those arguments
|
|
99
|
-
- **`wake.attach(server, { identify? })`** attaches Wakeshift to your MCP server.
|
|
100
|
-
- **`event.
|
|
97
|
+
- **`wake.defineEvent(name, { description, args, payload, text, authorize?, personal?, deleteOnDelivery? })`** returns the event. `args` is what a subscriber picks; its required string, integer and boolean fields route occurrences, so their values are lowercase IDs (a-z 0-9 . _ -), and an optional one filters on the payload field of the same name. `authorize(subscriber, args)` decides who may subscribe. With `deleteOnDelivery: true`, Wakeshift deletes each occurrence's text and payload once it's delivered. Define every event before `attach`, which throws with none.
|
|
98
|
+
- **`event.emit(args, payload, { id?, to? })`** checks the payload against its schema and sends it to everyone subscribed with those arguments. A personal event goes to its recipients, `to`: users (the `user` attach's `identify` gives for them), `{ user, subscription }` (exactly one conversation's subscription, the `id` `call.subscribe` returned) and `{ native: user }` (only that user's own MCP Events subscriptions), mixable, up to 1,000. It returns `{ matched, duplicate, unmatched, ended }`: `matched` counts the conversations woken, `unmatched` the users with nothing subscribed, `ended` the subscriptions no longer live (forget them). `id` makes retries emit once; a retry must name the same recipients.
|
|
99
|
+
- **`wake.attach(server, { identify?, tools?, events? })`** attaches Wakeshift to your MCP server and returns the handle your own tools use (below). It serves `events/list`, `events/subscribe` and `events/unsubscribe` to clients that speak MCP Events, and adds `subscribe_events` (for everyone else), `unsubscribe_events` and `list_subscriptions` (for every client, so a person can always see and stop what they're subscribed to); `tools` names fewer of them (`BUILTIN_TOOLS`; `[]` for none). `identify(extra)` says who is behind a request, as `{ user, client? }`: `user` is your app's ID for the person, from your own sign-in; without it, requests have no user, so personal events and `list_subscriptions` refuse. On a stateless server, which never sees `initialize`, `client` is the MCP client's name as your sign-in knows it (an OAuth registration's `client_name`, say): Wakeshift reads it to tell which program the agent runs in. A name the request carries wins. It also sends your app's manifest, with your events, the first time they're needed: it adds them and never removes one. A separate process that only emits never sends it.
|
|
100
|
+
- **`event.end(user, id)`** ends one of a user's subscriptions by its ID, outside a tool (a player who timed out, a member removed).
|
|
101
|
+
- **`event.status(args, { to? })`** counts the conversations subscribed with those arguments (a personal event: one user's), how many are connected now, and how: `{ subscriptions, connected, via: { stream, poll, hooks, webhook } }`. **`event.statusOf([{ user, subscription }, …])`** reads up to 1,000 subscriptions by ID: `[{ id, user, live, connected, via? }]`. Both only read.
|
|
102
|
+
- **`event.publishUrl(args, { to? })`** returns `{ id, event, url }`, a capability URL that emits the event with those arguments (a personal event: to one user) for services that only send webhooks. A POST's JSON body is the payload, checked against the event's schema. `wake.revokePublishUrl(id)` stops it.
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
### Subscribing from your own tools
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
A tool like "join" subscribes the conversation calling it: wrap its callback with the handle `attach` returns, and call `call.subscribe`. Wakeshift adds what the agent needs to be woken to whatever the tool returns, once per call however many subscriptions it makes, and ends the subscriptions of a call that fails.
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
```ts
|
|
109
|
+
// attached: what wake.attach(server, …) returned, above.
|
|
110
|
+
server.registerTool(
|
|
111
|
+
"join",
|
|
112
|
+
{ description: "Take a seat at a table", inputSchema: attached.inputs({ table: z.string() }) },
|
|
113
|
+
attached.events("join", async ({ table }, call) => {
|
|
114
|
+
const sub = await call.subscribe(turn, { table }, { reason: `Playing at ${table}` })
|
|
115
|
+
// Keep sub.user and sub.id to reach this conversation later:
|
|
116
|
+
// turn.emit({ table }, hand, { to: [{ user: sub.user, subscription: sub.id }] })
|
|
117
|
+
return `You're seated at ${table}.`
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
120
|
+
```
|
|
111
121
|
|
|
112
|
-
|
|
122
|
+
- **`attached.inputs(shape)`** is `shape` plus Wakeshift's three optional arguments, `session_key`, `harness` and `conversation_id`. A tool wrapped with `attached.events` needs them: without them it doesn't compile. An argument a tool doesn't declare is ignored, not refused.
|
|
123
|
+
- **`attached.events(name, handler)`** wraps the callback of the tool registered as `name` (the MCP SDK doesn't tell a callback its tool's name; the first call checks it). The handler gets the tool's own arguments and a `call`, and returns a string or a `CallToolResult`.
|
|
124
|
+
- **`call`**: `user` (from `identify`), `tool`, `native` and `nativeConversation`, `session` (the `session_key` passed; after a subscribe, the conversation's session), `recognized` (which program Wakeshift recognized the agent as, `null` before a subscribe) and `extra` (MCP's own). It works until the handler returns.
|
|
125
|
+
- **`await call.subscribe(event, args, { ttl?, reason?, label? })`** subscribes the calling conversation, or renews its subscription, and resolves to `{ id, user, created, session, recognized, native }`. `reason` is shown in your console. Your tool decides who may (`authorize` isn't consulted). **`await call.unsubscribe(event, args)`** ends it; **`await call.resolveSession()`** finds or starts the conversation's session without subscribing it.
|
|
126
|
+
- **A failed call**: one that throws, returns `isError`, returns output its `outputSchema` refuses, or is canceled, ends every subscription it made, and its reply gets nothing from Wakeshift.
|
|
127
|
+
- **Clients that subscribe themselves** (MCP Events: ChatGPT and others) aren't subscribed: `call.native` is true, and the reply tells them which event to subscribe to. `attach`'s `events.native(extra)` tells the SDK about callers it can't recognize, such as your own agents.
|
|
128
|
+
- **Structured output**: some hosts show the model only `structuredContent`. Make the tool's `outputSchema` with `attached.output(shape)`, and its `wakeshift` field carries what Wakeshift adds, too.
|
|
113
129
|
|
|
114
|
-
|
|
130
|
+
```ts
|
|
131
|
+
server.registerTool(
|
|
132
|
+
"look",
|
|
133
|
+
{
|
|
134
|
+
description: "Look at a table",
|
|
135
|
+
inputSchema: attached.inputs({ table: z.string() }),
|
|
136
|
+
outputSchema: attached.output({ seats: z.number() }),
|
|
137
|
+
},
|
|
138
|
+
attached.events("look", async ({ table }) => ({
|
|
139
|
+
content: [{ type: "text", text: `3 seats at ${table}.` }],
|
|
140
|
+
structuredContent: { seats: 3 },
|
|
141
|
+
})),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
server.registerTool(
|
|
145
|
+
"leave",
|
|
146
|
+
{ description: "Leave a table", inputSchema: attached.inputs({ table: z.string() }) },
|
|
147
|
+
attached.events("leave", async ({ table }, call) => {
|
|
148
|
+
const left = await call.unsubscribe(turn, { table })
|
|
149
|
+
return left ? `You left ${table}.` : `You weren't at ${table}.`
|
|
150
|
+
}),
|
|
151
|
+
)
|
|
152
|
+
```
|
|
115
153
|
|
|
154
|
+
A wrapped tool that subscribes to nothing, like `look`, still gets what Wakeshift has for the conversation calling it: occurrences held for an agent Wakeshift can't wake, or what it needs to reconnect. Usually that's nothing.
|
|
116
155
|
|
|
117
|
-
|
|
118
|
-
- `wake.callOf({ meta, clientName, tool?, user?, session?, userAgent?, harness?, conversation? })` does the same without an MCP framework.
|
|
119
|
-
- `wake.args`: a zod shape with `session_key`, `harness` and `conversation_id` (`SESSION_ARG`, `HARNESS_ARG`, `CONVERSATION_ARG`), to spread into your subscribe tool's `inputSchema` (and any tool whose results you annotate).
|
|
156
|
+
## Reference
|
|
120
157
|
|
|
121
|
-
###
|
|
158
|
+
### `new Wakeshift({ key, core?, fetch?, timeoutMs?, onWarning? })`
|
|
122
159
|
|
|
123
|
-
|
|
160
|
+
- `key`: `wst_` and 50 characters. It's your app's Ed25519 private key; it never goes over the wire. Each request carries a fresh assertion signed with it (valid two minutes). Older keys, `wst_test_<app>_<seed>` and `wst_live_<app>_<seed>`, keep working.
|
|
161
|
+
- `core`: the address of the console the key came from. Default: `WAKESHIFT_CORE` from the environment, else `https://app.wakeshift.ai`. An older `wst_test_` key has no default: it needs one of the two, and the constructor throws without them.
|
|
162
|
+
- `onWarning(message)`: called for problems that don't fail a call: each time for a failure, such as a failed call's subscription that couldn't be rolled back, and once for each kind of misuse, such as a wrapped tool that returned with a subscribe still in flight. Default: `console.warn`.
|
|
163
|
+
- `await wake.app()`: `{ id, name }`, which app the key is, as Wakeshift says. It asks once and remembers the answer.
|
|
164
|
+
- `wake.appId`: the app's ID. A current key doesn't name its app, so this throws until `await wake.app()` has answered; an older key names it. `wake.core`: the address in use.
|
|
124
165
|
|
|
125
|
-
###
|
|
166
|
+
### Users
|
|
126
167
|
|
|
127
|
-
|
|
128
|
-
| --- | --- |
|
|
129
|
-
| `subscribe(call, topic, { filter?, once?, replay?, ttl? })` | `{ id, topic, filter, once, expiresAt, session, addition, agent, result(textOrResult) }`. `agent`: `{ harness, via, claimed }`, what Wakeshift recognised the caller as and by which signal (`"client"`, `"meta"`, `"user_agent"`, or `"claim"` when only the agent's `harness` named it), both `null` when nothing; `claimed` is what the agent said (normalized), or `null`, and one that differs from `harness` was ignored. `filter`: equality on top-level fields of the event's `data`. `once`: end after the first delivery. `replay`: deliver the latest event published within this long before subscribing (at most `"24h"`). `ttl`: end after this long without a delivery or a fresh subscribe (default `"30d"`). Durations are seconds or `"90s"`, `"5m"`, `"2h"`, `"30d"` (whole days). Subscribing again refreshes it. `result` returns your text (or `CallToolResult`) followed by Wakeshift's text, with its `_meta`. |
|
|
130
|
-
| `unsubscribe(call, topic?)` | How many of the calling conversation's subscriptions ended (all your topics without `topic`). |
|
|
131
|
-
| `unsubscribeUser(user, topic?)` | How many ended: every conversation of `user` stops getting `topic` (all your topics without it). Each conversation is told you ended it. For when someone loses access. |
|
|
132
|
-
| `endSubscription(subscriptionId)` | `true` if it ended one subscription (its conversation is told you ended it); `false` if it had already ended or isn't yours. |
|
|
133
|
-
| `publish(topics, { text, data?, id? })` | `{ matched, duplicate }`. `topics`: one or up to 20. `text` at most 1000 bytes; `data` a JSON object within the 64 KB request limit. The same `id` within a day publishes once (`duplicate: true`); after a day, it publishes again. |
|
|
134
|
-
| `send(subscriptionId, event)` | `"delivered"` (connected, on its way), `"queued"` (waits for the conversation) or `"closed"` (the subscription ended). Ignores the filter. |
|
|
135
|
-
| `topicStatus(topic)` | `{ topic, subscriptions, connected, via: { stream, poll, hooks, webhook } }`: how many conversations are subscribed, and how many are connected now and how (`stream`: a stream held open; `poll`: waiting or polling; `hooks`: Wakeshift's hooks or plugin on the user's machine; `webhook`: a client subscribed natively, such as ChatGPT). Only reads. |
|
|
136
|
-
| `subscriptionStatus(subscriptionId)` | `{ id, topic, status: "active" \| "closed", connected, via?, expiresAt? }`. Only reads. |
|
|
137
|
-
| `publishUrl(topic)` | `{ id, topic, url }`: anyone can POST to `url` to publish to `topic` (a JSON object body becomes `data`; `Idempotency-Key` or `X-GitHub-Delivery` dedupes). |
|
|
138
|
-
| `revokePublishUrl(id)` | The URL answers 410 from then on. |
|
|
139
|
-
| `annotate(call, result)` | A copy of `result` with what Wakeshift adds for this conversation (messages waiting for an agent that can't be woken, or what an agent needs to reconnect; usually nothing). Only results you pass here (and `sub.result`) change. `addition(call)` returns the raw `{ text?, meta?, agent }`. |
|
|
140
|
-
| `closeTopic(topic)` | How many subscriptions ended. Each conversation gets what was already published to it, then a notice. |
|
|
141
|
-
| `forgetUser(user)` | `false` if there was nothing to forget. Deletes everything Wakeshift keeps for the user: their conversations, subscriptions, events and deliveries, stream links (open streams close), recent publishes to their `u/<user>/…` topics and publish URLs for them, and their ID. Your app's log records that a user was forgotten, without their ID or anything derived from it. Their subscription IDs stay known as closed. |
|
|
168
|
+
- `wake.forgetUser(user)`: `false` if there was nothing to forget. Deletes everything Wakeshift keeps for the user: their conversations, subscriptions, events and deliveries, stream links (open streams close), publish URLs for them, and their ID. Your app's log records that a user was forgotten, without their ID or anything derived from it.
|
|
142
169
|
|
|
143
170
|
### Manifest and workspaces
|
|
144
171
|
|
|
145
|
-
- `wake.publishManifest({ description?, agent?, events? })`: what your app may do; returns it as Wakeshift keeps it. `agent`: `{ wake, post, ttl_days, wake_limits?, start_prompt? }`, each scope event `{ pattern, description }`: what the agents in the workspaces your app keeps may be woken by and post.
|
|
172
|
+
- `wake.publishManifest({ description?, agent?, events?, others_can_wake?, preapprove_tools? })`: what your app may do; returns it as Wakeshift keeps it. `agent`: `{ wake, post, ttl_days, wake_limits?, start_prompt? }`, each scope event `{ pattern, description }`: what the agents in the workspaces your app keeps may be woken by and post. `others_can_wake`: other people and their agents wake your users' agents through your app (messages, moves), so the consent screen says so. `preapprove_tools`: your tools, by name, that agents' programs can run without asking each time (at most 20: tools that only read, and routine ones that reach only what the agent joined); when a user connects Claude Code or Cursor CLI, Wakeshift offers to allow exactly these. It replaces the manifest, so it's the way to remove an event. You needn't call it otherwise: your MCP server, the process that calls `attach`, sends the manifest automatically the first time it needs it (a subscription, a wrapped tool's call, an emit), adding your defined events to the ones Wakeshift has and never removing one or clearing a setting, so processes that share your key keep each other's events. A process that only emits never sends it. Once you've called it, every automatic send carries the settings you passed. `events` is only for events defined elsewhere: they go before your defined ones, and listing a defined one is an error.
|
|
146
173
|
- Your app identifies its own users with its own sign-in, and acts for the agents in the workspaces it keeps.
|
|
147
|
-
- `wake.workspace(user, name)`, `wake.addAgent(workspace, agent, displayName)`, `wake.checkAgent(workspace, agent)`: the workspace Wakeshift keeps for one of your users, an agent in it with a grant for your manifest's agent scope, and whether that grant is live.
|
|
174
|
+
- `wake.workspace(user, name)`, `wake.addAgent(workspace, agent, displayName)`, `wake.checkAgent(workspace, agent)`: the workspace Wakeshift keeps for one of your users, an agent in it with a grant for your manifest's agent scope, and whether that grant is live. Wakeshift keeps a user's conversations in that workspace; you don't need these to subscribe them.
|
|
148
175
|
- `harnessAgent(clientName)`: the agent ID a client name maps to (`"Claude Code"` is `claude-code`), as Wakeshift names it.
|
|
149
176
|
|
|
150
177
|
### Errors
|
|
151
178
|
|
|
152
|
-
API errors throw `WakeshiftError` with `status` and `code
|
|
179
|
+
API errors throw `WakeshiftError` with `status` and `code`: the same codes as the [HTTP API](https://wakeshift.ai/docs/http-api)'s, and `unreachable`. The SDK checks some mistakes before sending (arguments and payloads against the event's schemas, recipients, another app's event) and throws the error Wakeshift gives for the same mistake, so the code is the same whichever caught it, and the same in the Go and Python SDKs:
|
|
153
180
|
|
|
154
181
|
| Status | Code | When |
|
|
155
182
|
| --- | --- | --- |
|
|
156
|
-
| `400` | `bad_request` | Something in the request is invalid, including a field the API doesn't take. The message says what. |
|
|
183
|
+
| `400` | `bad_request` | Something in the request is invalid, including a field the API doesn't take. The message says what. Also more than 1,000 subscriptions in one `statusOf`, or one without its user. |
|
|
184
|
+
| `400` | `bad_arguments` | An event's arguments or payload don't match its schemas, a routing value isn't a lowercase ID, or the recipients don't fit the event: a personal event's emit, publish URL or status without `to` (a publish URL's or status's names exactly one user), a shared event's with one, a recipient with an empty user or subscription, or `call.subscribe` on a personal event with no user. |
|
|
157
185
|
| `401` | `unauthenticated` | The assertion was refused: expired, reused, signed with another key, or for another `aud` (a key for another Wakeshift). The message says which. |
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `403` | `other_client` | The `session` key is a conversation in another MCP client of the same user. Call again without it. |
|
|
161
|
-
| `404` | `unknown_subscription` | The subscription ID isn't one of your app's. |
|
|
186
|
+
| `403` | `other_client` | The `session_key` is a conversation in another MCP client of the same user. Call again without it. |
|
|
187
|
+
| `404` | `unknown_event` | Your app offers no event by that name, or `call.subscribe` or `call.unsubscribe` was given another app's event (one defined on another `Wakeshift` client). |
|
|
162
188
|
| `404` | `unknown_publish_url` | The publish URL ID isn't one of your app's. |
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
189
|
+
| `413` | `too_large` | The request body is over 64 KB (512 KB for an emit or a status call, room for 1,000 recipients). The limit is on the whole body, so the payload must leave room for the rest of the request. |
|
|
190
|
+
| `429` | `rate_limited` | Your emits to lists named over 10,000 recipients in a minute. |
|
|
165
191
|
| `0` | `unreachable` | No answer at all: a wrong `core`, a network problem, or the timeout. The message names the core it tried. |
|
|
166
192
|
|
|
167
|
-
`isCode(err, "
|
|
193
|
+
`isCode(err, "other_client")` checks one.
|
|
168
194
|
|
|
169
195
|
## Documentation
|
|
170
196
|
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An error response from Wakeshift: `code` is the API's error code (e.g.
|
|
3
|
-
* "
|
|
3
|
+
* "unknown_event", "other_client", "bad_arguments", "bad_request"),
|
|
4
4
|
* `status` the HTTP status. A request that got no answer
|
|
5
5
|
* has code "unreachable" and status 0.
|
|
6
6
|
*/
|
package/dist/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An error response from Wakeshift: `code` is the API's error code (e.g.
|
|
3
|
-
* "
|
|
3
|
+
* "unknown_event", "other_client", "bad_arguments", "bad_request"),
|
|
4
4
|
* `status` the HTTP status. A request that got no answer
|
|
5
5
|
* has code "unreachable" and status 0.
|
|
6
6
|
*/
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,MAAM,CAAS;IACf,IAAI,CAAS;IACtB,mDAAmD;IAC1C,MAAM,CAAS;IACxB,wEAAwE;IAC/D,MAAM,CAAU;IAEzB,YAAY,MAAc,EAAE,IAAY,EAAE,OAAe,EAAE,OAA8C;QACvG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,IAAI,IAAI,OAAO,KAAK,MAAM,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC,cAAc,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;QAC3G,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QACtB,IAAI,OAAO,EAAE,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACpD,CAAC;CACF;AAED,iDAAiD;AACjD,MAAM,UAAU,MAAM,CAAC,GAAY,EAAE,IAAY;IAC/C,OAAO,GAAG,YAAY,cAAc,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC;AAC5D,CAAC","sourcesContent":["/**\n * An error response from Wakeshift: `code` is the API's error code (e.g.\n * \"
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,MAAM,CAAS;IACf,IAAI,CAAS;IACtB,mDAAmD;IAC1C,MAAM,CAAS;IACxB,wEAAwE;IAC/D,MAAM,CAAU;IAEzB,YAAY,MAAc,EAAE,IAAY,EAAE,OAAe,EAAE,OAA8C;QACvG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,IAAI,IAAI,OAAO,KAAK,MAAM,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC,cAAc,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;QAC3G,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QACtB,IAAI,OAAO,EAAE,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACpD,CAAC;CACF;AAED,iDAAiD;AACjD,MAAM,UAAU,MAAM,CAAC,GAAY,EAAE,IAAY;IAC/C,OAAO,GAAG,YAAY,cAAc,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC;AAC5D,CAAC","sourcesContent":["/**\n * An error response from Wakeshift: `code` is the API's error code (e.g.\n * \"unknown_event\", \"other_client\", \"bad_arguments\", \"bad_request\"),\n * `status` the HTTP status. A request that got no answer\n * has code \"unreachable\" and status 0.\n */\nexport class WakeshiftError extends Error {\n readonly status: number;\n readonly code: string;\n /** Wakeshift's own message, without the prefix. */\n readonly detail: string;\n /** Why, where the API says (callback_failed: what the callback did). */\n readonly reason?: string;\n\n constructor(status: number, code: string, message: string, options?: { cause?: unknown; reason?: string }) {\n super(status ? `wakeshift: ${code || \"error\"} (${status}): ${message}` : `wakeshift: ${message}`, options);\n this.name = \"WakeshiftError\";\n this.status = status;\n this.code = code;\n this.detail = message;\n if (options?.reason) this.reason = options.reason;\n }\n}\n\n/** Whether err is a WakeshiftError with code. */\nexport function isCode(err: unknown, code: string): boolean {\n return err instanceof WakeshiftError && err.code === code;\n}\n"]}
|