@wakeshift/sdk 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +58 -4
- package/dist/discover.d.ts +4 -0
- package/dist/discover.js +128 -0
- package/dist/discover.js.map +1 -0
- package/dist/events.d.ts +14 -9
- package/dist/events.js +131 -88
- package/dist/events.js.map +1 -1
- package/dist/host.d.ts +2 -0
- package/dist/host.js +203 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +92 -0
- package/dist/mcp.js +22 -0
- package/dist/mcp.js.map +1 -0
- package/dist/spec.d.ts +4 -2
- package/dist/spec.js +4 -2
- package/dist/spec.js.map +1 -1
- package/dist/tasks.d.ts +33 -0
- package/dist/tasks.js +159 -0
- package/dist/tasks.js.map +1 -0
- package/dist/toolcall.d.ts +74 -22
- package/dist/toolcall.js +127 -66
- package/dist/toolcall.js.map +1 -1
- package/dist/wakeshift.d.ts +7 -3
- package/dist/wakeshift.js +29 -18
- package/dist/wakeshift.js.map +1 -1
- package/dist/zod.d.ts +16 -0
- package/dist/zod.js +95 -0
- package/dist/zod.js.map +1 -0
- package/package.json +15 -2
package/dist/toolcall.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
5
|
-
import { type AnySchema, type SchemaInput, type ShapeOutput, type ZodRawShapeCompat } from "
|
|
2
|
+
import * as z3 from "zod/v3";
|
|
3
|
+
import * as z4 from "zod/v4";
|
|
4
|
+
import type { CallToolResult, ToolCallExtra } from "./mcp.js";
|
|
5
|
+
import { type AnySchema, type SchemaInput, type ShapeOutput, type ZodRawShapeCompat } from "./zod.js";
|
|
6
6
|
import type { WakeEvent } from "./events.js";
|
|
7
7
|
import { type Duration, type Recognized } from "./wakeshift.js";
|
|
8
8
|
declare const SESSION_ARG = "session_key";
|
|
9
9
|
declare const HARNESS_ARG = "harness";
|
|
10
10
|
declare const CONVERSATION_ARG = "conversation_id";
|
|
11
|
-
|
|
12
|
-
export type ToolCallExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
11
|
+
export type { ToolCallExtra };
|
|
13
12
|
declare const wakeInputs: {
|
|
14
13
|
session_key: z.ZodOptional<z.ZodString>;
|
|
15
14
|
harness: z.ZodOptional<z.ZodString>;
|
|
@@ -18,10 +17,50 @@ declare const wakeInputs: {
|
|
|
18
17
|
declare const wakeOutput: {
|
|
19
18
|
wakeshift: z.ZodOptional<z.ZodString>;
|
|
20
19
|
};
|
|
20
|
+
declare const byVersion: {
|
|
21
|
+
v3: {
|
|
22
|
+
inputs: {
|
|
23
|
+
session_key: z3.ZodOptional<z3.ZodString>;
|
|
24
|
+
harness: z3.ZodOptional<z3.ZodString>;
|
|
25
|
+
conversation_id: z3.ZodOptional<z3.ZodString>;
|
|
26
|
+
};
|
|
27
|
+
output: {
|
|
28
|
+
wakeshift: z3.ZodOptional<z3.ZodString>;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
v4: {
|
|
32
|
+
inputs: {
|
|
33
|
+
session_key: z.ZodOptional<z.ZodString>;
|
|
34
|
+
harness: z.ZodOptional<z.ZodString>;
|
|
35
|
+
conversation_id: z.ZodOptional<z.ZodString>;
|
|
36
|
+
};
|
|
37
|
+
output: {
|
|
38
|
+
wakeshift: z.ZodOptional<z.ZodString>;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
21
42
|
/** Wakeshift's three arguments, as attached.inputs adds them. */
|
|
22
43
|
export type WakeInputs = typeof wakeInputs;
|
|
23
44
|
/** The optional wakeshift string attached.output adds. */
|
|
24
45
|
export type WakeOutputShape = typeof wakeOutput;
|
|
46
|
+
/** Wakeshift's three arguments in zod 4, as attached.inputs adds them to a zod 4 object. */
|
|
47
|
+
export type WakeInputsV4 = typeof byVersion.v4.inputs;
|
|
48
|
+
/** Wakeshift's three arguments in zod 3, as attached.inputs adds them to a zod 3 object. */
|
|
49
|
+
export type WakeInputsV3 = typeof byVersion.v3.inputs;
|
|
50
|
+
/** The optional wakeshift string in zod 4, as attached.output adds it to a zod 4 object. */
|
|
51
|
+
export type WakeOutputV4 = typeof byVersion.v4.output;
|
|
52
|
+
/** The optional wakeshift string in zod 3, as attached.output adds it to a zod 3 object. */
|
|
53
|
+
export type WakeOutputV3 = typeof byVersion.v3.output;
|
|
54
|
+
/** What a wrapped tool's arguments must include: Wakeshift's three (attached.inputs adds them). */
|
|
55
|
+
export type WakeArgs = {
|
|
56
|
+
[K in WakeKey]?: string | undefined;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The callback attached.events returns, for `server.registerTool`: it takes
|
|
60
|
+
* the tool's arguments (A, Wakeshift's three included) and your MCP SDK's
|
|
61
|
+
* request context (C).
|
|
62
|
+
*/
|
|
63
|
+
export type WrappedCallback<A, C> = (args: A, extra: C) => Promise<any>;
|
|
25
64
|
/** The names of Wakeshift's three arguments. */
|
|
26
65
|
export type WakeKey = typeof SESSION_ARG | typeof HARNESS_ARG | typeof CONVERSATION_ARG;
|
|
27
66
|
/** An input shape with Wakeshift's three arguments in it (attached.inputs'). */
|
|
@@ -63,7 +102,7 @@ export interface ToolSubscribeOptions {
|
|
|
63
102
|
* calls that subscribe the calling conversation. Only the wrapper makes
|
|
64
103
|
* one, and it works until the handler returns (then CallEnded).
|
|
65
104
|
*/
|
|
66
|
-
export interface ToolCall {
|
|
105
|
+
export interface ToolCall<C = any> {
|
|
67
106
|
/** Who attach's identify says is calling (undefined for none). */
|
|
68
107
|
readonly user: string | undefined;
|
|
69
108
|
/** The tool being called. */
|
|
@@ -80,8 +119,8 @@ export interface ToolCall {
|
|
|
80
119
|
readonly session: string | null;
|
|
81
120
|
/** What Wakeshift recognized the agent's program as: null until a subscribe or resolveSession. */
|
|
82
121
|
readonly recognized: Recognized | null;
|
|
83
|
-
/**
|
|
84
|
-
readonly extra:
|
|
122
|
+
/** The call's request context, as your MCP SDK passes it to the tool's callback: 2.x's ctx, 1.x's extra. */
|
|
123
|
+
readonly extra: C;
|
|
85
124
|
/**
|
|
86
125
|
* Subscribes the calling conversation to event with args, or renews its
|
|
87
126
|
* subscription. The reply then carries what the conversation needs to be
|
|
@@ -105,14 +144,14 @@ export declare class CallEnded extends Error {
|
|
|
105
144
|
constructor();
|
|
106
145
|
}
|
|
107
146
|
/** attach's event options. */
|
|
108
|
-
export interface EventsOptions {
|
|
147
|
+
export interface EventsOptions<C = any> {
|
|
109
148
|
/**
|
|
110
149
|
* Asked first whether a wrapped tool's call comes from a client that
|
|
111
150
|
* subscribes to the app's events itself (call.native), for callers the
|
|
112
151
|
* SDK can't tell (an app's own house agents). False leaves it to the
|
|
113
|
-
* SDK's own checks.
|
|
152
|
+
* SDK's own checks. It gets the call's request context, as identify does.
|
|
114
153
|
*/
|
|
115
|
-
native
|
|
154
|
+
native?(extra: C): boolean;
|
|
116
155
|
}
|
|
117
156
|
/** /calls/addition's answer. */
|
|
118
157
|
export interface CallAddition {
|
|
@@ -120,25 +159,38 @@ export interface CallAddition {
|
|
|
120
159
|
trailer?: string;
|
|
121
160
|
meta?: Record<string, unknown>;
|
|
122
161
|
}
|
|
162
|
+
/** The structuredContent field a wrapped tool's structured result carries Wakeshift's block in (the same in every SDK). */
|
|
163
|
+
export declare const WAKESHIFT_FIELD = "wakeshift";
|
|
123
164
|
/**
|
|
124
165
|
* Wakeshift attached to an MCP server: the handle `wake.attach` returns.
|
|
125
166
|
* Wrap a tool's callback with events, and give its input schema
|
|
126
|
-
* Wakeshift's three arguments with inputs.
|
|
167
|
+
* Wakeshift's three arguments with inputs. C is your MCP SDK's request
|
|
168
|
+
* context (2.x's ctx, 1.x's extra), which a wrapped call carries as
|
|
169
|
+
* `call.extra`.
|
|
127
170
|
*/
|
|
128
|
-
export declare class Attached {
|
|
171
|
+
export declare class Attached<C = any> {
|
|
129
172
|
private readonly at;
|
|
130
173
|
/**
|
|
131
|
-
*
|
|
132
|
-
* and conversation_id), for a wrapped tool's
|
|
174
|
+
* A tool's input schema plus Wakeshift's three optional arguments
|
|
175
|
+
* (session_key, harness and conversation_id), for a wrapped tool's
|
|
176
|
+
* inputSchema: a zod object, `inputSchema: attached.inputs(z.object({ table: z.string() }))`
|
|
177
|
+
* (as @modelcontextprotocol/server 2.x takes it), or a shape,
|
|
133
178
|
* `inputSchema: attached.inputs({ table: z.string() })`.
|
|
134
179
|
*/
|
|
180
|
+
inputs<O extends z4.ZodObject>(schema: O): z4.ZodObject<O["shape"] & WakeInputsV4, O["_zod"]["config"]>;
|
|
181
|
+
inputs<O extends z3.AnyZodObject>(schema: O): z3.ZodObject<O["shape"] & WakeInputsV3, O["_def"]["unknownKeys"], O["_def"]["catchall"]>;
|
|
135
182
|
inputs<S extends ZodRawShapeCompat>(shape: S): S & WakeInputs;
|
|
136
183
|
/**
|
|
137
|
-
*
|
|
138
|
-
* outputSchema
|
|
139
|
-
*
|
|
140
|
-
*
|
|
184
|
+
* A tool's output schema plus an optional wakeshift string, for a wrapped
|
|
185
|
+
* tool's outputSchema (a zod object or a shape, as inputs takes).
|
|
186
|
+
* Optional: every structured result of a wrapped tool gets the block's
|
|
187
|
+
* text (without the beacon line) in wakeshift, for hosts that show the
|
|
188
|
+
* model only structuredContent, and tools/list names it in the tool's
|
|
189
|
+
* output schema; this declares it in your own schema and types. The
|
|
190
|
+
* block stays in the content too.
|
|
141
191
|
*/
|
|
192
|
+
output<O extends z4.ZodObject>(schema: O): z4.ZodObject<O["shape"] & WakeOutputV4, O["_zod"]["config"]>;
|
|
193
|
+
output<O extends z3.AnyZodObject>(schema: O): z3.ZodObject<O["shape"] & WakeOutputV3, O["_def"]["unknownKeys"], O["_def"]["catchall"]>;
|
|
142
194
|
output<S extends ZodRawShapeCompat>(shape: S): S & WakeOutputShape;
|
|
143
195
|
/**
|
|
144
196
|
* Wraps a tool's callback, for `server.registerTool(name, …)` with the
|
|
@@ -148,7 +200,7 @@ export declare class Attached {
|
|
|
148
200
|
* inputSchema must come from attached.inputs (a type error otherwise).
|
|
149
201
|
* Register it on the server passed to attach.
|
|
150
202
|
*/
|
|
151
|
-
events<
|
|
203
|
+
events<A extends WakeArgs>(name: string, handler: (args: Omit<A, WakeKey>, call: ToolCall<C>) => WrappedResult | Promise<WrappedResult>): WrappedCallback<A, C>;
|
|
152
204
|
private run;
|
|
205
|
+
private amendTools;
|
|
153
206
|
}
|
|
154
|
-
export {};
|
package/dist/toolcall.js
CHANGED
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import * as z3 from "zod/v3";
|
|
20
20
|
import * as z4 from "zod/v4";
|
|
21
|
-
import
|
|
21
|
+
import * as z4mini from "zod/v4-mini";
|
|
22
|
+
import { getObjectShape, isSchema, isZ4Schema, normalizeObjectSchema } from "./zod.js";
|
|
22
23
|
import { WakeshiftError } from "./errors.js";
|
|
23
24
|
import { EXTENSION_KEY, dialectOf } from "./spec.js";
|
|
24
|
-
import {
|
|
25
|
+
import { takesTasks } from "./discover.js";
|
|
26
|
+
import { packCall, userAgentOf } from "./wakeshift.js";
|
|
25
27
|
// Wakeshift's three arguments (wakeshift.ts's SESSION_ARG, HARNESS_ARG and
|
|
26
28
|
// CONVERSATION_ARG, repeated: that module imports this one).
|
|
27
29
|
const SESSION_ARG = "session_key";
|
|
@@ -33,6 +35,8 @@ const SESSION_KEY_DESCRIPTION = "This conversation's session_key, from an earlie
|
|
|
33
35
|
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";
|
|
34
36
|
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";
|
|
35
37
|
const WAKESHIFT_OUTPUT_DESCRIPTION = "What Wakeshift adds for this conversation: how it's woken by this app's events";
|
|
38
|
+
/** @internal Wakeshift's three arguments' descriptions, for a tool whose input schema is written out (the wait tool, tasks.ts). */
|
|
39
|
+
export const WAKE_ARG_DESCRIPTIONS = { session_key: SESSION_KEY_DESCRIPTION, harness: HARNESS_DESCRIPTION, conversation_id: CONVERSATION_DESCRIPTION };
|
|
36
40
|
const wakeInputs = {
|
|
37
41
|
[SESSION_ARG]: z.string().optional().describe(SESSION_KEY_DESCRIPTION),
|
|
38
42
|
[HARNESS_ARG]: z.string().optional().describe(HARNESS_DESCRIPTION),
|
|
@@ -63,12 +67,26 @@ const byVersion = {
|
|
|
63
67
|
},
|
|
64
68
|
};
|
|
65
69
|
// fieldsFor is Wakeshift's fields in the zod API shape's own fields use
|
|
66
|
-
// (the default import's for
|
|
67
|
-
|
|
70
|
+
// (for an empty shape, the default import's, or zod 4's for the MCP SDK
|
|
71
|
+
// 2.x, which takes only zod 4).
|
|
72
|
+
function fieldsFor(shape, kind, host) {
|
|
68
73
|
const first = Object.values(shape ?? {})[0];
|
|
69
|
-
const
|
|
74
|
+
const version = first ? (isZ4Schema(first) ? "v4" : "v3") : host?.version === 2 ? "v4" : null;
|
|
75
|
+
const fields = version ? byVersion[version][kind] : kind === "inputs" ? wakeInputs : wakeOutput;
|
|
70
76
|
return fields;
|
|
71
77
|
}
|
|
78
|
+
/** @internal Wakeshift's three arguments as attach's own tools take them, in the zod the host's MCP SDK takes. */
|
|
79
|
+
export function wakeInputsFor(host) {
|
|
80
|
+
return host.version === 2 ? byVersion.v4.inputs : wakeInputs;
|
|
81
|
+
}
|
|
82
|
+
// withFields is an object schema with fields added, in the schema's own zod
|
|
83
|
+
// API (zod 3, zod 4 classic or mini).
|
|
84
|
+
function withFields(schema, fields) {
|
|
85
|
+
const s = schema;
|
|
86
|
+
if (typeof s.extend === "function")
|
|
87
|
+
return s.extend(fields);
|
|
88
|
+
return z4mini.extend(schema, fields);
|
|
89
|
+
}
|
|
72
90
|
const WAKE_KEYS = [SESSION_ARG, HARNESS_ARG, CONVERSATION_ARG];
|
|
73
91
|
/**
|
|
74
92
|
* @internal The tools/call _meta keys a harness names its conversation in
|
|
@@ -114,15 +132,38 @@ function takeTrailer(content) {
|
|
|
114
132
|
content[content.length - 1] = { ...last, text: rest };
|
|
115
133
|
return line;
|
|
116
134
|
}
|
|
135
|
+
/** The structuredContent field a wrapped tool's structured result carries Wakeshift's block in (the same in every SDK). */
|
|
136
|
+
export const WAKESHIFT_FIELD = "wakeshift";
|
|
137
|
+
/**
|
|
138
|
+
* @internal An object output schema (JSON Schema, as tools/list gives it)
|
|
139
|
+
* with an optional wakeshift string added, as a copy, so a client that
|
|
140
|
+
* checks structuredContent against it takes the block. Anything else, or a
|
|
141
|
+
* schema that already names it, comes back as it was.
|
|
142
|
+
* test/wrapped-block.json (output_schemas) pins it.
|
|
143
|
+
*/
|
|
144
|
+
export function withWakeshiftField(schema) {
|
|
145
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema))
|
|
146
|
+
return schema;
|
|
147
|
+
const s = schema;
|
|
148
|
+
if (s.type !== "object")
|
|
149
|
+
return schema;
|
|
150
|
+
const props = s.properties;
|
|
151
|
+
if (props !== undefined && (!props || typeof props !== "object" || Array.isArray(props)))
|
|
152
|
+
return schema;
|
|
153
|
+
if (props && WAKESHIFT_FIELD in props)
|
|
154
|
+
return schema;
|
|
155
|
+
return { ...s, properties: { ...(props ?? {}), [WAKESHIFT_FIELD]: { type: "string", description: WAKESHIFT_OUTPUT_DESCRIPTION } } };
|
|
156
|
+
}
|
|
117
157
|
/**
|
|
118
158
|
* @internal Adds the call's block to a wrapped handler's result: Wakeshift's
|
|
119
159
|
* text as a block after the app's content, then a text-carried beacon last,
|
|
120
160
|
* and _meta; a string result stays one text block. A result that already
|
|
121
|
-
* carries a beacon keeps it, last, and gets no second one.
|
|
122
|
-
* structuredContent
|
|
123
|
-
* is the shape, shared with
|
|
161
|
+
* carries a beacon keeps it, last, and gets no second one. A structured
|
|
162
|
+
* result (structuredContent an object) gets the block's text in its
|
|
163
|
+
* wakeshift field too. test/wrapped-block.json is the shape, shared with
|
|
164
|
+
* the other SDKs.
|
|
124
165
|
*/
|
|
125
|
-
export function mergeBlock(result, add
|
|
166
|
+
export function mergeBlock(result, add) {
|
|
126
167
|
const isString = typeof result === "string";
|
|
127
168
|
const base = isString ? { content: [{ type: "text", text: result }] } : result;
|
|
128
169
|
const out = { ...base, content: [...(base.content ?? [])] };
|
|
@@ -145,8 +186,8 @@ export function mergeBlock(result, add, wakeOutput) {
|
|
|
145
186
|
const texts = out.content.map((c) => (c.type === "text" ? c.text : "")).filter((t) => t !== "");
|
|
146
187
|
out.content = [{ type: "text", text: texts.join("\n\n") }];
|
|
147
188
|
}
|
|
148
|
-
if (text &&
|
|
149
|
-
out.structuredContent = { ...out.structuredContent,
|
|
189
|
+
if (text && out.structuredContent && typeof out.structuredContent === "object" && !Array.isArray(out.structuredContent)) {
|
|
190
|
+
out.structuredContent = { ...out.structuredContent, [WAKESHIFT_FIELD]: text };
|
|
150
191
|
}
|
|
151
192
|
return out;
|
|
152
193
|
}
|
|
@@ -183,12 +224,6 @@ function sameJSON(a, b) {
|
|
|
183
224
|
function detailOf(err) {
|
|
184
225
|
return err instanceof WakeshiftError ? err.detail : String(err?.message ?? err);
|
|
185
226
|
}
|
|
186
|
-
function registeredTool(server, name) {
|
|
187
|
-
const tools = server._registeredTools;
|
|
188
|
-
if (!tools || typeof tools !== "object")
|
|
189
|
-
return null; // an MCP SDK that keeps them elsewhere
|
|
190
|
-
return tools[name];
|
|
191
|
-
}
|
|
192
227
|
/** One tools/call of a wrapped tool: what its handler subscribed, for the block and the rollback. */
|
|
193
228
|
class CallScope {
|
|
194
229
|
at;
|
|
@@ -226,16 +261,18 @@ class CallScope {
|
|
|
226
261
|
// User-Agent, the agent's harness and conversation_id arguments, the
|
|
227
262
|
// conversation's session, and who's calling.
|
|
228
263
|
wire(session = this.session) {
|
|
229
|
-
const
|
|
264
|
+
const req = this.at.host.req(this.extra);
|
|
265
|
+
const meta = req.meta;
|
|
230
266
|
return packCall({
|
|
231
267
|
meta,
|
|
232
268
|
clientName: this.at.clientOf(meta, this.identified),
|
|
233
269
|
user: this.user,
|
|
234
270
|
session: session ?? undefined,
|
|
235
|
-
userAgent: userAgentOf(
|
|
271
|
+
userAgent: userAgentOf(req),
|
|
236
272
|
harness: this.args.harness,
|
|
237
273
|
conversation: this.args.conversation,
|
|
238
274
|
tool: this.tool,
|
|
275
|
+
tasks: takesTasks(meta, req),
|
|
239
276
|
});
|
|
240
277
|
}
|
|
241
278
|
// request runs one request of the call to Wakeshift, after any before it
|
|
@@ -388,19 +425,12 @@ class CallScope {
|
|
|
388
425
|
function recognizedOf(a) {
|
|
389
426
|
return { harness: a?.harness ?? null, via: a?.via ?? null, claimed: a?.claimed ?? null };
|
|
390
427
|
}
|
|
391
|
-
function headerOf(extra, name) {
|
|
392
|
-
const h = extra?.requestInfo?.headers;
|
|
393
|
-
if (!h)
|
|
394
|
-
return "";
|
|
395
|
-
const v = h[name] ?? h[name.toLowerCase()];
|
|
396
|
-
return (Array.isArray(v) ? v[0] : v) ?? "";
|
|
397
|
-
}
|
|
398
|
-
function userAgentOf(extra) {
|
|
399
|
-
return headerOf(extra, "user-agent") || headerOf(extra, "User-Agent");
|
|
400
|
-
}
|
|
401
428
|
function hasKey(o, key) {
|
|
402
429
|
return !!o && typeof o === "object" && !Array.isArray(o) && o[key] !== undefined;
|
|
403
430
|
}
|
|
431
|
+
function isRecord(v) {
|
|
432
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
433
|
+
}
|
|
404
434
|
/**
|
|
405
435
|
* @internal Whether a call comes from a client that subscribes to the app's
|
|
406
436
|
* events itself, on stateless servers too (which never see initialize), in
|
|
@@ -411,15 +441,16 @@ function hasKey(o, key) {
|
|
|
411
441
|
* _meta's). conversation is ChatGPT's conversation, when it names one.
|
|
412
442
|
*/
|
|
413
443
|
export function nativeOf(at, extra, identified) {
|
|
414
|
-
const
|
|
444
|
+
const req = at.host.req(extra);
|
|
445
|
+
const meta = (req.meta ?? {});
|
|
415
446
|
const conversation = typeof meta["openai/session"] === "string" && meta["openai/session"] ? meta["openai/session"] : null;
|
|
416
447
|
if (at.events.native?.(extra))
|
|
417
448
|
return { native: true, conversation };
|
|
418
449
|
if (conversation)
|
|
419
450
|
return { native: true, conversation };
|
|
420
|
-
if (dialectOf(at.clientOf(meta, identified), userAgentOf(
|
|
451
|
+
if (dialectOf(at.clientOf(meta, identified), userAgentOf(req)) === "chatgpt")
|
|
421
452
|
return { native: true, conversation: null };
|
|
422
|
-
const caps = at.
|
|
453
|
+
const caps = at.host.clientCapabilities();
|
|
423
454
|
if (hasKey(caps?.extensions, EXTENSION_KEY))
|
|
424
455
|
return { native: true, conversation: null };
|
|
425
456
|
const metaCaps = meta["io.modelcontextprotocol/clientCapabilities"];
|
|
@@ -430,7 +461,9 @@ export function nativeOf(at, extra, identified) {
|
|
|
430
461
|
/**
|
|
431
462
|
* Wakeshift attached to an MCP server: the handle `wake.attach` returns.
|
|
432
463
|
* Wrap a tool's callback with events, and give its input schema
|
|
433
|
-
* Wakeshift's three arguments with inputs.
|
|
464
|
+
* Wakeshift's three arguments with inputs. C is your MCP SDK's request
|
|
465
|
+
* context (2.x's ctx, 1.x's extra), which a wrapped call carries as
|
|
466
|
+
* `call.extra`.
|
|
434
467
|
*/
|
|
435
468
|
export class Attached {
|
|
436
469
|
at;
|
|
@@ -440,27 +473,33 @@ export class Attached {
|
|
|
440
473
|
constructor(at) {
|
|
441
474
|
this.at = at;
|
|
442
475
|
}
|
|
443
|
-
/**
|
|
444
|
-
* shape plus Wakeshift's three optional arguments (session_key, harness
|
|
445
|
-
* and conversation_id), for a wrapped tool's inputSchema:
|
|
446
|
-
* `inputSchema: attached.inputs({ table: z.string() })`.
|
|
447
|
-
*/
|
|
448
476
|
inputs(shape) {
|
|
477
|
+
if (isSchema(shape)) {
|
|
478
|
+
const own = getObjectShape(shape);
|
|
479
|
+
if (!own)
|
|
480
|
+
throw new Error("wakeshift: attached.inputs takes a zod object or a shape");
|
|
481
|
+
for (const k of WAKE_KEYS)
|
|
482
|
+
if (k in own)
|
|
483
|
+
throw new Error(`wakeshift: attached.inputs adds ${k} itself: leave it out of the schema`);
|
|
484
|
+
return withFields(shape, byVersion[isZ4Schema(shape) ? "v4" : "v3"].inputs);
|
|
485
|
+
}
|
|
449
486
|
for (const k of WAKE_KEYS)
|
|
450
487
|
if (k in (shape ?? {}))
|
|
451
488
|
throw new Error(`wakeshift: attached.inputs adds ${k} itself: leave it out of the shape`);
|
|
452
|
-
return { ...shape, ...fieldsFor(shape, "inputs") };
|
|
489
|
+
return { ...shape, ...fieldsFor(shape, "inputs", this.at.host) };
|
|
453
490
|
}
|
|
454
|
-
/**
|
|
455
|
-
* shape plus an optional wakeshift string, for a wrapped tool's
|
|
456
|
-
* outputSchema: the wrapper sets it to the block's text (without the
|
|
457
|
-
* beacon line), for hosts that show the model only structuredContent.
|
|
458
|
-
* The block stays in the content too.
|
|
459
|
-
*/
|
|
460
491
|
output(shape) {
|
|
492
|
+
if (isSchema(shape)) {
|
|
493
|
+
const own = getObjectShape(shape);
|
|
494
|
+
if (!own)
|
|
495
|
+
throw new Error("wakeshift: attached.output takes a zod object or a shape");
|
|
496
|
+
if (WAKESHIFT_FIELD in own)
|
|
497
|
+
throw new Error("wakeshift: attached.output adds wakeshift itself: leave it out of the schema");
|
|
498
|
+
return withFields(shape, byVersion[isZ4Schema(shape) ? "v4" : "v3"].output);
|
|
499
|
+
}
|
|
461
500
|
if ("wakeshift" in (shape ?? {}))
|
|
462
501
|
throw new Error("wakeshift: attached.output adds wakeshift itself: leave it out of the shape");
|
|
463
|
-
return { ...shape, ...fieldsFor(shape, "output") };
|
|
502
|
+
return { ...shape, ...fieldsFor(shape, "output", this.at.host) };
|
|
464
503
|
}
|
|
465
504
|
/**
|
|
466
505
|
* Wraps a tool's callback, for `server.registerTool(name, …)` with the
|
|
@@ -480,11 +519,11 @@ export class Attached {
|
|
|
480
519
|
return cb;
|
|
481
520
|
}
|
|
482
521
|
async run(name, cb, handler, input, extra) {
|
|
483
|
-
const
|
|
522
|
+
const host = this.at.host;
|
|
523
|
+
const tool = host.registeredTool(name);
|
|
484
524
|
if (tool === undefined || (tool && (tool.handler ?? tool.callback) !== cb)) {
|
|
485
525
|
throw new Error(`wakeshift: attached.events("${name}", …) isn't the tool named ${name} on the server passed to attach: register it with the same name, on that server`);
|
|
486
526
|
}
|
|
487
|
-
let wakeOutput = false;
|
|
488
527
|
let outputSchema;
|
|
489
528
|
if (tool) {
|
|
490
529
|
const shape = getObjectShape(normalizeObjectSchema(tool.inputSchema));
|
|
@@ -492,8 +531,6 @@ export class Attached {
|
|
|
492
531
|
throw new Error(`wakeshift: the tool ${name} doesn't take Wakeshift's arguments: make its inputSchema with attached.inputs(…)`);
|
|
493
532
|
}
|
|
494
533
|
outputSchema = tool.outputSchema;
|
|
495
|
-
const out = getObjectShape(normalizeObjectSchema(outputSchema));
|
|
496
|
-
wakeOutput = !!out && "wakeshift" in out;
|
|
497
534
|
}
|
|
498
535
|
else {
|
|
499
536
|
this.at.warnOnce("internals", "wakeshift: this MCP SDK keeps its tools where Wakeshift can't read them, so wrapped tools' registration and output aren't checked");
|
|
@@ -521,7 +558,7 @@ export class Attached {
|
|
|
521
558
|
await call.rollback();
|
|
522
559
|
return result;
|
|
523
560
|
}
|
|
524
|
-
if ((typeof result === "object" && result.isError) || extra
|
|
561
|
+
if ((typeof result === "object" && result.isError) || host.req(extra).signal?.aborted || !(await host.validOutput(outputSchema, result))) {
|
|
525
562
|
await call.rollback();
|
|
526
563
|
return typeof result === "string" ? { content: [{ type: "text", text: result }] } : result;
|
|
527
564
|
}
|
|
@@ -536,23 +573,47 @@ export class Attached {
|
|
|
536
573
|
this.at.wake.warn(`wakeshift: Wakeshift's block for a result of ${name} failed, so the call's subscriptions were rolled back: ${detailOf(err)}`);
|
|
537
574
|
return { isError: true, content: [{ type: "text", text: `Wakeshift couldn't finish setting up this conversation's wakes (${detailOf(err)}). Try again.` }] };
|
|
538
575
|
}
|
|
539
|
-
const out = mergeBlock(result, add ?? {}
|
|
540
|
-
if ((
|
|
541
|
-
|
|
576
|
+
const out = mergeBlock(result, add ?? {});
|
|
577
|
+
if (isRecord(out.structuredContent) && WAKESHIFT_FIELD in out.structuredContent && !(await host.validOutput(outputSchema, out))) {
|
|
578
|
+
// A schema that refuses fields it doesn't name (a strict object):
|
|
579
|
+
// MCP would refuse the result, so it goes without the field.
|
|
580
|
+
const { [WAKESHIFT_FIELD]: _dropped, ...own } = out.structuredContent;
|
|
581
|
+
out.structuredContent = own;
|
|
582
|
+
this.at.warnOnce("output:" + name, `wakeshift: the wrapped tool ${name}'s outputSchema refuses a wakeshift field, so hosts that show only structuredContent won't see how this conversation is woken: make it with attached.output`);
|
|
542
583
|
}
|
|
543
584
|
return out;
|
|
544
585
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
586
|
+
/**
|
|
587
|
+
* @internal Wraps the server's tools/list handler so each wrapped tool's
|
|
588
|
+
* object output schema names wakeshift (withWakeshiftField). McpServer
|
|
589
|
+
* sets the handler when the first tool is registered, perhaps after
|
|
590
|
+
* attach, so a later set is wrapped too (as tasks.ts does).
|
|
591
|
+
*/
|
|
592
|
+
amendToolsList() {
|
|
593
|
+
const handlers = this.at.host.handlers();
|
|
594
|
+
if (!handlers)
|
|
595
|
+
return; // a future SDK: schemas stay as registered
|
|
596
|
+
const wrap = (h) => async (request, extra) => {
|
|
597
|
+
const out = await h(request, extra);
|
|
598
|
+
if (out && Array.isArray(out.tools))
|
|
599
|
+
this.amendTools(out.tools);
|
|
600
|
+
return out;
|
|
601
|
+
};
|
|
602
|
+
const h = handlers.get("tools/list");
|
|
603
|
+
if (h)
|
|
604
|
+
Map.prototype.set.call(handlers, "tools/list", wrap(h));
|
|
605
|
+
const set = handlers.set.bind(handlers);
|
|
606
|
+
handlers.set = (method, h) => set(method, method === "tools/list" ? wrap(h) : h);
|
|
607
|
+
}
|
|
608
|
+
amendTools(tools) {
|
|
609
|
+
for (let i = 0; i < tools.length; i++) {
|
|
610
|
+
const t = tools[i];
|
|
611
|
+
if (!t || typeof t !== "object" || typeof t.name !== "string" || !this.wrapped.has(t.name) || !t.outputSchema)
|
|
612
|
+
continue;
|
|
613
|
+
const schema = withWakeshiftField(t.outputSchema);
|
|
614
|
+
if (schema !== t.outputSchema)
|
|
615
|
+
tools[i] = { ...t, outputSchema: schema };
|
|
616
|
+
}
|
|
617
|
+
}
|
|
557
618
|
}
|
|
558
619
|
//# sourceMappingURL=toolcall.js.map
|