poe-code 3.0.231 → 3.0.233

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.
@@ -5,5 +5,5 @@ export type { TypedSchema } from "./schema.js";
5
5
  export { Image, Audio, File, toContentBlocks, fileTypeFromBuffer, } from "./content/index.js";
6
6
  export type { ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, } from "./content/index.js";
7
7
  export type { ToolReturn } from "./content/index.js";
8
- export type { ServerOptions, ToolHandler, ToolDefinition, Tool, CallToolResult, HandleResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult, } from "./types.js";
8
+ export type { ServerOptions, ToolHandler, ToolDefinition, Tool, ToolAnnotations, ToolExecution, Icon, ContentAnnotations, ResourceLink, CallToolResult, PromptContentItem, PromptArgument, Prompt, PromptMessage, GetPromptResult, PromptHandler, PromptDefinition, Resource, ResourceTemplate, ResourceContents, ReadResourceResult, ResourceHandler, ResourceDefinition, ResourceTemplateDefinition, HandleResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult, } from "./types.js";
9
9
  export { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
@@ -1,11 +1,21 @@
1
- import type { ServerOptions, ToolHandler, HandleResult, Transport, SDKTransport, JSONRPCNotification } from "./types.js";
1
+ import type { ServerOptions, ToolDefinition, ToolHandler, HandleResult, Prompt, PromptHandler, Resource, ResourceHandler, ResourceTemplate, Transport, SDKTransport, JSONRPCNotification } from "./types.js";
2
2
  import type { TypedSchema } from "./schema.js";
3
3
  export interface Server {
4
4
  tool<T>(name: string, description: string, inputSchema: TypedSchema<T>, handler: ToolHandler<T>): Server;
5
+ registerTool<T>(definition: Omit<ToolDefinition<T>, "handler">, handler: ToolHandler<T>): Server;
6
+ prompt(definition: Prompt, handler: PromptHandler): Server;
7
+ resource(definition: Resource, handler: ResourceHandler): Server;
8
+ resourceTemplate(definition: ResourceTemplate, handler: ResourceHandler): Server;
5
9
  onNotification(listener: (notification: JSONRPCNotification) => void): () => void;
6
10
  removeTool(name: string): boolean;
11
+ removePrompt(name: string): boolean;
12
+ removeResource(uri: string): boolean;
13
+ removeResourceTemplate(uriTemplate: string): boolean;
7
14
  notifyToolsChanged(): Promise<void>;
8
- createMessageSession(): MessageSession;
15
+ notifyPromptsChanged(): Promise<void>;
16
+ notifyResourcesChanged(): Promise<void>;
17
+ notifyResourceUpdated(uri: string): Promise<void>;
18
+ createMessageSession(listener?: (notification: JSONRPCNotification) => void | Promise<void>): MessageSession;
9
19
  handleMessage(method: string, params?: Record<string, unknown>): Promise<HandleResult>;
10
20
  listen(): Promise<void>;
11
21
  connect(transport: Transport): Promise<void>;
@@ -1,4 +1,7 @@
1
1
  import * as readline from "readline";
2
+ import AjvModule from "ajv";
3
+ import uriTemplateParser from "uri-template";
4
+ import UriTemplate from "uri-template-lite";
2
5
  import { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
3
6
  import { parseMessage, formatSuccessResponse, formatErrorResponse, } from "./jsonrpc.js";
4
7
  import { toContentBlocks } from "./content/convert.js";
@@ -9,12 +12,21 @@ const SUPPORTED_PROTOCOL_VERSIONS = new Set([
9
12
  PROTOCOL_VERSION,
10
13
  ]);
11
14
  export function createServer(options) {
15
+ const Ajv = "default" in AjvModule ? AjvModule.default : AjvModule;
16
+ const jsonSchemaValidator = new Ajv({ strict: false });
17
+ const supportNotifications = options.supportNotifications !== false;
18
+ const supportResourceSubscriptions = options.supportResourceSubscriptions !== false;
12
19
  const tools = new Map();
20
+ const prompts = new Map();
21
+ const resources = new Map();
22
+ const resourceTemplates = new Map();
13
23
  const notificationListeners = new Set();
14
24
  const connectionNotificationListeners = new Map();
15
25
  const defaultLifecycle = {
16
26
  initialized: false,
17
27
  initializeAccepted: false,
28
+ notificationReady: false,
29
+ resourceSubscriptions: new Set(),
18
30
  };
19
31
  const messageLifecycles = new Set([defaultLifecycle]);
20
32
  const handleMessageWithLifecycle = async (method, lifecycle, params) => {
@@ -30,6 +42,7 @@ export function createServer(options) {
30
42
  // still enforced by the separate lifecycle object given to each connection.
31
43
  lifecycle.initializeAccepted = true;
32
44
  lifecycle.initialized = true;
45
+ lifecycle.notificationReady = false;
33
46
  const requestedProtocol = typeof params?.protocolVersion === "string"
34
47
  ? params.protocolVersion
35
48
  : undefined;
@@ -39,7 +52,14 @@ export function createServer(options) {
39
52
  : PROTOCOL_VERSION,
40
53
  capabilities: {
41
54
  tools: {
42
- listChanged: true,
55
+ ...(supportNotifications ? { listChanged: true } : {}),
56
+ },
57
+ prompts: {
58
+ ...(supportNotifications ? { listChanged: true } : {}),
59
+ },
60
+ resources: {
61
+ ...(supportNotifications ? { listChanged: true } : {}),
62
+ ...(supportResourceSubscriptions ? { subscribe: true } : {}),
43
63
  },
44
64
  },
45
65
  serverInfo: {
@@ -58,6 +78,7 @@ export function createServer(options) {
58
78
  },
59
79
  };
60
80
  }
81
+ lifecycle.notificationReady = true;
61
82
  return { result: undefined };
62
83
  }
63
84
  // All other methods require initialization
@@ -72,10 +93,10 @@ export function createServer(options) {
72
93
  if (method === "tools/list") {
73
94
  const toolList = [];
74
95
  for (const tool of tools.values()) {
96
+ const descriptor = { ...tool };
97
+ delete descriptor.handler;
75
98
  toolList.push({
76
- name: tool.name,
77
- description: tool.description,
78
- inputSchema: tool.inputSchema,
99
+ ...descriptor,
79
100
  });
80
101
  }
81
102
  return { result: { tools: toolList } };
@@ -100,7 +121,7 @@ export function createServer(options) {
100
121
  };
101
122
  }
102
123
  const toolArgs = (params?.arguments ?? {});
103
- if (options.validateToolArguments !== false && !areValidToolArguments(tool.inputSchema, toolArgs)) {
124
+ if (options.validateToolArguments !== false && !jsonSchemaValidator.validate(tool.inputSchema, toolArgs)) {
104
125
  return {
105
126
  error: {
106
127
  code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
@@ -116,6 +137,11 @@ export function createServer(options) {
116
137
  const result = isCallToolResult(handlerResult)
117
138
  ? handlerResult
118
139
  : { content: toContentBlocks(handlerResult) };
140
+ if (tool.outputSchema !== undefined
141
+ && (result.structuredContent === undefined
142
+ || !jsonSchemaValidator.validate(tool.outputSchema, result.structuredContent))) {
143
+ throw new Error("Invalid structured tool result");
144
+ }
119
145
  return { result };
120
146
  }
121
147
  catch (err) {
@@ -135,6 +161,96 @@ export function createServer(options) {
135
161
  return { result };
136
162
  }
137
163
  }
164
+ if (method === "prompts/list") {
165
+ return {
166
+ result: {
167
+ prompts: [...prompts.values()].map(({ handler: _handler, ...prompt }) => prompt),
168
+ },
169
+ };
170
+ }
171
+ if (method === "prompts/get") {
172
+ const promptName = typeof params?.name === "string" ? params.name : undefined;
173
+ if (promptName === undefined) {
174
+ return invalidParams("Prompt name required");
175
+ }
176
+ const prompt = prompts.get(promptName);
177
+ if (prompt === undefined) {
178
+ return invalidParams(`Prompt not found: ${promptName}`);
179
+ }
180
+ const args = toStringArguments(params?.arguments);
181
+ if (args === undefined || !hasRequiredPromptArguments(prompt, args)) {
182
+ return invalidParams("Invalid prompt arguments");
183
+ }
184
+ try {
185
+ const result = await prompt.handler(args);
186
+ if (!isGetPromptResult(result)) {
187
+ return internalError("Invalid prompt result");
188
+ }
189
+ return { result };
190
+ }
191
+ catch (error) {
192
+ return internalError(toErrorMessage(error));
193
+ }
194
+ }
195
+ if (method === "resources/list") {
196
+ return {
197
+ result: {
198
+ resources: [...resources.values()].map(({ handler: _handler, ...resource }) => resource),
199
+ },
200
+ };
201
+ }
202
+ if (method === "resources/templates/list") {
203
+ return {
204
+ result: {
205
+ resourceTemplates: [...resourceTemplates.values()].map(({ handler: _handler, ...resourceTemplate }) => resourceTemplate),
206
+ },
207
+ };
208
+ }
209
+ if (method === "resources/read") {
210
+ const uri = typeof params?.uri === "string" ? params.uri : undefined;
211
+ if (uri === undefined || !isValidUri(uri)) {
212
+ return invalidParams("Resource URI required");
213
+ }
214
+ const resource = findReadableResource(uri, resources, resourceTemplates);
215
+ if (resource === undefined) {
216
+ return resourceNotFound(uri);
217
+ }
218
+ try {
219
+ const result = await resource.handler(uri);
220
+ if (!isReadResourceResult(result)) {
221
+ return internalError("Invalid resource result");
222
+ }
223
+ return { result };
224
+ }
225
+ catch (error) {
226
+ return internalError(toErrorMessage(error));
227
+ }
228
+ }
229
+ if (method === "resources/subscribe" || method === "resources/unsubscribe") {
230
+ if (!supportResourceSubscriptions) {
231
+ return {
232
+ error: {
233
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
234
+ message: "Method not found",
235
+ },
236
+ };
237
+ }
238
+ const uri = typeof params?.uri === "string" ? params.uri : undefined;
239
+ if (uri === undefined || !isValidUri(uri)) {
240
+ return invalidParams("Resource URI required");
241
+ }
242
+ if (method === "resources/subscribe"
243
+ && findReadableResource(uri, resources, resourceTemplates) === undefined) {
244
+ return resourceNotFound(uri);
245
+ }
246
+ if (method === "resources/subscribe") {
247
+ lifecycle.resourceSubscriptions.add(uri);
248
+ }
249
+ else {
250
+ lifecycle.resourceSubscriptions.delete(uri);
251
+ }
252
+ return { result: {} };
253
+ }
138
254
  return {
139
255
  error: {
140
256
  code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
@@ -142,15 +258,23 @@ export function createServer(options) {
142
258
  },
143
259
  };
144
260
  };
145
- const createMessageSession = () => {
261
+ const createMessageSession = (listener) => {
146
262
  const lifecycle = {
147
263
  initialized: false,
148
264
  initializeAccepted: false,
265
+ notificationReady: false,
266
+ resourceSubscriptions: new Set(),
149
267
  };
150
268
  messageLifecycles.add(lifecycle);
269
+ if (listener !== undefined) {
270
+ connectionNotificationListeners.set(listener, lifecycle);
271
+ }
151
272
  return {
152
273
  handleMessage: (method, params) => handleMessageWithLifecycle(method, lifecycle, params),
153
274
  close: () => {
275
+ if (listener !== undefined) {
276
+ connectionNotificationListeners.delete(listener);
277
+ }
154
278
  messageLifecycles.delete(lifecycle);
155
279
  },
156
280
  };
@@ -186,16 +310,17 @@ export function createServer(options) {
186
310
  write(formatSuccessResponse(requestWithId.id, result) + "\n");
187
311
  }
188
312
  };
189
- const broadcastNotification = async (method) => {
313
+ const broadcastNotification = async (method, params, canSend = () => true) => {
190
314
  const notification = {
191
315
  jsonrpc: "2.0",
192
316
  method,
317
+ ...(params === undefined ? {} : { params }),
193
318
  };
194
319
  for (const listener of notificationListeners) {
195
320
  listener(notification);
196
321
  }
197
322
  await Promise.all([...connectionNotificationListeners].map(async ([listener, lifecycle]) => {
198
- if (lifecycle.initialized) {
323
+ if (lifecycle.notificationReady && canSend(lifecycle)) {
199
324
  await listener(notification);
200
325
  }
201
326
  }));
@@ -210,6 +335,30 @@ export function createServer(options) {
210
335
  });
211
336
  return server;
212
337
  },
338
+ registerTool(definition, handler) {
339
+ tools.set(definition.name, {
340
+ ...definition,
341
+ handler: handler,
342
+ });
343
+ return server;
344
+ },
345
+ prompt(definition, handler) {
346
+ prompts.set(definition.name, { ...definition, handler });
347
+ return server;
348
+ },
349
+ resource(definition, handler) {
350
+ if (!isValidUri(definition.uri)) {
351
+ throw new Error(`Invalid resource URI: ${definition.uri}`);
352
+ }
353
+ resources.set(definition.uri, { ...definition, handler });
354
+ return server;
355
+ },
356
+ resourceTemplate(definition, handler) {
357
+ uriTemplateParser.parse(definition.uriTemplate);
358
+ new UriTemplate(definition.uriTemplate);
359
+ resourceTemplates.set(definition.uriTemplate, { ...definition, handler });
360
+ return server;
361
+ },
213
362
  onNotification(listener) {
214
363
  notificationListeners.add(listener);
215
364
  return () => {
@@ -219,11 +368,36 @@ export function createServer(options) {
219
368
  removeTool(name) {
220
369
  return tools.delete(name);
221
370
  },
371
+ removePrompt(name) {
372
+ return prompts.delete(name);
373
+ },
374
+ removeResource(uri) {
375
+ return resources.delete(uri);
376
+ },
377
+ removeResourceTemplate(uriTemplate) {
378
+ return resourceTemplates.delete(uriTemplate);
379
+ },
222
380
  async notifyToolsChanged() {
223
- if ([...messageLifecycles].some((lifecycle) => lifecycle.initialized)) {
381
+ if (supportNotifications && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
224
382
  await broadcastNotification("notifications/tools/list_changed");
225
383
  }
226
384
  },
385
+ async notifyPromptsChanged() {
386
+ if (supportNotifications && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
387
+ await broadcastNotification("notifications/prompts/list_changed");
388
+ }
389
+ },
390
+ async notifyResourcesChanged() {
391
+ if (supportNotifications && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
392
+ await broadcastNotification("notifications/resources/list_changed");
393
+ }
394
+ },
395
+ async notifyResourceUpdated(uri) {
396
+ if (!supportResourceSubscriptions) {
397
+ return;
398
+ }
399
+ await broadcastNotification("notifications/resources/updated", { uri }, (lifecycle) => lifecycle.resourceSubscriptions.has(uri));
400
+ },
227
401
  createMessageSession,
228
402
  handleMessage,
229
403
  async listen() {
@@ -234,7 +408,7 @@ export function createServer(options) {
234
408
  },
235
409
  async connect(transport) {
236
410
  return new Promise((resolve) => {
237
- const lifecycle = { initialized: false, initializeAccepted: false };
411
+ const lifecycle = { initialized: false, initializeAccepted: false, notificationReady: false, resourceSubscriptions: new Set() };
238
412
  const messageHandler = (method, params) => handleMessageWithLifecycle(method, lifecycle, params);
239
413
  messageLifecycles.add(lifecycle);
240
414
  const listener = (notification) => {
@@ -263,7 +437,7 @@ export function createServer(options) {
263
437
  },
264
438
  async connectSDK(transport) {
265
439
  return new Promise((resolve, reject) => {
266
- const lifecycle = { initialized: false, initializeAccepted: false };
440
+ const lifecycle = { initialized: false, initializeAccepted: false, notificationReady: false, resourceSubscriptions: new Set() };
267
441
  const messageHandler = (method, params) => handleMessageWithLifecycle(method, lifecycle, params);
268
442
  messageLifecycles.add(lifecycle);
269
443
  const listener = (notification) => transport.send(notification);
@@ -326,39 +500,107 @@ export function createServer(options) {
326
500
  };
327
501
  return server;
328
502
  }
329
- function isCallToolResult(value) {
330
- return hasContentArray(value) && value.content.every(isContentItem);
503
+ function invalidParams(message) {
504
+ return {
505
+ error: {
506
+ code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
507
+ message,
508
+ },
509
+ };
331
510
  }
332
- function hasContentArray(value) {
333
- return typeof value === "object" && value !== null && "content" in value
334
- && Array.isArray(value.content);
511
+ function internalError(message) {
512
+ return {
513
+ error: {
514
+ code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
515
+ message,
516
+ },
517
+ };
335
518
  }
336
- function areValidToolArguments(schema, value) {
337
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
519
+ function resourceNotFound(uri) {
520
+ return {
521
+ error: {
522
+ code: JSON_RPC_ERROR_CODES.RESOURCE_NOT_FOUND,
523
+ message: `Resource not found: ${uri}`,
524
+ },
525
+ };
526
+ }
527
+ function toErrorMessage(error) {
528
+ return error instanceof Error ? error.message : String(error);
529
+ }
530
+ function isValidUri(uri) {
531
+ try {
532
+ new URL(uri);
533
+ return true;
534
+ }
535
+ catch {
338
536
  return false;
339
537
  }
340
- const argumentsObject = value;
341
- for (const key of schema.required ?? []) {
342
- if (!Object.hasOwn(argumentsObject, key)) {
343
- return false;
344
- }
538
+ }
539
+ function toStringArguments(value) {
540
+ if (value === undefined) {
541
+ return {};
345
542
  }
346
- for (const [key, property] of Object.entries(schema.properties)) {
347
- if (!Object.hasOwn(argumentsObject, key)) {
348
- continue;
349
- }
350
- const argument = argumentsObject[key];
351
- if (argument === null && property.nullable === true) {
352
- continue;
353
- }
354
- if ((property.type === "array" && !Array.isArray(argument))
355
- || (property.type === "object" && (typeof argument !== "object" || argument === null || Array.isArray(argument)))
356
- || (property.type === "integer" && (!Number.isInteger(argument)))
357
- || (property.type !== "array" && property.type !== "object" && property.type !== "integer" && typeof argument !== property.type)) {
358
- return false;
543
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
544
+ return undefined;
545
+ }
546
+ const args = {};
547
+ for (const [name, argument] of Object.entries(value)) {
548
+ if (typeof argument !== "string") {
549
+ return undefined;
359
550
  }
551
+ args[name] = argument;
552
+ }
553
+ return args;
554
+ }
555
+ function hasRequiredPromptArguments(prompt, args) {
556
+ return (prompt.arguments ?? []).every((argument) => argument.required !== true || args[argument.name] !== undefined);
557
+ }
558
+ function findReadableResource(uri, resources, resourceTemplates) {
559
+ const resource = resources.get(uri);
560
+ if (resource !== undefined) {
561
+ return resource;
360
562
  }
361
- return true;
563
+ return [...resourceTemplates.values()].find((template) => matchesUriTemplate(template.uriTemplate, uri));
564
+ }
565
+ function matchesUriTemplate(template, uri) {
566
+ try {
567
+ return new UriTemplate(template).match(uri) !== null;
568
+ }
569
+ catch {
570
+ return false;
571
+ }
572
+ }
573
+ function isCallToolResult(value) {
574
+ return hasContentArray(value) && value.content.every(isContentItem);
575
+ }
576
+ function isGetPromptResult(value) {
577
+ if (typeof value !== "object" || value === null || !("messages" in value)) {
578
+ return false;
579
+ }
580
+ return Array.isArray(value.messages)
581
+ && value.messages.every((message) => typeof message === "object"
582
+ && message !== null
583
+ && "role" in message
584
+ && (message.role === "user" || message.role === "assistant")
585
+ && "content" in message
586
+ && isPromptContentItem(message.content));
587
+ }
588
+ function isReadResourceResult(value) {
589
+ if (typeof value !== "object" || value === null || !("contents" in value)) {
590
+ return false;
591
+ }
592
+ return Array.isArray(value.contents)
593
+ && value.contents.every((content) => typeof content === "object"
594
+ && content !== null
595
+ && "uri" in content
596
+ && typeof content.uri === "string"
597
+ && isValidUri(content.uri)
598
+ && (("text" in content && typeof content.text === "string")
599
+ || ("blob" in content && typeof content.blob === "string" && isBase64(content.blob))));
600
+ }
601
+ function hasContentArray(value) {
602
+ return typeof value === "object" && value !== null && "content" in value
603
+ && Array.isArray(value.content);
362
604
  }
363
605
  function isContentItem(value) {
364
606
  if (typeof value !== "object" || value === null || !("type" in value)) {
@@ -369,13 +611,41 @@ function isContentItem(value) {
369
611
  return typeof block.text === "string";
370
612
  }
371
613
  if (block.type === "image" || block.type === "audio") {
372
- return typeof block.data === "string" && typeof block.mimeType === "string";
614
+ return typeof block.data === "string" && isBase64(block.data) && typeof block.mimeType === "string";
615
+ }
616
+ if (block.type === "resource_link") {
617
+ return typeof block.uri === "string" && typeof block.name === "string";
373
618
  }
374
619
  if (block.type !== "resource" || typeof block.resource !== "object" || block.resource === null) {
375
620
  return false;
376
621
  }
377
622
  const resource = block.resource;
378
623
  return typeof resource.uri === "string"
379
- && typeof resource.mimeType === "string"
380
- && (typeof resource.text === "string" || typeof resource.blob === "string");
624
+ && (resource.mimeType === undefined || typeof resource.mimeType === "string")
625
+ && (typeof resource.text === "string" || (typeof resource.blob === "string" && isBase64(resource.blob)));
626
+ }
627
+ function isBase64(value) {
628
+ if (value.length === 0) {
629
+ return true;
630
+ }
631
+ if (value.length % 4 !== 0) {
632
+ return false;
633
+ }
634
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
635
+ const paddingStart = value.indexOf("=");
636
+ const encoded = paddingStart === -1 ? value : value.slice(0, paddingStart);
637
+ const padding = paddingStart === -1 ? "" : value.slice(paddingStart);
638
+ if (padding.length > 2 || [...padding].some((character) => character !== "=")) {
639
+ return false;
640
+ }
641
+ if ([...encoded].some((character) => !alphabet.includes(character))) {
642
+ return false;
643
+ }
644
+ return Buffer.from(value, "base64").toString("base64") === value;
645
+ }
646
+ function isPromptContentItem(value) {
647
+ if (!isContentItem(value)) {
648
+ return false;
649
+ }
650
+ return !(typeof value === "object" && value !== null && "type" in value && value.type === "resource_link");
381
651
  }