@zetaloop/chappie 0.4.1 → 1.0.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/.codex-plugin/plugin.json +13 -0
- package/README.md +26 -35
- package/dist/chunk-5CKVVDZ2.js +1042 -0
- package/dist/chunk-DM523QRR.js +148 -0
- package/dist/chunk-DN7OHZK6.js +110 -0
- package/dist/chunk-DQWC5GFN.js +1745 -0
- package/dist/chunk-EIPDEA3I.js +286 -0
- package/dist/chunk-GWYP7B66.js +8479 -0
- package/dist/chunk-GX4JWLHH.js +98 -0
- package/dist/chunk-K2VE2MDU.js +14916 -0
- package/dist/chunk-OWAAWZNT.js +56 -0
- package/dist/chunk-UOFCCHBS.js +94 -0
- package/dist/cli.js +47 -0
- package/dist/codex-RI2XL4HV.js +5144 -0
- package/dist/codex-plugin-NSKWB3WS.js +179 -0
- package/dist/codex.json +23 -0
- package/dist/index.js +4752 -0
- package/dist/instructions.md +13 -0
- package/dist/omp.js +240 -0
- package/dist/opencode.js +904 -0
- package/dist/promise-OHH5J44X.js +72 -0
- package/dist/stdio-KJVCYPXJ.js +1279 -0
- package/docs/setup.md +82 -0
- package/docs/tools.md +108 -100
- package/package.json +100 -56
- package/src/activity.ts +0 -21
- package/src/broker.ts +0 -1008
- package/src/config.ts +0 -21
- package/src/delivery.ts +0 -54
- package/src/history.ts +0 -123
- package/src/index.ts +0 -39
- package/src/instructions.md +0 -19
- package/src/ipc.ts +0 -477
- package/src/provider.ts +0 -194
- package/src/questions.ts +0 -95
- package/src/resources.ts +0 -189
- package/src/server.ts +0 -615
- package/src/session.ts +0 -603
- package/src/state.ts +0 -179
- package/src/stdio.ts +0 -58
- package/src/tools.ts +0 -91
- package/src/transfer.ts +0 -162
- /package/{src → dist}/question.html +0 -0
package/src/server.ts
DELETED
|
@@ -1,615 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
|
3
|
-
import * as z from "zod";
|
|
4
|
-
import packageJson from "../package.json" with { type: "json" };
|
|
5
|
-
import type { Broker } from "./broker.ts";
|
|
6
|
-
import { deliveryContent } from "./delivery.ts";
|
|
7
|
-
import { historyInput } from "./history.ts";
|
|
8
|
-
import {
|
|
9
|
-
answerContent,
|
|
10
|
-
answerInput,
|
|
11
|
-
questionInput,
|
|
12
|
-
questionInstructions,
|
|
13
|
-
questionOutput,
|
|
14
|
-
} from "./questions.ts";
|
|
15
|
-
import {
|
|
16
|
-
directTools,
|
|
17
|
-
inputContent,
|
|
18
|
-
type ToolInput,
|
|
19
|
-
toolResult,
|
|
20
|
-
} from "./tools.ts";
|
|
21
|
-
|
|
22
|
-
const instructions = readFileSync(
|
|
23
|
-
new URL("./instructions.md", import.meta.url),
|
|
24
|
-
"utf8",
|
|
25
|
-
).trim();
|
|
26
|
-
|
|
27
|
-
const outputSchema = z.object({
|
|
28
|
-
text: z
|
|
29
|
-
.string()
|
|
30
|
-
.describe(
|
|
31
|
-
"Complete text output, including Pi user input, submitted webpage answers, and deferred results. Images and file resources accompany it as native content blocks.",
|
|
32
|
-
),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const syncInstructions =
|
|
36
|
-
"On conflicting activity, start sync and verify with your own initialization code. Keep codes private. Verification returns a new code to the coordinator; ordinary tools and initialization stay locked. Discuss through chat and history, including after failed verification. The coordinator decides who continues, their tasks, and who exits, including retaining only one execution. Acknowledge the decision; if asked to exit, leave a chat handoff and end your response. Only the coordinator releases after decisions and exits are confirmed.";
|
|
37
|
-
|
|
38
|
-
const questionTemplate = "ui://chappie/question.html";
|
|
39
|
-
const questionSchema = outputSchema.extend({ question: questionOutput });
|
|
40
|
-
const toolAnnotations = {
|
|
41
|
-
readOnlyHint: true,
|
|
42
|
-
destructiveHint: false,
|
|
43
|
-
idempotentHint: true,
|
|
44
|
-
openWorldHint: false,
|
|
45
|
-
} as const;
|
|
46
|
-
|
|
47
|
-
interface RequestContext {
|
|
48
|
-
mcpReq: {
|
|
49
|
-
_meta?: Record<string, unknown>;
|
|
50
|
-
signal: AbortSignal;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function createServer(broker: Broker): McpServer {
|
|
55
|
-
const server = new McpServer(
|
|
56
|
-
{
|
|
57
|
-
name: "chappie",
|
|
58
|
-
version: packageJson.version,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
instructions: [
|
|
62
|
-
instructions,
|
|
63
|
-
...(broker.askEnabled ? [questionInstructions] : []),
|
|
64
|
-
...(broker.syncEnabled ? [syncInstructions] : []),
|
|
65
|
-
].join("\n\n"),
|
|
66
|
-
},
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
function handle<Args, Result>(
|
|
70
|
-
callback: (args: Args, context: RequestContext) => Promise<Result>,
|
|
71
|
-
communication = false,
|
|
72
|
-
) {
|
|
73
|
-
return (args: Args, context: RequestContext): Promise<Result> =>
|
|
74
|
-
broker.run(
|
|
75
|
-
requireChatId(context),
|
|
76
|
-
(args as { sessionId?: string }).sessionId,
|
|
77
|
-
context.mcpReq.signal,
|
|
78
|
-
(signal) =>
|
|
79
|
-
callback(args, {
|
|
80
|
-
...context,
|
|
81
|
-
mcpReq: { ...context.mcpReq, signal },
|
|
82
|
-
}),
|
|
83
|
-
communication,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
server.registerTool(
|
|
88
|
-
"init",
|
|
89
|
-
{
|
|
90
|
-
title: "Connect to Pi",
|
|
91
|
-
description:
|
|
92
|
-
"Select this chat's default Pi session and return its environment, tool catalog, and initialization name for coordination. Use the task's sessionId to resume, or find it by cwd/name with sessions. For a task without a specified target, omit sessionId to reuse the default or select the first online, unbound session. Read recent history when resuming work.",
|
|
93
|
-
outputSchema,
|
|
94
|
-
inputSchema: z.object({
|
|
95
|
-
sessionId: z
|
|
96
|
-
.string()
|
|
97
|
-
.optional()
|
|
98
|
-
.describe("Default Pi session ID; may be shared with other chats"),
|
|
99
|
-
}),
|
|
100
|
-
annotations: toolAnnotations,
|
|
101
|
-
},
|
|
102
|
-
handle(async (args, context) => {
|
|
103
|
-
const chatId = requireChatId(context);
|
|
104
|
-
const { inputs, ...initialized } = await broker.initialize(
|
|
105
|
-
chatId,
|
|
106
|
-
args.sessionId,
|
|
107
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
108
|
-
context.mcpReq.signal,
|
|
109
|
-
);
|
|
110
|
-
return finishResult(broker, context, textResult(initialized, inputs));
|
|
111
|
-
}),
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
server.registerTool(
|
|
115
|
-
"chat",
|
|
116
|
-
{
|
|
117
|
-
title: "Reply in Pi",
|
|
118
|
-
description:
|
|
119
|
-
"Send an assistant message to Pi. Renders Markdown and saves the message in the session transcript.",
|
|
120
|
-
outputSchema,
|
|
121
|
-
inputSchema: z.object({
|
|
122
|
-
text: z.string().min(1).describe("Assistant message in Markdown"),
|
|
123
|
-
sessionId: z
|
|
124
|
-
.string()
|
|
125
|
-
.optional()
|
|
126
|
-
.describe(
|
|
127
|
-
"Pi session for this operation; becomes the default if none is set",
|
|
128
|
-
),
|
|
129
|
-
}),
|
|
130
|
-
annotations: toolAnnotations,
|
|
131
|
-
},
|
|
132
|
-
handle(async (args, context) => {
|
|
133
|
-
const chatId = requireChatId(context);
|
|
134
|
-
const { sessionId, cwd, inputs, initialization } = await broker.chat(
|
|
135
|
-
chatId,
|
|
136
|
-
args.sessionId,
|
|
137
|
-
args.text,
|
|
138
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
139
|
-
context.mcpReq.signal,
|
|
140
|
-
);
|
|
141
|
-
return finishResult(
|
|
142
|
-
broker,
|
|
143
|
-
context,
|
|
144
|
-
textResult(
|
|
145
|
-
{ sessionId, cwd, ...(initialization ? { initialization } : {}) },
|
|
146
|
-
inputs,
|
|
147
|
-
),
|
|
148
|
-
);
|
|
149
|
-
}, true),
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
if (broker.askEnabled) {
|
|
153
|
-
server.registerTool(
|
|
154
|
-
"ask",
|
|
155
|
-
{
|
|
156
|
-
title: "Ask in ChatGPT",
|
|
157
|
-
description:
|
|
158
|
-
"Create a question in ChatGPT and return its ID immediately. Call ask_assert next with question.id. Answers, revisions, and skips arrive as webAnswer in later tool results.",
|
|
159
|
-
inputSchema: questionInput.extend({
|
|
160
|
-
sessionId: z
|
|
161
|
-
.string()
|
|
162
|
-
.optional()
|
|
163
|
-
.describe(
|
|
164
|
-
"Pi session for this question; defaults to this chat's session",
|
|
165
|
-
),
|
|
166
|
-
}),
|
|
167
|
-
outputSchema: questionSchema,
|
|
168
|
-
annotations: toolAnnotations,
|
|
169
|
-
_meta: { ui: { resourceUri: questionTemplate } },
|
|
170
|
-
},
|
|
171
|
-
handle(async ({ sessionId, ...input }, context) => {
|
|
172
|
-
const { initialization, ...question } = await broker.ask(
|
|
173
|
-
requireChatId(context),
|
|
174
|
-
sessionId,
|
|
175
|
-
input,
|
|
176
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
177
|
-
context.mcpReq.signal,
|
|
178
|
-
);
|
|
179
|
-
const result = await finishResult(broker, context, {
|
|
180
|
-
content: [
|
|
181
|
-
...(initialization ? textResult({ initialization }).content : []),
|
|
182
|
-
{
|
|
183
|
-
type: "text",
|
|
184
|
-
text: `Question created. Call ask_assert({"questionId":"${question.id}"}) next.`,
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
});
|
|
188
|
-
return {
|
|
189
|
-
...result,
|
|
190
|
-
structuredContent: { ...result.structuredContent, question },
|
|
191
|
-
};
|
|
192
|
-
}),
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
server.registerTool(
|
|
196
|
-
"ask_assert",
|
|
197
|
-
{
|
|
198
|
-
title: "Assert question display",
|
|
199
|
-
description:
|
|
200
|
-
"Assert that an ask widget loaded in ChatGPT. Call immediately after ask with question.id. Returns when the widget reports loaded; times out if it fails to load. User answers arrive separately as webAnswer.",
|
|
201
|
-
inputSchema: z.object({
|
|
202
|
-
questionId: z.string().describe("question.id returned by ask"),
|
|
203
|
-
}),
|
|
204
|
-
outputSchema: questionSchema,
|
|
205
|
-
annotations: toolAnnotations,
|
|
206
|
-
},
|
|
207
|
-
handle(async ({ questionId }, context) => {
|
|
208
|
-
const question = await broker.assertQuestion(
|
|
209
|
-
requireChatId(context),
|
|
210
|
-
questionId,
|
|
211
|
-
context.mcpReq.signal,
|
|
212
|
-
);
|
|
213
|
-
const result = await finishResult(broker, context, {
|
|
214
|
-
content: [{ type: "text", text: "Question widget loaded." }],
|
|
215
|
-
});
|
|
216
|
-
return {
|
|
217
|
-
...result,
|
|
218
|
-
structuredContent: { ...result.structuredContent, question },
|
|
219
|
-
};
|
|
220
|
-
}),
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
server.registerTool(
|
|
224
|
-
"answer",
|
|
225
|
-
{
|
|
226
|
-
title: "Question state",
|
|
227
|
-
description:
|
|
228
|
-
"Read a saved question, report widget loading, or save an answer, revision, or skip.",
|
|
229
|
-
inputSchema: z.object({
|
|
230
|
-
questionId: z.string(),
|
|
231
|
-
answer: answerInput.optional(),
|
|
232
|
-
loaded: z
|
|
233
|
-
.literal(true)
|
|
234
|
-
.optional()
|
|
235
|
-
.describe("The question widget has loaded"),
|
|
236
|
-
}),
|
|
237
|
-
outputSchema: questionSchema,
|
|
238
|
-
annotations: toolAnnotations,
|
|
239
|
-
_meta: { ui: { visibility: ["app"] }, "openai/widgetAccessible": true },
|
|
240
|
-
},
|
|
241
|
-
async ({ questionId, answer, loaded = false }, context) => {
|
|
242
|
-
const question = await broker.answer(
|
|
243
|
-
requireChatId(context),
|
|
244
|
-
questionId,
|
|
245
|
-
answer,
|
|
246
|
-
loaded,
|
|
247
|
-
);
|
|
248
|
-
const text = answer
|
|
249
|
-
? answer.skipped
|
|
250
|
-
? "Question skipped."
|
|
251
|
-
: "Answer saved."
|
|
252
|
-
: loaded
|
|
253
|
-
? "Question widget loaded."
|
|
254
|
-
: "Question state.";
|
|
255
|
-
return {
|
|
256
|
-
content: [{ type: "text", text }],
|
|
257
|
-
structuredContent: { text, question },
|
|
258
|
-
};
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
server.registerResource(
|
|
263
|
-
"question",
|
|
264
|
-
questionTemplate,
|
|
265
|
-
{ title: "Chappie question", mimeType: "text/html;profile=mcp-app" },
|
|
266
|
-
async () => ({
|
|
267
|
-
contents: [
|
|
268
|
-
{
|
|
269
|
-
uri: questionTemplate,
|
|
270
|
-
mimeType: "text/html;profile=mcp-app",
|
|
271
|
-
text: readFileSync(
|
|
272
|
-
new URL("./question.html", import.meta.url),
|
|
273
|
-
"utf8",
|
|
274
|
-
),
|
|
275
|
-
_meta: {
|
|
276
|
-
ui: {
|
|
277
|
-
prefersBorder: true,
|
|
278
|
-
csp: { connectDomains: [], resourceDomains: [] },
|
|
279
|
-
},
|
|
280
|
-
"openai/widgetDescription":
|
|
281
|
-
"A persistent question the user can answer while the assistant continues working.",
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
}),
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
server.registerTool(
|
|
290
|
-
"tools",
|
|
291
|
-
{
|
|
292
|
-
title: "Pi tools",
|
|
293
|
-
description:
|
|
294
|
-
"Get full definitions of Pi tools for call. Filter by names, or omit names to list all active tools.",
|
|
295
|
-
outputSchema,
|
|
296
|
-
inputSchema: z.object({
|
|
297
|
-
names: z
|
|
298
|
-
.array(z.string())
|
|
299
|
-
.min(1)
|
|
300
|
-
.optional()
|
|
301
|
-
.describe("Tool names to describe; omit to return every active tool"),
|
|
302
|
-
sessionId: z
|
|
303
|
-
.string()
|
|
304
|
-
.optional()
|
|
305
|
-
.describe(
|
|
306
|
-
"Pi session for this operation; becomes the default if none is set",
|
|
307
|
-
),
|
|
308
|
-
}),
|
|
309
|
-
annotations: toolAnnotations,
|
|
310
|
-
},
|
|
311
|
-
handle(async (args, context) => {
|
|
312
|
-
const { inputs, ...inspected } = await broker.tools(
|
|
313
|
-
requireChatId(context),
|
|
314
|
-
args.sessionId,
|
|
315
|
-
args.names,
|
|
316
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
317
|
-
context.mcpReq.signal,
|
|
318
|
-
);
|
|
319
|
-
return finishResult(
|
|
320
|
-
broker,
|
|
321
|
-
context,
|
|
322
|
-
textResult(
|
|
323
|
-
{
|
|
324
|
-
session: inspected.session,
|
|
325
|
-
tools: inspected.tools,
|
|
326
|
-
...(inspected.initialization
|
|
327
|
-
? { initialization: inspected.initialization }
|
|
328
|
-
: {}),
|
|
329
|
-
},
|
|
330
|
-
inputs,
|
|
331
|
-
),
|
|
332
|
-
);
|
|
333
|
-
}),
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
server.registerTool(
|
|
337
|
-
"call",
|
|
338
|
-
{
|
|
339
|
-
title: "Call Pi tools",
|
|
340
|
-
description:
|
|
341
|
-
"Execute Pi tools using the definitions returned by tools. Each calls array is one native Pi batch.",
|
|
342
|
-
outputSchema,
|
|
343
|
-
inputSchema: z.object({
|
|
344
|
-
calls: z
|
|
345
|
-
.array(
|
|
346
|
-
z.object({
|
|
347
|
-
name: z.string(),
|
|
348
|
-
arguments: z.record(z.string(), z.unknown()),
|
|
349
|
-
}),
|
|
350
|
-
)
|
|
351
|
-
.min(1),
|
|
352
|
-
sessionId: z
|
|
353
|
-
.string()
|
|
354
|
-
.optional()
|
|
355
|
-
.describe(
|
|
356
|
-
"Pi session for this operation; becomes the default if none is set",
|
|
357
|
-
),
|
|
358
|
-
}),
|
|
359
|
-
annotations: toolAnnotations,
|
|
360
|
-
},
|
|
361
|
-
handle(async (args, context) => {
|
|
362
|
-
const result = await broker.call(
|
|
363
|
-
requireChatId(context),
|
|
364
|
-
args.sessionId,
|
|
365
|
-
args.calls,
|
|
366
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
367
|
-
context.mcpReq.signal,
|
|
368
|
-
);
|
|
369
|
-
return finishResult(
|
|
370
|
-
broker,
|
|
371
|
-
context,
|
|
372
|
-
toolResult(
|
|
373
|
-
result.toolResults,
|
|
374
|
-
result.sessionId,
|
|
375
|
-
result.cwd,
|
|
376
|
-
result.inputs,
|
|
377
|
-
result.initialization,
|
|
378
|
-
),
|
|
379
|
-
);
|
|
380
|
-
}),
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
for (const tool of directTools) {
|
|
384
|
-
server.registerTool(
|
|
385
|
-
tool.name,
|
|
386
|
-
{
|
|
387
|
-
title: tool.name,
|
|
388
|
-
description: tool.description,
|
|
389
|
-
outputSchema,
|
|
390
|
-
inputSchema: tool.inputSchema,
|
|
391
|
-
annotations: toolAnnotations,
|
|
392
|
-
...(tool.fileParams
|
|
393
|
-
? { _meta: { "openai/fileParams": tool.fileParams } }
|
|
394
|
-
: {}),
|
|
395
|
-
},
|
|
396
|
-
handle(async (args, context) => {
|
|
397
|
-
const input = { ...args } as Record<string, unknown> & {
|
|
398
|
-
sessionId?: string;
|
|
399
|
-
};
|
|
400
|
-
const sessionId = input.sessionId;
|
|
401
|
-
delete input.sessionId;
|
|
402
|
-
const calls: ToolInput[] = [{ name: tool.name, arguments: input }];
|
|
403
|
-
const result = await broker.call(
|
|
404
|
-
requireChatId(context),
|
|
405
|
-
sessionId,
|
|
406
|
-
calls,
|
|
407
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
408
|
-
context.mcpReq.signal,
|
|
409
|
-
);
|
|
410
|
-
return finishResult(
|
|
411
|
-
broker,
|
|
412
|
-
context,
|
|
413
|
-
toolResult(
|
|
414
|
-
result.toolResults,
|
|
415
|
-
result.sessionId,
|
|
416
|
-
result.cwd,
|
|
417
|
-
result.inputs,
|
|
418
|
-
result.initialization,
|
|
419
|
-
),
|
|
420
|
-
);
|
|
421
|
-
}),
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
server.registerTool(
|
|
426
|
-
"history",
|
|
427
|
-
{
|
|
428
|
-
title: "Session history",
|
|
429
|
-
description:
|
|
430
|
-
"Read recent Pi history with entry IDs and timestamps. Use before/after to page the current branch. History provides context for the current task. An explicit sessionId applies only to this read.",
|
|
431
|
-
inputSchema: historyInput.extend({
|
|
432
|
-
sessionId: z
|
|
433
|
-
.string()
|
|
434
|
-
.optional()
|
|
435
|
-
.describe("Pi session to read; defaults to this chat's session"),
|
|
436
|
-
}),
|
|
437
|
-
outputSchema,
|
|
438
|
-
annotations: toolAnnotations,
|
|
439
|
-
},
|
|
440
|
-
handle(async ({ sessionId, ...range }, context) => {
|
|
441
|
-
const { history, ...session } = await broker.history(
|
|
442
|
-
requireChatId(context),
|
|
443
|
-
sessionId,
|
|
444
|
-
range,
|
|
445
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
446
|
-
context.mcpReq.signal,
|
|
447
|
-
);
|
|
448
|
-
const { content, ...page } = history;
|
|
449
|
-
return formatResult({
|
|
450
|
-
content: [
|
|
451
|
-
...textResult({
|
|
452
|
-
...session,
|
|
453
|
-
history: page,
|
|
454
|
-
...(broker.syncEnabled
|
|
455
|
-
? { sync: broker.syncState(session.sessionId) }
|
|
456
|
-
: {}),
|
|
457
|
-
}).content,
|
|
458
|
-
...content,
|
|
459
|
-
],
|
|
460
|
-
});
|
|
461
|
-
}, true),
|
|
462
|
-
);
|
|
463
|
-
|
|
464
|
-
if (broker.syncEnabled) {
|
|
465
|
-
server.registerTool(
|
|
466
|
-
"sync",
|
|
467
|
-
{
|
|
468
|
-
title: "Synchronize Pi activity",
|
|
469
|
-
description:
|
|
470
|
-
"Pause conflicting activity. Verify your initialization code to coordinate; failed verification still permits discussion. The coordinator decides who continues, their tasks, who exits, and when to release.",
|
|
471
|
-
inputSchema: z.object({
|
|
472
|
-
action: z.enum(["start", "verify", "release"]),
|
|
473
|
-
code: z
|
|
474
|
-
.string()
|
|
475
|
-
.optional()
|
|
476
|
-
.describe(
|
|
477
|
-
"Initialization code for verification, or the verified code for release; omit when unavailable",
|
|
478
|
-
),
|
|
479
|
-
sessionId: z
|
|
480
|
-
.string()
|
|
481
|
-
.optional()
|
|
482
|
-
.describe(
|
|
483
|
-
"Pi session to synchronize; defaults to this chat's session",
|
|
484
|
-
),
|
|
485
|
-
}),
|
|
486
|
-
outputSchema,
|
|
487
|
-
annotations: toolAnnotations,
|
|
488
|
-
},
|
|
489
|
-
async ({ action, code, sessionId }, context) =>
|
|
490
|
-
formatResult(
|
|
491
|
-
textResult(
|
|
492
|
-
await broker.sync(
|
|
493
|
-
requireChatId(context),
|
|
494
|
-
sessionId,
|
|
495
|
-
action,
|
|
496
|
-
code,
|
|
497
|
-
context.mcpReq._meta?.["otunnel/requestId"],
|
|
498
|
-
context.mcpReq.signal,
|
|
499
|
-
),
|
|
500
|
-
),
|
|
501
|
-
),
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
server.registerTool(
|
|
506
|
-
"sessions",
|
|
507
|
-
{
|
|
508
|
-
title: "Local sessions",
|
|
509
|
-
description:
|
|
510
|
-
"List online Pi sessions with their IDs, devices, cwd, names, execution status, and saved binding counts. Also returns this chat's default.",
|
|
511
|
-
outputSchema,
|
|
512
|
-
inputSchema: z.object({
|
|
513
|
-
sessionId: z
|
|
514
|
-
.string()
|
|
515
|
-
.optional()
|
|
516
|
-
.describe("Filter the online list to this Pi session"),
|
|
517
|
-
}),
|
|
518
|
-
annotations: toolAnnotations,
|
|
519
|
-
},
|
|
520
|
-
handle(async (args, context) => {
|
|
521
|
-
const chatId = requestChatId(context);
|
|
522
|
-
const inputs = chatId
|
|
523
|
-
? await broker.inputs(chatId, args.sessionId, context.mcpReq.signal)
|
|
524
|
-
: [];
|
|
525
|
-
const result = textResult(
|
|
526
|
-
{
|
|
527
|
-
binding: chatId ? (broker.binding(chatId) ?? null) : null,
|
|
528
|
-
sessions: broker.listSessions(args.sessionId),
|
|
529
|
-
},
|
|
530
|
-
inputs,
|
|
531
|
-
);
|
|
532
|
-
return finishResult(broker, context, result);
|
|
533
|
-
}, true),
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
server.registerResource(
|
|
537
|
-
"Pi resource",
|
|
538
|
-
new ResourceTemplate("chappie://session/{sessionId}/{kind}/{id}/{name}", {
|
|
539
|
-
list: undefined,
|
|
540
|
-
}),
|
|
541
|
-
{ title: "Pi resource" },
|
|
542
|
-
async (uri, _variables, context) => {
|
|
543
|
-
const resource = await broker.readResource(
|
|
544
|
-
uri.href,
|
|
545
|
-
context.mcpReq.signal,
|
|
546
|
-
);
|
|
547
|
-
return {
|
|
548
|
-
contents: [
|
|
549
|
-
{
|
|
550
|
-
uri: resource.uri,
|
|
551
|
-
mimeType: resource.mimeType,
|
|
552
|
-
blob: resource.blob,
|
|
553
|
-
},
|
|
554
|
-
],
|
|
555
|
-
};
|
|
556
|
-
},
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
return server;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function textResult(
|
|
563
|
-
value: unknown,
|
|
564
|
-
inputs: Parameters<typeof inputContent>[0] = [],
|
|
565
|
-
) {
|
|
566
|
-
return {
|
|
567
|
-
content: [
|
|
568
|
-
{ type: "text" as const, text: JSON.stringify(value) },
|
|
569
|
-
...inputContent(inputs),
|
|
570
|
-
],
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
async function finishResult<
|
|
575
|
-
T extends { content: ReturnType<typeof toolResult>["content"] },
|
|
576
|
-
>(broker: Broker, context: RequestContext, result: T) {
|
|
577
|
-
context.mcpReq.signal.throwIfAborted();
|
|
578
|
-
const chatId = requestChatId(context);
|
|
579
|
-
if (!broker.deliveryAllowed(context.mcpReq.signal))
|
|
580
|
-
return formatResult(result);
|
|
581
|
-
const deliveries = chatId ? broker.deliveries(chatId) : [];
|
|
582
|
-
const answers = chatId ? broker.answers(chatId) : [];
|
|
583
|
-
const content = [
|
|
584
|
-
...result.content,
|
|
585
|
-
...deliveryContent(deliveries),
|
|
586
|
-
...answerContent(answers),
|
|
587
|
-
];
|
|
588
|
-
await broker.acknowledge(deliveries, answers, context.mcpReq.signal);
|
|
589
|
-
return formatResult({ ...result, content });
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function formatResult<
|
|
593
|
-
T extends { content: ReturnType<typeof toolResult>["content"] },
|
|
594
|
-
>(result: T) {
|
|
595
|
-
return {
|
|
596
|
-
...result,
|
|
597
|
-
structuredContent: {
|
|
598
|
-
text: result.content
|
|
599
|
-
.flatMap((block) => (block.type === "text" ? [block.text] : []))
|
|
600
|
-
.join("\n"),
|
|
601
|
-
},
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function requestChatId(context: RequestContext): string | undefined {
|
|
606
|
-
const value = context.mcpReq._meta?.["openai/session"];
|
|
607
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function requireChatId(context: RequestContext): string {
|
|
611
|
-
const chatId = requestChatId(context);
|
|
612
|
-
if (!chatId)
|
|
613
|
-
throw new Error("ChatGPT did not provide openai/session metadata");
|
|
614
|
-
return chatId;
|
|
615
|
-
}
|