@pasko70/pibo 1.16.6 → 2.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/dist/agent-runtime/auth.js +28 -0
- package/dist/agent-runtime/capabilities.js +221 -0
- package/dist/agent-runtime/context-build.js +313 -0
- package/dist/agent-runtime/contract.js +45 -0
- package/dist/agent-runtime/errors.js +57 -0
- package/dist/agent-runtime/events.js +1 -0
- package/dist/agent-runtime/history.js +1 -0
- package/dist/agent-runtime/profile-validation.js +109 -0
- package/dist/agent-runtime/registry.js +608 -0
- package/dist/agent-runtime/resource-files.js +87 -0
- package/dist/agent-runtime/resource-service.js +635 -0
- package/dist/agent-runtime/resources.js +1 -0
- package/dist/agent-runtime/routed-session.js +931 -0
- package/dist/agent-runtime/testing/contract.js +51 -0
- package/dist/agent-runtime/testing/fake-adapter.js +192 -0
- package/dist/agent-runtime/types.js +3 -0
- package/dist/agent-runtimes/codex-native/adapter.js +905 -0
- package/dist/agent-runtimes/codex-native/auth.js +476 -0
- package/dist/agent-runtimes/codex-native/client.js +734 -0
- package/dist/agent-runtimes/codex-native/config.js +152 -0
- package/dist/agent-runtimes/codex-native/history.js +283 -0
- package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
- package/dist/agent-runtimes/codex-native/models.js +581 -0
- package/dist/agent-runtimes/codex-native/process.js +459 -0
- package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
- package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
- package/dist/agent-runtimes/codex-native/redaction.js +31 -0
- package/dist/agent-runtimes/codex-native/requests.js +378 -0
- package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
- package/dist/agent-runtimes/codex-native/thread.js +388 -0
- package/dist/agent-runtimes/codex-native/turn.js +849 -0
- package/dist/agent-runtimes/pi/adapter.js +653 -0
- package/dist/agent-runtimes/pi/auth.js +455 -0
- package/dist/agent-runtimes/pi/history.js +640 -0
- package/dist/agent-runtimes/pi/model-catalog.js +87 -0
- package/dist/agent-runtimes/pi/routed-session.js +1464 -0
- package/dist/agent-runtimes/pi/runtime.js +534 -0
- package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
- package/dist/apps/chat/agent-profiles.js +40 -23
- package/dist/apps/chat/agent-store.js +111 -64
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-request-normalizers.js +47 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -9
- package/dist/apps/chat/data/history-query-service.js +108 -0
- package/dist/apps/chat/data/session-query-service.js +24 -3
- package/dist/apps/chat/data/timeline-query-service.js +3 -3
- package/dist/apps/chat/model-catalog.js +1 -55
- package/dist/apps/chat/provider-auth-actions.js +171 -23
- package/dist/apps/chat/stream.js +12 -0
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/trace.js +126 -415
- package/dist/apps/chat/web-app.js +582 -157
- package/dist/apps/chat-ui/assets/{dist-BTLtT_Vf.js → dist-9v2ZXT6V.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CMk1aPug.js → dist-B1LVAytl.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CcwA_IWN.js → dist-C6LPDFXU.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DKOthbxr.js → dist-DVtXxfHc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ls2I6BIw.js → dist-DW4Qrfbd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D1rAtRWs.js → dist-DmbsHdMC.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C1JQRskW.js → dist-DyvwuSFO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DfpaylQ-.js → dist-I26VB0QO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D10bTFD5.js → dist-RtHbJRPV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-5KpmmBBZ.js → dist-sZgJkWZb.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
- package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
- package/dist/auth/login-actions.js +5 -251
- package/dist/cli-session/localSessionSource.js +8 -1
- package/dist/core/context-build.js +124 -20
- package/dist/core/default-profile.js +8 -0
- package/dist/core/profiles.js +30 -3
- package/dist/core/routed-session.js +5 -1446
- package/dist/core/runtime.js +5 -561
- package/dist/core/session-router.js +637 -83
- package/dist/data/cli.js +20 -3
- package/dist/data/schema.js +104 -1
- package/dist/data/session-store.js +24 -1
- package/dist/debug/events.js +25 -10
- package/dist/debug/failures.js +23 -3
- package/dist/debug/index.js +24 -6
- package/dist/debug/messages.js +87 -7
- package/dist/debug/persisted-payloads.js +31 -0
- package/dist/debug/pty.js +7 -6
- package/dist/debug/runtime-binding.js +29 -0
- package/dist/debug/session.js +137 -0
- package/dist/debug/summary.js +12 -0
- package/dist/debug/telemetry.js +28 -0
- package/dist/debug/tools.js +12 -2
- package/dist/debug/trace.js +154 -76
- package/dist/gateway/server.js +87 -2
- package/dist/gateway/tool.js +5 -5
- package/dist/index.js +21 -2
- package/dist/loops/tools.js +13 -12
- package/dist/mcp/agent-context.js +13 -4
- package/dist/mcp/client.js +10 -6
- package/dist/mcp/config.js +5 -2
- package/dist/mcp/runtime-session.js +166 -0
- package/dist/plugins/builtin.js +191 -53
- package/dist/plugins/codex-native.js +29 -0
- package/dist/plugins/registry.js +66 -2
- package/dist/runs/tools.js +26 -25
- package/dist/session-ui/terminalRows.js +2 -2
- package/dist/sessions/pibo-data-store.js +125 -16
- package/dist/sessions/runtime-binding.js +107 -0
- package/dist/sessions/sqlite-store.js +239 -18
- package/dist/sessions/store.js +86 -7
- package/dist/shared/trace-engine.js +18 -9
- package/dist/shared/trace-event-projection.js +29 -11
- package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
- package/dist/shared/trace-order.js +9 -4
- package/dist/shared/trace-page-merge.js +1 -1
- package/dist/signals/projector.js +20 -2
- package/dist/signals/registry.js +11 -0
- package/dist/subagents/tool.js +8 -6
- package/dist/tools/codex-browser.js +20 -19
- package/dist/tools/codex-compat.js +10 -9
- package/dist/tools/codex-image-generation.js +8 -6
- package/dist/tools/contract.js +45 -0
- package/dist/tools/credential-registry.js +182 -0
- package/dist/tools/mcp-bridge.js +511 -0
- package/dist/tools/payload-writer.js +17 -0
- package/dist/tools/runtime/tool.js +10 -9
- package/dist/tools/schema.js +9 -0
- package/dist/tools/session-service.js +193 -0
- package/dist/tools/session-tool-set.js +85 -0
- package/dist/web-annotations/tools.js +17 -16
- package/docs/README.md +11 -0
- package/package.json +3 -2
- package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
- package/dist/apps/chat-ui/assets/index-C2YI0xfw.js +0 -237
- package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { Server as McpProtocolServer } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
6
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, isInitializeRequest, } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { Value } from "typebox/value";
|
|
8
|
+
import { PiboToolCredentialError, PiboToolCredentialRegistry, } from "./credential-registry.js";
|
|
9
|
+
const DEFAULT_MAX_REQUEST_BYTES = 2 * 1024 * 1024;
|
|
10
|
+
const DEFAULT_LARGE_RESULT_THRESHOLD_BYTES = 128 * 1024;
|
|
11
|
+
const DEFAULT_PREVIEW_BYTES = 4 * 1024;
|
|
12
|
+
export class PiboToolMcpBridgeAuthorizationError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "PiboToolMcpBridgeAuthorizationError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function readBearerToken(request) {
|
|
21
|
+
const header = request.headers.authorization;
|
|
22
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
23
|
+
if (!value)
|
|
24
|
+
return undefined;
|
|
25
|
+
const match = /^Bearer\s+([^\s]+)$/i.exec(value.trim());
|
|
26
|
+
return match?.[1];
|
|
27
|
+
}
|
|
28
|
+
async function readJsonBody(request, maxBytes) {
|
|
29
|
+
const chunks = [];
|
|
30
|
+
let byteLength = 0;
|
|
31
|
+
for await (const chunk of request) {
|
|
32
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
33
|
+
byteLength += buffer.byteLength;
|
|
34
|
+
if (byteLength > maxBytes)
|
|
35
|
+
throw new Error(`MCP request exceeds ${maxBytes} bytes.`);
|
|
36
|
+
chunks.push(buffer);
|
|
37
|
+
}
|
|
38
|
+
if (chunks.length === 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
41
|
+
}
|
|
42
|
+
function sendJson(response, statusCode, body, headers = {}) {
|
|
43
|
+
if (response.headersSent || response.writableEnded)
|
|
44
|
+
return;
|
|
45
|
+
response.writeHead(statusCode, {
|
|
46
|
+
"content-type": "application/json; charset=utf-8",
|
|
47
|
+
"cache-control": "no-store",
|
|
48
|
+
...headers,
|
|
49
|
+
});
|
|
50
|
+
response.end(JSON.stringify(body));
|
|
51
|
+
}
|
|
52
|
+
function jsonRpcError(code, message) {
|
|
53
|
+
return {
|
|
54
|
+
jsonrpc: "2.0",
|
|
55
|
+
id: null,
|
|
56
|
+
error: { code, message },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function toJsonValue(value) {
|
|
60
|
+
if (value === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(JSON.stringify(value));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function toStructuredObject(value) {
|
|
70
|
+
if (value === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
73
|
+
return value;
|
|
74
|
+
return { value };
|
|
75
|
+
}
|
|
76
|
+
function textPreview(value, maxBytes) {
|
|
77
|
+
if (Buffer.byteLength(value, "utf8") <= maxBytes)
|
|
78
|
+
return value;
|
|
79
|
+
let preview = value.slice(0, maxBytes);
|
|
80
|
+
while (Buffer.byteLength(preview, "utf8") > maxBytes)
|
|
81
|
+
preview = preview.slice(0, -1);
|
|
82
|
+
return `${preview}\n…`;
|
|
83
|
+
}
|
|
84
|
+
function piboToolToMcpTool(tool) {
|
|
85
|
+
const inputSchema = tool.inputSchema;
|
|
86
|
+
return {
|
|
87
|
+
name: tool.name,
|
|
88
|
+
title: tool.title,
|
|
89
|
+
description: tool.description,
|
|
90
|
+
inputSchema,
|
|
91
|
+
...(tool.outputSchema ? { outputSchema: tool.outputSchema } : {}),
|
|
92
|
+
...(tool.annotations
|
|
93
|
+
? {
|
|
94
|
+
annotations: {
|
|
95
|
+
title: tool.title,
|
|
96
|
+
...(tool.annotations.readOnly !== undefined ? { readOnlyHint: tool.annotations.readOnly } : {}),
|
|
97
|
+
...(tool.annotations.destructive !== undefined ? { destructiveHint: tool.annotations.destructive } : {}),
|
|
98
|
+
...(tool.annotations.idempotent !== undefined ? { idempotentHint: tool.annotations.idempotent } : {}),
|
|
99
|
+
...(tool.annotations.openWorld !== undefined ? { openWorldHint: tool.annotations.openWorld } : {}),
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
: {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function storeLargeContent(options) {
|
|
106
|
+
return await options.writer.write({
|
|
107
|
+
piboSessionId: options.scope.piboSessionId,
|
|
108
|
+
toolName: options.tool.name,
|
|
109
|
+
toolCallId: options.toolCallId,
|
|
110
|
+
contentType: options.contentType,
|
|
111
|
+
value: options.value,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async function convertContent(items, options) {
|
|
115
|
+
const content = [];
|
|
116
|
+
const payloadRefs = [];
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
if (item.type === "text") {
|
|
119
|
+
const byteLength = Buffer.byteLength(item.text, "utf8");
|
|
120
|
+
if (options.writer && byteLength > options.threshold) {
|
|
121
|
+
const stored = await storeLargeContent({
|
|
122
|
+
writer: options.writer,
|
|
123
|
+
scope: options.scope,
|
|
124
|
+
tool: options.tool,
|
|
125
|
+
toolCallId: options.toolCallId,
|
|
126
|
+
contentType: "text/plain; charset=utf-8",
|
|
127
|
+
value: item.text,
|
|
128
|
+
});
|
|
129
|
+
payloadRefs.push(stored.ref);
|
|
130
|
+
content.push({
|
|
131
|
+
type: "text",
|
|
132
|
+
text: `${stored.preview ?? textPreview(item.text, options.previewBytes)}\n[Large result stored as ${stored.ref}; ${stored.byteLength} bytes]`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
content.push({ type: "text", text: item.text });
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (item.payloadRef && item.data === undefined) {
|
|
141
|
+
payloadRefs.push(item.payloadRef);
|
|
142
|
+
content.push({ type: "text", text: item.alt ?? `Image payload stored as ${item.payloadRef}` });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (item.data === undefined) {
|
|
146
|
+
content.push({ type: "text", text: item.alt ?? "Image result omitted." });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const bytes = Buffer.from(item.data, "base64");
|
|
150
|
+
if (options.writer && bytes.byteLength > options.threshold) {
|
|
151
|
+
const stored = await storeLargeContent({
|
|
152
|
+
writer: options.writer,
|
|
153
|
+
scope: options.scope,
|
|
154
|
+
tool: options.tool,
|
|
155
|
+
toolCallId: options.toolCallId,
|
|
156
|
+
contentType: item.mimeType,
|
|
157
|
+
value: bytes,
|
|
158
|
+
});
|
|
159
|
+
payloadRefs.push(stored.ref);
|
|
160
|
+
content.push({ type: "text", text: item.alt ?? `Image stored as ${stored.ref}; ${stored.byteLength} bytes` });
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
content.push({ type: "image", data: item.data, mimeType: item.mimeType });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { content, payloadRefs };
|
|
167
|
+
}
|
|
168
|
+
async function piboResultToMcp(result, options) {
|
|
169
|
+
const converted = await convertContent(result.content, options);
|
|
170
|
+
const payloadRefs = [...new Set([...(result.payloadRefs ?? []), ...converted.payloadRefs])];
|
|
171
|
+
let structuredContent = result.structuredContent ?? toJsonValue(result.details);
|
|
172
|
+
if (structuredContent !== undefined && options.writer) {
|
|
173
|
+
const encoded = JSON.stringify(structuredContent);
|
|
174
|
+
if (Buffer.byteLength(encoded, "utf8") > options.threshold) {
|
|
175
|
+
const stored = await storeLargeContent({
|
|
176
|
+
writer: options.writer,
|
|
177
|
+
scope: options.scope,
|
|
178
|
+
tool: options.tool,
|
|
179
|
+
toolCallId: options.toolCallId,
|
|
180
|
+
contentType: "application/json",
|
|
181
|
+
value: encoded,
|
|
182
|
+
});
|
|
183
|
+
payloadRefs.push(stored.ref);
|
|
184
|
+
structuredContent = undefined;
|
|
185
|
+
converted.content.push({
|
|
186
|
+
type: "text",
|
|
187
|
+
text: `[Structured result stored as ${stored.ref}; ${stored.byteLength} bytes]`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (converted.content.length === 0)
|
|
192
|
+
converted.content.push({ type: "text", text: result.isError ? "Tool failed." : "Tool completed." });
|
|
193
|
+
return {
|
|
194
|
+
content: converted.content,
|
|
195
|
+
...(toStructuredObject(structuredContent) ? { structuredContent: toStructuredObject(structuredContent) } : {}),
|
|
196
|
+
...(result.isError !== undefined ? { isError: result.isError } : {}),
|
|
197
|
+
_meta: {
|
|
198
|
+
piboSessionId: options.scope.piboSessionId,
|
|
199
|
+
runtimeInstanceId: options.scope.runtimeInstanceId,
|
|
200
|
+
adapterId: options.scope.adapterId,
|
|
201
|
+
sessionGeneration: options.scope.sessionGeneration,
|
|
202
|
+
toolCallId: options.toolCallId,
|
|
203
|
+
...(payloadRefs.length ? { payloadRefs } : {}),
|
|
204
|
+
...(result.metadata ? { metadata: result.metadata } : {}),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function progressMessage(update) {
|
|
209
|
+
if (update.message)
|
|
210
|
+
return update.message;
|
|
211
|
+
const text = update.content.find((item) => item.type === "text");
|
|
212
|
+
return text?.type === "text" ? text.text : undefined;
|
|
213
|
+
}
|
|
214
|
+
export class PiboToolMcpBridge {
|
|
215
|
+
credentials;
|
|
216
|
+
options;
|
|
217
|
+
requestScope = new AsyncLocalStorage();
|
|
218
|
+
mcpSessions = new Map();
|
|
219
|
+
closingSessions = new Set();
|
|
220
|
+
server;
|
|
221
|
+
address;
|
|
222
|
+
constructor(options) {
|
|
223
|
+
if (options.host !== undefined && options.host !== "127.0.0.1" && options.host !== "::1") {
|
|
224
|
+
throw new Error("Pibo tool MCP bridge must bind to a loopback address.");
|
|
225
|
+
}
|
|
226
|
+
this.credentials = options.credentialRegistry ?? new PiboToolCredentialRegistry();
|
|
227
|
+
this.options = {
|
|
228
|
+
...options,
|
|
229
|
+
host: options.host ?? "127.0.0.1",
|
|
230
|
+
port: options.port ?? 0,
|
|
231
|
+
maxRequestBytes: options.maxRequestBytes ?? DEFAULT_MAX_REQUEST_BYTES,
|
|
232
|
+
largeResultThresholdBytes: options.largeResultThresholdBytes ?? DEFAULT_LARGE_RESULT_THRESHOLD_BYTES,
|
|
233
|
+
previewBytes: options.previewBytes ?? DEFAULT_PREVIEW_BYTES,
|
|
234
|
+
serverName: options.serverName ?? "pibo-session-tools",
|
|
235
|
+
serverVersion: options.serverVersion ?? "1",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async start() {
|
|
239
|
+
if (this.address)
|
|
240
|
+
return { ...this.address };
|
|
241
|
+
const server = createServer((request, response) => {
|
|
242
|
+
void this.handleRequest(request, response).catch((error) => {
|
|
243
|
+
sendJson(response, 500, jsonRpcError(-32603, error instanceof Error ? error.message : String(error)));
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
await new Promise((resolve, reject) => {
|
|
247
|
+
server.once("error", reject);
|
|
248
|
+
server.listen(this.options.port, this.options.host, () => {
|
|
249
|
+
server.off("error", reject);
|
|
250
|
+
resolve();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
const address = server.address();
|
|
254
|
+
if (!address || typeof address === "string") {
|
|
255
|
+
server.close();
|
|
256
|
+
throw new Error("Pibo tool MCP bridge failed to resolve its loopback address.");
|
|
257
|
+
}
|
|
258
|
+
this.server = server;
|
|
259
|
+
this.address = {
|
|
260
|
+
host: this.options.host,
|
|
261
|
+
port: address.port,
|
|
262
|
+
url: `http://${this.options.host === "::1" ? "[::1]" : this.options.host}:${address.port}/mcp`,
|
|
263
|
+
};
|
|
264
|
+
return { ...this.address };
|
|
265
|
+
}
|
|
266
|
+
getAddress() {
|
|
267
|
+
return this.address ? { ...this.address } : undefined;
|
|
268
|
+
}
|
|
269
|
+
issueCredential(scope, ttlMs) {
|
|
270
|
+
return this.credentials.issue(scope, ttlMs);
|
|
271
|
+
}
|
|
272
|
+
closeCredentialSessions(credentialId) {
|
|
273
|
+
return this.closeMatchingSessions((session) => session.credentialId === credentialId);
|
|
274
|
+
}
|
|
275
|
+
closeSessionGeneration(piboSessionId, sessionGeneration) {
|
|
276
|
+
return this.closeMatchingSessions((session) => (session.piboSessionId === piboSessionId
|
|
277
|
+
&& session.sessionGeneration === sessionGeneration));
|
|
278
|
+
}
|
|
279
|
+
async stop() {
|
|
280
|
+
this.closeMatchingSessions(() => true);
|
|
281
|
+
await Promise.allSettled([...this.closingSessions]);
|
|
282
|
+
const server = this.server;
|
|
283
|
+
this.server = undefined;
|
|
284
|
+
this.address = undefined;
|
|
285
|
+
if (!server)
|
|
286
|
+
return;
|
|
287
|
+
await new Promise((resolve, reject) => {
|
|
288
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
289
|
+
server.closeAllConnections();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
closeMatchingSessions(predicate) {
|
|
293
|
+
let closed = 0;
|
|
294
|
+
for (const session of new Set(this.mcpSessions.values())) {
|
|
295
|
+
if (session.closing || !predicate(session))
|
|
296
|
+
continue;
|
|
297
|
+
session.closing = true;
|
|
298
|
+
if (session.sessionId)
|
|
299
|
+
this.mcpSessions.delete(session.sessionId);
|
|
300
|
+
const closing = Promise.allSettled([
|
|
301
|
+
session.transport.close(),
|
|
302
|
+
session.protocolServer.close(),
|
|
303
|
+
]).finally(() => this.closingSessions.delete(closing));
|
|
304
|
+
this.closingSessions.add(closing);
|
|
305
|
+
closed += 1;
|
|
306
|
+
}
|
|
307
|
+
return closed;
|
|
308
|
+
}
|
|
309
|
+
async authenticate(request) {
|
|
310
|
+
const token = readBearerToken(request);
|
|
311
|
+
if (!token)
|
|
312
|
+
throw new PiboToolCredentialError("credential_invalid", "Bearer credential is required.");
|
|
313
|
+
const scope = this.credentials.authenticate(token);
|
|
314
|
+
if (this.options.isSessionGenerationActive && !await this.options.isSessionGenerationActive(scope)) {
|
|
315
|
+
throw new PiboToolMcpBridgeAuthorizationError("session_generation_inactive", "The Pibo runtime session generation for this tool credential is no longer active.");
|
|
316
|
+
}
|
|
317
|
+
return scope;
|
|
318
|
+
}
|
|
319
|
+
async resolveAllowedTools(scope) {
|
|
320
|
+
const selected = new Set(scope.allowedToolNames);
|
|
321
|
+
const tools = await this.options.resolveTools(scope);
|
|
322
|
+
return new Map(tools
|
|
323
|
+
.filter((tool) => selected.has(tool.name) && tool.portable !== false)
|
|
324
|
+
.map((tool) => [tool.name, tool]));
|
|
325
|
+
}
|
|
326
|
+
currentRequestScope() {
|
|
327
|
+
const scope = this.requestScope.getStore();
|
|
328
|
+
if (!scope)
|
|
329
|
+
throw new PiboToolMcpBridgeAuthorizationError("session_generation_inactive", "MCP request has no active Pibo credential scope.");
|
|
330
|
+
return scope;
|
|
331
|
+
}
|
|
332
|
+
configureProtocolServer(protocolServer) {
|
|
333
|
+
protocolServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
334
|
+
const scope = this.currentRequestScope();
|
|
335
|
+
const tools = await this.resolveAllowedTools(scope);
|
|
336
|
+
return { tools: [...tools.values()].map(piboToolToMcpTool) };
|
|
337
|
+
});
|
|
338
|
+
protocolServer.setRequestHandler(CallToolRequestSchema, async (call, extra) => {
|
|
339
|
+
return await this.executeToolCall(call, extra);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
async executeToolCall(call, extra) {
|
|
343
|
+
const scope = this.currentRequestScope();
|
|
344
|
+
const tools = await this.resolveAllowedTools(scope);
|
|
345
|
+
const tool = tools.get(call.params.name);
|
|
346
|
+
if (!scope.allowedToolNames.includes(call.params.name)) {
|
|
347
|
+
return {
|
|
348
|
+
content: [{ type: "text", text: `Tool "${call.params.name}" is outside this session credential's selected tool set.` }],
|
|
349
|
+
isError: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (!tool) {
|
|
353
|
+
return {
|
|
354
|
+
content: [{ type: "text", text: `Tool "${call.params.name}" is unavailable or not portable for this runtime session.` }],
|
|
355
|
+
isError: true,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const input = call.params.arguments ?? {};
|
|
359
|
+
if (!Value.Check(tool.inputSchema, input)) {
|
|
360
|
+
const errors = [...Value.Errors(tool.inputSchema, input)].slice(0, 5).map((error) => {
|
|
361
|
+
const location = error.path
|
|
362
|
+
?? error.instancePath
|
|
363
|
+
?? "/";
|
|
364
|
+
return `${location || "/"}: ${error.message}`;
|
|
365
|
+
});
|
|
366
|
+
return {
|
|
367
|
+
content: [{ type: "text", text: `Invalid arguments for ${tool.name}: ${errors.join("; ")}` }],
|
|
368
|
+
isError: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const preparedInput = tool.prepareInput ? tool.prepareInput(input) : input;
|
|
372
|
+
const toolCallId = String(extra.requestId);
|
|
373
|
+
const executionDefaults = this.options.resolveExecutionContext
|
|
374
|
+
? await this.options.resolveExecutionContext(scope)
|
|
375
|
+
: {};
|
|
376
|
+
const pendingProgress = [];
|
|
377
|
+
let updateSequence = 0;
|
|
378
|
+
const onUpdate = extra._meta?.progressToken === undefined
|
|
379
|
+
? undefined
|
|
380
|
+
: (update) => {
|
|
381
|
+
updateSequence += 1;
|
|
382
|
+
pendingProgress.push(extra.sendNotification({
|
|
383
|
+
method: "notifications/progress",
|
|
384
|
+
params: {
|
|
385
|
+
progressToken: extra._meta.progressToken,
|
|
386
|
+
progress: update.progress ?? updateSequence,
|
|
387
|
+
...(update.total !== undefined ? { total: update.total } : {}),
|
|
388
|
+
...(progressMessage(update) ? { message: progressMessage(update) } : {}),
|
|
389
|
+
},
|
|
390
|
+
}).catch(() => { }));
|
|
391
|
+
};
|
|
392
|
+
try {
|
|
393
|
+
const result = await tool.execute(toolCallId, preparedInput, extra.signal, onUpdate, {
|
|
394
|
+
...executionDefaults,
|
|
395
|
+
piboSessionId: scope.piboSessionId,
|
|
396
|
+
piboRoomId: scope.piboRoomId,
|
|
397
|
+
profileName: scope.profileName,
|
|
398
|
+
cwd: scope.cwd,
|
|
399
|
+
runtimeInstanceId: scope.runtimeInstanceId,
|
|
400
|
+
adapterId: scope.adapterId,
|
|
401
|
+
sessionGeneration: scope.sessionGeneration,
|
|
402
|
+
});
|
|
403
|
+
await Promise.allSettled(pendingProgress);
|
|
404
|
+
if (tool.outputSchema && (result.structuredContent === undefined
|
|
405
|
+
|| !Value.Check(tool.outputSchema, result.structuredContent))) {
|
|
406
|
+
throw new Error(`Tool "${tool.name}" returned structuredContent that does not match its output schema.`);
|
|
407
|
+
}
|
|
408
|
+
return await piboResultToMcp(result, {
|
|
409
|
+
scope,
|
|
410
|
+
tool,
|
|
411
|
+
toolCallId,
|
|
412
|
+
writer: this.options.payloadWriter,
|
|
413
|
+
threshold: this.options.largeResultThresholdBytes,
|
|
414
|
+
previewBytes: this.options.previewBytes,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
await Promise.allSettled(pendingProgress);
|
|
419
|
+
return {
|
|
420
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
421
|
+
isError: true,
|
|
422
|
+
_meta: {
|
|
423
|
+
piboSessionId: scope.piboSessionId,
|
|
424
|
+
runtimeInstanceId: scope.runtimeInstanceId,
|
|
425
|
+
adapterId: scope.adapterId,
|
|
426
|
+
sessionGeneration: scope.sessionGeneration,
|
|
427
|
+
toolCallId,
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async createMcpSession(scope) {
|
|
433
|
+
const protocolServer = new McpProtocolServer({ name: this.options.serverName, version: this.options.serverVersion }, { capabilities: { tools: {} } });
|
|
434
|
+
let session;
|
|
435
|
+
const transport = new StreamableHTTPServerTransport({
|
|
436
|
+
sessionIdGenerator: randomUUID,
|
|
437
|
+
onsessioninitialized: (sessionId) => {
|
|
438
|
+
session.sessionId = sessionId;
|
|
439
|
+
this.mcpSessions.set(sessionId, session);
|
|
440
|
+
},
|
|
441
|
+
onsessionclosed: (sessionId) => {
|
|
442
|
+
this.mcpSessions.delete(sessionId);
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
session = {
|
|
446
|
+
credentialId: scope.credentialId,
|
|
447
|
+
piboSessionId: scope.piboSessionId,
|
|
448
|
+
sessionGeneration: scope.sessionGeneration,
|
|
449
|
+
protocolServer,
|
|
450
|
+
transport,
|
|
451
|
+
};
|
|
452
|
+
this.configureProtocolServer(protocolServer);
|
|
453
|
+
await protocolServer.connect(transport);
|
|
454
|
+
return session;
|
|
455
|
+
}
|
|
456
|
+
requestSessionId(request) {
|
|
457
|
+
const header = request.headers["mcp-session-id"];
|
|
458
|
+
return (Array.isArray(header) ? header[0] : header)?.trim() || undefined;
|
|
459
|
+
}
|
|
460
|
+
async handleRequest(request, response) {
|
|
461
|
+
if (request.url !== "/mcp") {
|
|
462
|
+
sendJson(response, 404, jsonRpcError(-32601, "Not found."));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (!request.method || !["POST", "GET", "DELETE"].includes(request.method)) {
|
|
466
|
+
sendJson(response, 405, jsonRpcError(-32600, "Unsupported MCP HTTP method."), { allow: "POST, GET, DELETE" });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
let scope;
|
|
470
|
+
try {
|
|
471
|
+
scope = await this.authenticate(request);
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
const status = error instanceof PiboToolMcpBridgeAuthorizationError ? 403 : 401;
|
|
475
|
+
sendJson(response, status, jsonRpcError(-32001, error instanceof Error ? error.message : "Unauthorized."), {
|
|
476
|
+
"www-authenticate": "Bearer",
|
|
477
|
+
});
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
let body;
|
|
481
|
+
if (request.method === "POST") {
|
|
482
|
+
try {
|
|
483
|
+
body = await readJsonBody(request, this.options.maxRequestBytes);
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
sendJson(response, 400, jsonRpcError(-32700, error instanceof Error ? error.message : "Invalid JSON."));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const sessionId = this.requestSessionId(request);
|
|
491
|
+
let session = sessionId ? this.mcpSessions.get(sessionId) : undefined;
|
|
492
|
+
if (sessionId && !session) {
|
|
493
|
+
sendJson(response, 404, jsonRpcError(-32001, "Unknown or closed MCP session."));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (session && session.credentialId !== scope.credentialId) {
|
|
497
|
+
sendJson(response, 403, jsonRpcError(-32001, "MCP session belongs to a different Pibo tool credential."));
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (!session) {
|
|
501
|
+
if (request.method !== "POST" || !isInitializeRequest(body)) {
|
|
502
|
+
sendJson(response, 400, jsonRpcError(-32600, "An initialize request is required before using the MCP session."));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
session = await this.createMcpSession(scope);
|
|
506
|
+
}
|
|
507
|
+
await this.requestScope.run(scope, async () => {
|
|
508
|
+
await session.transport.handleRequest(request, response, body);
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Adapt the shared durable PayloadStore for large portable tool results. */
|
|
2
|
+
export function createPiboToolPayloadWriter(store) {
|
|
3
|
+
return {
|
|
4
|
+
write(input) {
|
|
5
|
+
const payload = store.writePayload({
|
|
6
|
+
value: input.value,
|
|
7
|
+
contentType: input.contentType,
|
|
8
|
+
retentionClass: "tool_result",
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
ref: payload.id,
|
|
12
|
+
byteLength: payload.byteSize,
|
|
13
|
+
...(payload.previewText ? { preview: payload.previewText } : {}),
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { piboStringEnum } from "../schema.js";
|
|
3
|
+
import { definePiboTool } from "../contract.js";
|
|
3
4
|
export function createRuntimeToolProfile() {
|
|
4
5
|
return {
|
|
5
6
|
name: "runtime",
|
|
@@ -93,24 +94,24 @@ function formatRuntimeResult(result) {
|
|
|
93
94
|
return lines.join("\n");
|
|
94
95
|
}
|
|
95
96
|
export function createRuntimeToolDefinition(controller) {
|
|
96
|
-
return
|
|
97
|
+
return definePiboTool({
|
|
97
98
|
name: "runtime",
|
|
98
|
-
|
|
99
|
+
title: "Runtime",
|
|
99
100
|
description: "Run Python/Node code in persistent sessions. Use this instead of bash for Python/Node execution except trivial one-line shell checks.",
|
|
100
101
|
promptSnippet: "Use runtime for Python/Node execution, especially longer snippets (~20+ lines), uncertain code, or objects/state to inspect. Prefer runtime over bash for Python/Node except trivial one-line shell checks. Auto-starts on exec; print values you need. Runtime keeps variables and processes alive between calls; reuse sessionId to inspect or continue from that state after partial failures. This saves output tokens and keeps your mental model tied to live state. Use read for file contents; use bash for shell commands and package installs.",
|
|
101
102
|
executionMode: "parallel",
|
|
102
|
-
|
|
103
|
-
action:
|
|
104
|
-
runtime: Type.Optional(
|
|
103
|
+
inputSchema: Type.Object({
|
|
104
|
+
action: piboStringEnum(["exec", "inspect", "vars", "interrupt", "list"], { description: "Runtime action to perform." }),
|
|
105
|
+
runtime: Type.Optional(piboStringEnum(["python", "node"], { description: "Runtime kind; default python." })),
|
|
105
106
|
name: Type.Optional(Type.String({ description: "Optional human-readable runtime name." })),
|
|
106
107
|
target: Type.Optional(Type.Any({ description: "Auto-start target options such as { type: 'local', cwd, executable, args, env }." })),
|
|
107
108
|
sessionId: Type.Optional(Type.String({ description: "Existing runtime session id; omit to use auto runtime." })),
|
|
108
109
|
code: Type.Optional(Type.String({ description: "Code to execute for exec." })),
|
|
109
110
|
timeoutMs: Type.Optional(Type.Number({ description: "Action timeout in milliseconds." })),
|
|
110
|
-
mode: Type.Optional(
|
|
111
|
+
mode: Type.Optional(piboStringEnum(["exec", "eval", "auto"], { description: "For exec: execution mode. Use eval when you need an expression result." })),
|
|
111
112
|
closeOnSuccess: Type.Optional(Type.Boolean({ description: "For exec: close the runtime only if execution succeeds." })),
|
|
112
113
|
expression: Type.Optional(Type.String({ description: "Expression to inspect." })),
|
|
113
|
-
what: Type.Optional(
|
|
114
|
+
what: Type.Optional(piboStringEnum(["summary", "signature", "members", "source", "doc", "all"], { description: "Inspection detail to return." })),
|
|
114
115
|
maxBytes: Type.Optional(Type.Number({ description: "Maximum bytes for summaries or inspect fields." })),
|
|
115
116
|
includePrivate: Type.Optional(Type.Boolean({ description: "For vars: include private names." })),
|
|
116
117
|
maxItems: Type.Optional(Type.Number({ description: "For vars: maximum variables to return." })),
|