chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
118
third_party/pi/packages/mcp/src/protocol/content.ts
vendored
Normal file
118
third_party/pi/packages/mcp/src/protocol/content.ts
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
export interface ContentAnnotations {
|
||||
audience?: ("user" | "assistant")[];
|
||||
priority?: number;
|
||||
lastModified?: string;
|
||||
}
|
||||
|
||||
export interface TextContent {
|
||||
type: "text";
|
||||
text: string;
|
||||
annotations?: ContentAnnotations;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ImageContent {
|
||||
type: "image";
|
||||
data: string;
|
||||
mimeType: string;
|
||||
annotations?: ContentAnnotations;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface AudioContent {
|
||||
type: "audio";
|
||||
data: string;
|
||||
mimeType: string;
|
||||
annotations?: ContentAnnotations;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ResourceLinkContent {
|
||||
type: "resource_link";
|
||||
uri: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
mimeType?: string;
|
||||
size?: number;
|
||||
annotations?: ContentAnnotations;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface TextResourceContents {
|
||||
uri: string;
|
||||
mimeType?: string;
|
||||
text: string;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface BlobResourceContents {
|
||||
uri: string;
|
||||
mimeType?: string;
|
||||
blob: string;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface EmbeddedResourceContent {
|
||||
type: "resource";
|
||||
resource: TextResourceContents | BlobResourceContents;
|
||||
annotations?: ContentAnnotations;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLinkContent | EmbeddedResourceContent;
|
||||
|
||||
export interface CallToolResult {
|
||||
content: ContentBlock[];
|
||||
/** Any JSON value, including arrays and `null` (MCP 2026-07-28; earlier revisions allowed only objects). */
|
||||
structuredContent?: unknown;
|
||||
isError?: boolean;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool result content in the shape LLM APIs accept: text and base64 images. Matches the
|
||||
* `TextContent` and `ImageContent` types of `@earendil-works/pi-ai`.
|
||||
*/
|
||||
export type LlmContent = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
|
||||
|
||||
function blockToLlmContent(block: ContentBlock): LlmContent {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return { type: "text", text: block.text };
|
||||
case "image":
|
||||
return { type: "image", data: block.data, mimeType: block.mimeType };
|
||||
case "audio":
|
||||
return { type: "text", text: `[audio ${block.mimeType} omitted]` };
|
||||
case "resource_link":
|
||||
return { type: "text", text: `${block.name}: ${block.uri}` };
|
||||
case "resource": {
|
||||
const resource = block.resource;
|
||||
if ("text" in resource) return { type: "text", text: resource.text };
|
||||
if (resource.mimeType?.startsWith("image/")) {
|
||||
return { type: "image", data: resource.blob, mimeType: resource.mimeType };
|
||||
}
|
||||
return {
|
||||
type: "text",
|
||||
text: `[binary resource ${resource.uri} (${resource.mimeType ?? "unknown type"}) omitted]`,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return { type: "text", text: `[unsupported MCP content ${(block as { type: string }).type}]` };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tool result to text and image content for a model. Text and images pass through,
|
||||
* embedded text resources become text, embedded image resources become images, and other blocks
|
||||
* (audio, resource links, binary resources) become a short text placeholder. A result without
|
||||
* content blocks but with `structuredContent` becomes its JSON, since servers should, but do not
|
||||
* always, mirror structured results as text.
|
||||
*/
|
||||
export function toLlmContent(result: Pick<CallToolResult, "content" | "structuredContent">): LlmContent[] {
|
||||
const content = (result.content ?? []).map(blockToLlmContent);
|
||||
if (content.length === 0 && result.structuredContent !== undefined) {
|
||||
content.push({ type: "text", text: JSON.stringify(result.structuredContent, null, 2) });
|
||||
}
|
||||
return content;
|
||||
}
|
||||
113
third_party/pi/packages/mcp/src/protocol/jsonrpc.ts
vendored
Normal file
113
third_party/pi/packages/mcp/src/protocol/jsonrpc.ts
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
export type JsonRpcId = string | number;
|
||||
|
||||
export interface JsonRpcRequest {
|
||||
jsonrpc: "2.0";
|
||||
id: JsonRpcId;
|
||||
method: string;
|
||||
params?: unknown;
|
||||
}
|
||||
|
||||
export interface JsonRpcNotification {
|
||||
jsonrpc: "2.0";
|
||||
method: string;
|
||||
params?: unknown;
|
||||
}
|
||||
|
||||
export interface JsonRpcErrorObject {
|
||||
code: number;
|
||||
message: string;
|
||||
data?: unknown;
|
||||
}
|
||||
|
||||
export interface JsonRpcSuccessResponse {
|
||||
jsonrpc: "2.0";
|
||||
id: JsonRpcId;
|
||||
result: unknown;
|
||||
}
|
||||
|
||||
export interface JsonRpcErrorResponse {
|
||||
jsonrpc: "2.0";
|
||||
id: JsonRpcId;
|
||||
error: JsonRpcErrorObject;
|
||||
}
|
||||
|
||||
export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
||||
export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse;
|
||||
|
||||
export const JSON_RPC_ERROR_CODES = {
|
||||
parseError: -32700,
|
||||
invalidRequest: -32600,
|
||||
methodNotFound: -32601,
|
||||
invalidParams: -32602,
|
||||
internalError: -32603,
|
||||
} as const;
|
||||
|
||||
export class McpError extends Error {
|
||||
readonly code: number;
|
||||
readonly data: unknown;
|
||||
|
||||
constructor(code: number, message: string, data?: unknown) {
|
||||
super(message);
|
||||
this.name = "McpError";
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export class McpConnectionClosedError extends Error {
|
||||
constructor(message = "MCP connection closed") {
|
||||
super(message);
|
||||
this.name = "McpConnectionClosedError";
|
||||
}
|
||||
}
|
||||
|
||||
export class McpTimeoutError extends Error {
|
||||
readonly timeoutMs: number;
|
||||
|
||||
constructor(timeoutMs: number) {
|
||||
super(`MCP request timed out after ${timeoutMs}ms`);
|
||||
this.name = "McpTimeoutError";
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
}
|
||||
|
||||
export class McpAbortError extends Error {
|
||||
constructor(message = "MCP request aborted") {
|
||||
super(message);
|
||||
this.name = "AbortError";
|
||||
}
|
||||
}
|
||||
|
||||
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export function toError(value: unknown): Error {
|
||||
return value instanceof Error ? value : new Error(String(value));
|
||||
}
|
||||
|
||||
export function isJsonRpcId(value: unknown): value is JsonRpcId {
|
||||
return typeof value === "string" || (typeof value === "number" && Number.isFinite(value));
|
||||
}
|
||||
|
||||
export function isJsonRpcRequest(message: unknown): message is JsonRpcRequest {
|
||||
return (
|
||||
isObject(message) && message.jsonrpc === "2.0" && isJsonRpcId(message.id) && typeof message.method === "string"
|
||||
);
|
||||
}
|
||||
|
||||
export function isJsonRpcNotification(message: unknown): message is JsonRpcNotification {
|
||||
return isObject(message) && message.jsonrpc === "2.0" && !("id" in message) && typeof message.method === "string";
|
||||
}
|
||||
|
||||
export function isJsonRpcResponse(message: unknown): message is JsonRpcResponse {
|
||||
if (!isObject(message) || message.jsonrpc !== "2.0" || !isJsonRpcId(message.id)) return false;
|
||||
if ("result" in message) return !("error" in message);
|
||||
if (!("error" in message) || !isObject(message.error)) return false;
|
||||
return typeof message.error.code === "number" && typeof message.error.message === "string";
|
||||
}
|
||||
|
||||
export function parseJsonRpcMessage(value: unknown): JsonRpcMessage {
|
||||
if (isJsonRpcRequest(value) || isJsonRpcNotification(value) || isJsonRpcResponse(value)) return value;
|
||||
throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid JSON-RPC message");
|
||||
}
|
||||
131
third_party/pi/packages/mcp/src/protocol/types.ts
vendored
Normal file
131
third_party/pi/packages/mcp/src/protocol/types.ts
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
import type { BlobResourceContents, ContentAnnotations, TextResourceContents } from "./content.ts";
|
||||
import type { JsonRpcId } from "./jsonrpc.ts";
|
||||
|
||||
export const LATEST_PROTOCOL_VERSION = "2025-11-25";
|
||||
/**
|
||||
* Versions the client accepts from a server. Servers that do not support the requested version answer
|
||||
* with their own latest one, so older versions stay accepted for servers built on older SDKs.
|
||||
*/
|
||||
export const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05"] as const;
|
||||
export type SupportedProtocolVersion = (typeof SUPPORTED_PROTOCOL_VERSIONS)[number];
|
||||
|
||||
export interface Implementation {
|
||||
name: string;
|
||||
version: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface Root {
|
||||
uri: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface ClientCapabilities {
|
||||
experimental?: Record<string, unknown>;
|
||||
roots?: { listChanged?: boolean };
|
||||
sampling?: Record<string, unknown>;
|
||||
elicitation?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ServerCapabilities {
|
||||
experimental?: Record<string, unknown>;
|
||||
logging?: Record<string, unknown>;
|
||||
prompts?: { listChanged?: boolean };
|
||||
resources?: { subscribe?: boolean; listChanged?: boolean };
|
||||
tools?: { listChanged?: boolean };
|
||||
completions?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface InitializeParams {
|
||||
protocolVersion: string;
|
||||
capabilities: ClientCapabilities;
|
||||
clientInfo: Implementation;
|
||||
}
|
||||
|
||||
export interface InitializeResult {
|
||||
protocolVersion: string;
|
||||
capabilities: ServerCapabilities;
|
||||
serverInfo: Implementation;
|
||||
instructions?: string;
|
||||
}
|
||||
|
||||
export interface ProgressNotification {
|
||||
progressToken: string | number;
|
||||
progress: number;
|
||||
total?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface CancelledNotification {
|
||||
requestId: JsonRpcId;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface ToolAnnotations {
|
||||
title?: string;
|
||||
readOnlyHint?: boolean;
|
||||
destructiveHint?: boolean;
|
||||
idempotentHint?: boolean;
|
||||
openWorldHint?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolExecution {
|
||||
taskSupport?: "forbidden" | "optional" | "required";
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
name: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
inputSchema: Record<string, unknown>;
|
||||
outputSchema?: Record<string, unknown>;
|
||||
annotations?: ToolAnnotations;
|
||||
execution?: ToolExecution;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ListToolsResult {
|
||||
tools: Tool[];
|
||||
nextCursor?: string;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** A resource a server lists in `resources/list`. */
|
||||
export interface Resource {
|
||||
uri: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
mimeType?: string;
|
||||
size?: number;
|
||||
annotations?: ContentAnnotations;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** A family of resources, addressed by an RFC 6570 URI template, from `resources/templates/list`. */
|
||||
export interface ResourceTemplate {
|
||||
uriTemplate: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
mimeType?: string;
|
||||
annotations?: ContentAnnotations;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ListResourcesResult {
|
||||
resources: Resource[];
|
||||
nextCursor?: string;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ListResourceTemplatesResult {
|
||||
resourceTemplates: ResourceTemplate[];
|
||||
nextCursor?: string;
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ReadResourceResult {
|
||||
contents: (TextResourceContents | BlobResourceContents)[];
|
||||
_meta?: Record<string, unknown>;
|
||||
}
|
||||
Reference in New Issue
Block a user