@pasko70/pibo 1.16.5 → 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.
Files changed (146) hide show
  1. package/dist/agent-runtime/auth.js +28 -0
  2. package/dist/agent-runtime/capabilities.js +221 -0
  3. package/dist/agent-runtime/context-build.js +313 -0
  4. package/dist/agent-runtime/contract.js +45 -0
  5. package/dist/agent-runtime/errors.js +57 -0
  6. package/dist/agent-runtime/events.js +1 -0
  7. package/dist/agent-runtime/history.js +1 -0
  8. package/dist/agent-runtime/profile-validation.js +109 -0
  9. package/dist/agent-runtime/registry.js +608 -0
  10. package/dist/agent-runtime/resource-files.js +87 -0
  11. package/dist/agent-runtime/resource-service.js +635 -0
  12. package/dist/agent-runtime/resources.js +1 -0
  13. package/dist/agent-runtime/routed-session.js +931 -0
  14. package/dist/agent-runtime/testing/contract.js +51 -0
  15. package/dist/agent-runtime/testing/fake-adapter.js +192 -0
  16. package/dist/agent-runtime/types.js +3 -0
  17. package/dist/agent-runtimes/codex-native/adapter.js +905 -0
  18. package/dist/agent-runtimes/codex-native/auth.js +476 -0
  19. package/dist/agent-runtimes/codex-native/client.js +734 -0
  20. package/dist/agent-runtimes/codex-native/config.js +152 -0
  21. package/dist/agent-runtimes/codex-native/history.js +283 -0
  22. package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
  23. package/dist/agent-runtimes/codex-native/models.js +581 -0
  24. package/dist/agent-runtimes/codex-native/process.js +459 -0
  25. package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
  26. package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
  27. package/dist/agent-runtimes/codex-native/redaction.js +31 -0
  28. package/dist/agent-runtimes/codex-native/requests.js +378 -0
  29. package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
  30. package/dist/agent-runtimes/codex-native/thread.js +388 -0
  31. package/dist/agent-runtimes/codex-native/turn.js +849 -0
  32. package/dist/agent-runtimes/pi/adapter.js +653 -0
  33. package/dist/agent-runtimes/pi/auth.js +455 -0
  34. package/dist/agent-runtimes/pi/history.js +640 -0
  35. package/dist/agent-runtimes/pi/model-catalog.js +87 -0
  36. package/dist/agent-runtimes/pi/routed-session.js +1464 -0
  37. package/dist/agent-runtimes/pi/runtime.js +534 -0
  38. package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
  39. package/dist/apps/chat/agent-profiles.js +40 -23
  40. package/dist/apps/chat/agent-store.js +111 -64
  41. package/dist/apps/chat/chat-api-routes.js +1 -1
  42. package/dist/apps/chat/chat-request-normalizers.js +47 -0
  43. package/dist/apps/chat/data/chat-data-mappers.js +25 -9
  44. package/dist/apps/chat/data/history-query-service.js +108 -0
  45. package/dist/apps/chat/data/session-query-service.js +24 -3
  46. package/dist/apps/chat/data/timeline-query-service.js +3 -3
  47. package/dist/apps/chat/model-catalog.js +1 -55
  48. package/dist/apps/chat/provider-auth-actions.js +171 -23
  49. package/dist/apps/chat/stream.js +12 -0
  50. package/dist/apps/chat/trace-v2.js +1 -0
  51. package/dist/apps/chat/trace.js +126 -415
  52. package/dist/apps/chat/web-app.js +582 -157
  53. package/dist/apps/chat-ui/assets/{dist-QXwINWYY.js → dist-9v2ZXT6V.js} +1 -1
  54. package/dist/apps/chat-ui/assets/{dist-CvZNkkOi.js → dist-B1LVAytl.js} +1 -1
  55. package/dist/apps/chat-ui/assets/{dist-_SP-MjLq.js → dist-C6LPDFXU.js} +1 -1
  56. package/dist/apps/chat-ui/assets/{dist-Bad4Yz4P.js → dist-DVtXxfHc.js} +1 -1
  57. package/dist/apps/chat-ui/assets/{dist-xy-zomwl.js → dist-DW4Qrfbd.js} +1 -1
  58. package/dist/apps/chat-ui/assets/{dist-DI4i80O2.js → dist-DmbsHdMC.js} +1 -1
  59. package/dist/apps/chat-ui/assets/{dist-D17yAUqp.js → dist-DyvwuSFO.js} +1 -1
  60. package/dist/apps/chat-ui/assets/{dist-Bc3Vbgjh.js → dist-I26VB0QO.js} +1 -1
  61. package/dist/apps/chat-ui/assets/{dist-BzqH7w58.js → dist-RtHbJRPV.js} +1 -1
  62. package/dist/apps/chat-ui/assets/{dist-CMDJ-WZm.js → dist-sZgJkWZb.js} +1 -1
  63. package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
  64. package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
  65. package/dist/apps/chat-ui/index.html +2 -2
  66. package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
  67. package/dist/apps/chat-vscode-web/index.html +1 -1
  68. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  69. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
  70. package/dist/auth/login-actions.js +5 -251
  71. package/dist/cli-session/localSessionSource.js +8 -1
  72. package/dist/core/context-build.js +124 -20
  73. package/dist/core/default-profile.js +8 -0
  74. package/dist/core/profiles.js +30 -3
  75. package/dist/core/routed-session.js +5 -1446
  76. package/dist/core/runtime.js +5 -561
  77. package/dist/core/session-router.js +637 -83
  78. package/dist/data/cli.js +20 -3
  79. package/dist/data/schema.js +104 -1
  80. package/dist/data/session-store.js +24 -1
  81. package/dist/debug/events.js +25 -10
  82. package/dist/debug/failures.js +23 -3
  83. package/dist/debug/index.js +24 -6
  84. package/dist/debug/messages.js +87 -7
  85. package/dist/debug/persisted-payloads.js +31 -0
  86. package/dist/debug/pty.js +7 -6
  87. package/dist/debug/runtime-binding.js +29 -0
  88. package/dist/debug/session.js +137 -0
  89. package/dist/debug/summary.js +12 -0
  90. package/dist/debug/telemetry.js +28 -0
  91. package/dist/debug/tools.js +12 -2
  92. package/dist/debug/trace.js +154 -76
  93. package/dist/gateway/server.js +87 -2
  94. package/dist/gateway/tool.js +5 -5
  95. package/dist/index.js +21 -2
  96. package/dist/loops/tools.js +13 -12
  97. package/dist/mcp/agent-context.js +13 -4
  98. package/dist/mcp/client.js +10 -6
  99. package/dist/mcp/config.js +5 -2
  100. package/dist/mcp/runtime-session.js +166 -0
  101. package/dist/plugins/builtin.js +191 -53
  102. package/dist/plugins/codex-native.js +29 -0
  103. package/dist/plugins/registry.js +66 -2
  104. package/dist/runs/tools.js +26 -25
  105. package/dist/session-ui/terminalRows.js +2 -2
  106. package/dist/sessions/pibo-data-store.js +125 -16
  107. package/dist/sessions/runtime-binding.js +107 -0
  108. package/dist/sessions/sqlite-store.js +239 -18
  109. package/dist/sessions/store.js +86 -7
  110. package/dist/shared/trace-engine.js +18 -9
  111. package/dist/shared/trace-event-projection.js +29 -11
  112. package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
  113. package/dist/shared/trace-order.js +9 -4
  114. package/dist/shared/trace-page-merge.js +1 -1
  115. package/dist/signals/projector.js +20 -2
  116. package/dist/signals/registry.js +11 -0
  117. package/dist/subagents/tool.js +8 -6
  118. package/dist/tools/codex-browser.js +20 -19
  119. package/dist/tools/codex-compat.js +10 -9
  120. package/dist/tools/codex-image-generation.js +8 -6
  121. package/dist/tools/contract.js +45 -0
  122. package/dist/tools/credential-registry.js +182 -0
  123. package/dist/tools/mcp-bridge.js +511 -0
  124. package/dist/tools/payload-writer.js +17 -0
  125. package/dist/tools/runtime/tool.js +10 -9
  126. package/dist/tools/schema.js +9 -0
  127. package/dist/tools/session-service.js +193 -0
  128. package/dist/tools/session-tool-set.js +85 -0
  129. package/dist/web-annotations/tools.js +17 -16
  130. package/docs/README.md +11 -0
  131. package/package.json +3 -2
  132. package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
  133. package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
  134. package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
  135. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
  136. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
  137. package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
  138. package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
  139. package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
  140. package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
  141. package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
  142. package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
  143. package/dist/apps/chat-ui/assets/index-C45B0rw7.js +0 -237
  144. package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
  145. package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
  146. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.16.5.vsix +0 -0
@@ -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 { StringEnum, Type } from "@earendil-works/pi-ai";
2
- import { defineTool } from "@earendil-works/pi-coding-agent";
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 defineTool({
97
+ return definePiboTool({
97
98
  name: "runtime",
98
- label: "Runtime",
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
- parameters: Type.Object({
103
- action: StringEnum(["exec", "inspect", "vars", "interrupt", "list"], { description: "Runtime action to perform." }),
104
- runtime: Type.Optional(StringEnum(["python", "node"], { description: "Runtime kind; default python." })),
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(StringEnum(["exec", "eval", "auto"], { description: "For exec: execution mode. Use eval when you need an expression result." })),
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(StringEnum(["summary", "signature", "members", "source", "doc", "all"], { description: "Inspection detail to return." })),
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." })),
@@ -0,0 +1,9 @@
1
+ import { Type } from "typebox";
2
+ /** JSON-Schema string enum helper that does not depend on a harness SDK. */
3
+ export function piboStringEnum(values, options = {}) {
4
+ return Type.Unsafe({
5
+ type: "string",
6
+ enum: [...values],
7
+ ...options,
8
+ });
9
+ }