@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,445 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const MAX_CONTEXT_CONTRIBUTIONS = 128;
|
|
5
|
+
const MAX_CONTEXT_CONTRIBUTION_BYTES = 256 * 1024;
|
|
6
|
+
const MAX_CONTEXT_BYTES = 1024 * 1024;
|
|
7
|
+
const MAX_MCP_CONFIG_BYTES = 2 * 1024 * 1024;
|
|
8
|
+
const MAX_MCP_SERVERS = 32;
|
|
9
|
+
const MAX_MCP_STATUS_PAGES = 20;
|
|
10
|
+
const MCP_STATUS_PAGE_SIZE = 100;
|
|
11
|
+
const PIBO_TOOL_TOKEN_TTL_MS = 5 * 60_000;
|
|
12
|
+
const PIBO_TOOL_TOKEN_RENEW_LEAD_MS = 60_000;
|
|
13
|
+
const PIBO_TOOL_TOKEN_ROLLOVER_MS = 25 * 60_000;
|
|
14
|
+
const PIBO_TOOL_TOKEN_TURN_ROLLOVER_MS = 5 * 60_000;
|
|
15
|
+
const ENV_REFERENCE_PATTERN = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
16
|
+
const ENABLED_MCP_CONTEXT_ID = "context:enabled-mcp-servers";
|
|
17
|
+
const MCP_STDIO_LAUNCHER_PATH = fileURLToPath(new URL("./mcp-stdio-launcher.js", import.meta.url));
|
|
18
|
+
const MAX_STDIO_COMMAND_BYTES = 16 * 1024;
|
|
19
|
+
const MAX_STDIO_ARGS_BYTES = 256 * 1024;
|
|
20
|
+
const MAX_STDIO_ENVIRONMENT_BYTES = 512 * 1024;
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function asJsonValue(value) {
|
|
25
|
+
return JSON.parse(JSON.stringify(value));
|
|
26
|
+
}
|
|
27
|
+
function safeServerName(value) {
|
|
28
|
+
if (!value.trim() || value.length > 128 || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
29
|
+
throw new Error("Selected MCP server name is not supported by native Codex delivery.");
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function uniquePiboServerName(externalNames) {
|
|
34
|
+
const base = "pibo-session-tools";
|
|
35
|
+
if (!externalNames.has(base))
|
|
36
|
+
return base;
|
|
37
|
+
return `${base}-${createHash("sha256").update([...externalNames].sort().join("\0")).digest("hex").slice(0, 8)}`;
|
|
38
|
+
}
|
|
39
|
+
function environmentReference(value) {
|
|
40
|
+
return ENV_REFERENCE_PATTERN.exec(value)?.[1];
|
|
41
|
+
}
|
|
42
|
+
function boundedString(value, label, maxLength = 4096) {
|
|
43
|
+
if (typeof value !== "string" || !value.trim())
|
|
44
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
45
|
+
if (value.length > maxLength)
|
|
46
|
+
throw new Error(`${label} exceeds ${maxLength} characters.`);
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function optionalStringArray(value, label, maxItems = 512, maxLength = 256) {
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (!Array.isArray(value) || value.length > maxItems)
|
|
53
|
+
throw new Error(`${label} must be a bounded string array.`);
|
|
54
|
+
return value.map((entry, index) => boundedString(entry, `${label}[${index}]`, maxLength));
|
|
55
|
+
}
|
|
56
|
+
function buildDeveloperInstructions(resources) {
|
|
57
|
+
if (!resources)
|
|
58
|
+
return undefined;
|
|
59
|
+
const contributions = resources.getContextContributions().filter((contribution) => contribution.id !== ENABLED_MCP_CONTEXT_ID && contribution.content?.trim());
|
|
60
|
+
if (contributions.length === 0)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (contributions.length > MAX_CONTEXT_CONTRIBUTIONS) {
|
|
63
|
+
throw new Error(`Native Codex context exceeds ${MAX_CONTEXT_CONTRIBUTIONS} contributions.`);
|
|
64
|
+
}
|
|
65
|
+
let totalBytes = 0;
|
|
66
|
+
const sections = contributions.map((contribution, index) => {
|
|
67
|
+
const content = contribution.content ?? "";
|
|
68
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
69
|
+
if (bytes > MAX_CONTEXT_CONTRIBUTION_BYTES) {
|
|
70
|
+
throw new Error(`Native Codex context contribution ${index + 1} exceeds ${MAX_CONTEXT_CONTRIBUTION_BYTES} bytes.`);
|
|
71
|
+
}
|
|
72
|
+
totalBytes += bytes;
|
|
73
|
+
if (totalBytes > MAX_CONTEXT_BYTES)
|
|
74
|
+
throw new Error(`Native Codex context exceeds ${MAX_CONTEXT_BYTES} bytes.`);
|
|
75
|
+
const label = contribution.label.replace(/[\r\n]+/g, " ").slice(0, 256);
|
|
76
|
+
return [`## ${label}`, "", content].join("\n");
|
|
77
|
+
});
|
|
78
|
+
return [
|
|
79
|
+
"# Pibo-Selected Context",
|
|
80
|
+
"",
|
|
81
|
+
"The following contributions are additive developer context selected by Pibo. They do not replace Codex native system instructions or native tools.",
|
|
82
|
+
"",
|
|
83
|
+
...sections,
|
|
84
|
+
].join("\n\n");
|
|
85
|
+
}
|
|
86
|
+
async function readMaterializedMcpConfig(resources) {
|
|
87
|
+
const configPath = resources?.getMcpConfigPath();
|
|
88
|
+
if (!configPath)
|
|
89
|
+
return {};
|
|
90
|
+
const bytes = await readFile(configPath);
|
|
91
|
+
if (bytes.byteLength > MAX_MCP_CONFIG_BYTES)
|
|
92
|
+
throw new Error(`Native Codex MCP configuration exceeds ${MAX_MCP_CONFIG_BYTES} bytes.`);
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new Error("Native Codex MCP configuration is invalid JSON.");
|
|
99
|
+
}
|
|
100
|
+
if (!isRecord(parsed))
|
|
101
|
+
throw new Error("Native Codex MCP configuration is invalid.");
|
|
102
|
+
const config = parsed;
|
|
103
|
+
if (config.mcpServers === undefined)
|
|
104
|
+
return {};
|
|
105
|
+
if (!isRecord(config.mcpServers))
|
|
106
|
+
throw new Error("Native Codex MCP server configuration is invalid.");
|
|
107
|
+
if (Object.keys(config.mcpServers).length > MAX_MCP_SERVERS) {
|
|
108
|
+
throw new Error(`Native Codex MCP configuration exceeds ${MAX_MCP_SERVERS} servers.`);
|
|
109
|
+
}
|
|
110
|
+
return config.mcpServers;
|
|
111
|
+
}
|
|
112
|
+
function toolSelectionFields(name, value, verifiedToolNames) {
|
|
113
|
+
const allowedTools = optionalStringArray(value.allowedTools, `MCP server ${name} allowedTools`);
|
|
114
|
+
const disabledTools = optionalStringArray(value.disabledTools, `MCP server ${name} disabledTools`);
|
|
115
|
+
if (verifiedToolNames.length > 512)
|
|
116
|
+
throw new Error(`Selected MCP server "${name}" exposes too many tools.`);
|
|
117
|
+
const enabledTools = [...new Set(verifiedToolNames.map((toolName, index) => boundedString(toolName, `MCP server ${name} tool ${index}`, 256)))];
|
|
118
|
+
return enabledTools.length > 0 || allowedTools !== undefined || disabledTools !== undefined
|
|
119
|
+
? { enabled_tools: enabledTools }
|
|
120
|
+
: {};
|
|
121
|
+
}
|
|
122
|
+
function convertHttpMcpServer(name, value, environment, verifiedToolNames) {
|
|
123
|
+
if (!isRecord(value) || typeof value.url !== "string") {
|
|
124
|
+
throw new Error(`Selected MCP server "${name}" requires Streamable HTTP for native Codex delivery.`);
|
|
125
|
+
}
|
|
126
|
+
if (value.url.includes("${")) {
|
|
127
|
+
throw new Error(`Selected MCP server "${name}" has a sensitive or environment-derived URL that native Codex cannot receive safely.`);
|
|
128
|
+
}
|
|
129
|
+
let parsedUrl;
|
|
130
|
+
try {
|
|
131
|
+
parsedUrl = new URL(value.url);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
throw new Error(`Selected MCP server "${name}" has an invalid URL.`);
|
|
135
|
+
}
|
|
136
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
137
|
+
throw new Error(`Selected MCP server "${name}" must use HTTP or HTTPS.`);
|
|
138
|
+
}
|
|
139
|
+
const httpHeaders = Object.create(null);
|
|
140
|
+
const envHttpHeaders = Object.create(null);
|
|
141
|
+
if (value.headers !== undefined) {
|
|
142
|
+
if (!isRecord(value.headers) || Object.keys(value.headers).length > 128) {
|
|
143
|
+
throw new Error(`Selected MCP server "${name}" has invalid headers.`);
|
|
144
|
+
}
|
|
145
|
+
for (const [header, rawValue] of Object.entries(value.headers)) {
|
|
146
|
+
const headerValue = boundedString(rawValue, `MCP header ${header}`, 8192);
|
|
147
|
+
const environmentName = environmentReference(headerValue);
|
|
148
|
+
if (environmentName) {
|
|
149
|
+
if (environment[environmentName] === undefined)
|
|
150
|
+
throw new Error(`Selected MCP server "${name}" is missing a scoped header value.`);
|
|
151
|
+
envHttpHeaders[header] = environmentName;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
httpHeaders[header] = headerValue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const toolSelection = toolSelectionFields(name, value, verifiedToolNames);
|
|
159
|
+
let toolTimeoutSeconds;
|
|
160
|
+
if (value.timeout !== undefined) {
|
|
161
|
+
if (typeof value.timeout !== "number" || !Number.isFinite(value.timeout) || value.timeout <= 0) {
|
|
162
|
+
throw new Error(`Selected MCP server "${name}" has an invalid timeout.`);
|
|
163
|
+
}
|
|
164
|
+
toolTimeoutSeconds = value.timeout / 1000;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
url: parsedUrl.toString(),
|
|
168
|
+
enabled: true,
|
|
169
|
+
required: true,
|
|
170
|
+
...(Object.keys(httpHeaders).length > 0 ? { http_headers: httpHeaders } : {}),
|
|
171
|
+
...(Object.keys(envHttpHeaders).length > 0 ? { env_http_headers: envHttpHeaders } : {}),
|
|
172
|
+
...toolSelection,
|
|
173
|
+
...(toolTimeoutSeconds !== undefined ? { tool_timeout_sec: toolTimeoutSeconds } : {}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function stdioEnvironmentName(name, suffix) {
|
|
177
|
+
const hash = createHash("sha256").update(name).digest("hex").slice(0, 16).toUpperCase();
|
|
178
|
+
return `PIBO_CODEX_MCP_STDIO_${hash}_${suffix}`;
|
|
179
|
+
}
|
|
180
|
+
function convertStdioMcpServer(name, materialized, resolved, environment, verifiedToolNames) {
|
|
181
|
+
if (!isRecord(materialized) || !isRecord(resolved) || typeof resolved.command !== "string") {
|
|
182
|
+
throw new Error(`Selected MCP server "${name}" has an invalid stdio configuration.`);
|
|
183
|
+
}
|
|
184
|
+
const command = boundedString(resolved.command, `MCP server ${name} command`, MAX_STDIO_COMMAND_BYTES);
|
|
185
|
+
if (command.includes("\u0000") || Buffer.byteLength(command, "utf8") > MAX_STDIO_COMMAND_BYTES) {
|
|
186
|
+
throw new Error(`Selected MCP server "${name}" has an invalid command.`);
|
|
187
|
+
}
|
|
188
|
+
const args = optionalStringArray(resolved.args, `MCP server ${name} args`, 512, 16 * 1024) ?? [];
|
|
189
|
+
if (args.some((value) => value.includes("\u0000")))
|
|
190
|
+
throw new Error(`Selected MCP server "${name}" has invalid arguments.`);
|
|
191
|
+
const argsJson = JSON.stringify(args);
|
|
192
|
+
if (Buffer.byteLength(argsJson, "utf8") > MAX_STDIO_ARGS_BYTES) {
|
|
193
|
+
throw new Error(`Selected MCP server "${name}" has oversized arguments.`);
|
|
194
|
+
}
|
|
195
|
+
const childEnvironment = Object.create(null);
|
|
196
|
+
if (resolved.env !== undefined) {
|
|
197
|
+
if (!isRecord(resolved.env) || Object.keys(resolved.env).length > 512) {
|
|
198
|
+
throw new Error(`Selected MCP server "${name}" has an invalid environment.`);
|
|
199
|
+
}
|
|
200
|
+
for (const [key, rawValue] of Object.entries(resolved.env)) {
|
|
201
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof rawValue !== "string" || rawValue.includes("\u0000")) {
|
|
202
|
+
throw new Error(`Selected MCP server "${name}" has an invalid environment entry.`);
|
|
203
|
+
}
|
|
204
|
+
childEnvironment[key] = rawValue;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const environmentJson = JSON.stringify(childEnvironment);
|
|
208
|
+
if (Buffer.byteLength(environmentJson, "utf8") > MAX_STDIO_ENVIRONMENT_BYTES) {
|
|
209
|
+
throw new Error(`Selected MCP server "${name}" has an oversized environment.`);
|
|
210
|
+
}
|
|
211
|
+
const cwd = resolved.cwd === undefined
|
|
212
|
+
? ""
|
|
213
|
+
: boundedString(resolved.cwd, `MCP server ${name} cwd`, MAX_STDIO_COMMAND_BYTES);
|
|
214
|
+
if (cwd.includes("\u0000") || Buffer.byteLength(cwd, "utf8") > MAX_STDIO_COMMAND_BYTES) {
|
|
215
|
+
throw new Error(`Selected MCP server "${name}" has an invalid working directory.`);
|
|
216
|
+
}
|
|
217
|
+
const commandEnvironment = stdioEnvironmentName(name, "COMMAND");
|
|
218
|
+
const argsEnvironment = stdioEnvironmentName(name, "ARGS");
|
|
219
|
+
const childEnvironmentName = stdioEnvironmentName(name, "ENV");
|
|
220
|
+
const cwdEnvironment = stdioEnvironmentName(name, "CWD");
|
|
221
|
+
environment[commandEnvironment] = command;
|
|
222
|
+
environment[argsEnvironment] = argsJson;
|
|
223
|
+
environment[childEnvironmentName] = environmentJson;
|
|
224
|
+
environment[cwdEnvironment] = cwd;
|
|
225
|
+
const environmentNames = [commandEnvironment, argsEnvironment, childEnvironmentName, cwdEnvironment];
|
|
226
|
+
return {
|
|
227
|
+
command: process.execPath,
|
|
228
|
+
args: [MCP_STDIO_LAUNCHER_PATH, ...environmentNames],
|
|
229
|
+
env_vars: environmentNames,
|
|
230
|
+
enabled: true,
|
|
231
|
+
required: true,
|
|
232
|
+
...toolSelectionFields(name, materialized, verifiedToolNames),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function validateSkillsListResponse(value) {
|
|
236
|
+
if (!isRecord(value) || !Array.isArray(value.data) || value.data.length > 128) {
|
|
237
|
+
throw new Error("Codex skills/list returned an invalid response.");
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
function validateMcpStatusResponse(value) {
|
|
242
|
+
if (!isRecord(value)
|
|
243
|
+
|| !Array.isArray(value.data)
|
|
244
|
+
|| value.data.length > MAX_MCP_SERVERS
|
|
245
|
+
|| (value.nextCursor !== undefined && value.nextCursor !== null && typeof value.nextCursor !== "string")) {
|
|
246
|
+
throw new Error("Codex mcpServerStatus/list returned an invalid response.");
|
|
247
|
+
}
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
export class CodexNativeResourceDelivery {
|
|
251
|
+
environment;
|
|
252
|
+
threadConfig;
|
|
253
|
+
developerInstructions;
|
|
254
|
+
skillRoots;
|
|
255
|
+
skillPaths;
|
|
256
|
+
expectedMcpServers;
|
|
257
|
+
portableTools;
|
|
258
|
+
mcpAccess;
|
|
259
|
+
credentialIssuedAtMs;
|
|
260
|
+
credentialExpiresAtMs;
|
|
261
|
+
constructor(environment, threadConfig, developerInstructions, skillRoots, skillPaths, expectedMcpServers, portableTools, mcpAccess, credentialIssuedAtMs, credentialExpiresAtMs) {
|
|
262
|
+
this.environment = environment;
|
|
263
|
+
this.threadConfig = threadConfig;
|
|
264
|
+
this.developerInstructions = developerInstructions;
|
|
265
|
+
this.skillRoots = skillRoots;
|
|
266
|
+
this.skillPaths = skillPaths;
|
|
267
|
+
this.expectedMcpServers = expectedMcpServers;
|
|
268
|
+
this.portableTools = portableTools;
|
|
269
|
+
this.mcpAccess = mcpAccess;
|
|
270
|
+
this.credentialIssuedAtMs = credentialIssuedAtMs;
|
|
271
|
+
this.credentialExpiresAtMs = credentialExpiresAtMs;
|
|
272
|
+
}
|
|
273
|
+
static async prepare(input) {
|
|
274
|
+
const resourceEnvironment = { ...(input.resources?.getAdapterEnvironment() ?? {}) };
|
|
275
|
+
const materializedMcpServers = await readMaterializedMcpConfig(input.resources);
|
|
276
|
+
const externalNames = new Set(Object.keys(materializedMcpServers).map(safeServerName));
|
|
277
|
+
const resolvedMcpServers = input.resources?.getExternalMcpServerConfigs() ?? {};
|
|
278
|
+
const codexMcpServers = Object.create(null);
|
|
279
|
+
const expectedMcpServers = [];
|
|
280
|
+
const inspectionByName = new Map((input.resources?.getInspection().mcpServers ?? []).map((server) => [server.name, server]));
|
|
281
|
+
for (const [name, config] of Object.entries(materializedMcpServers)) {
|
|
282
|
+
const safeName = safeServerName(name);
|
|
283
|
+
const toolNames = inspectionByName.get(name)?.tools.map((tool) => tool.name) ?? [];
|
|
284
|
+
codexMcpServers[safeName] = isRecord(config) && typeof config.url === "string"
|
|
285
|
+
? convertHttpMcpServer(safeName, config, resourceEnvironment, toolNames)
|
|
286
|
+
: convertStdioMcpServer(safeName, config, resolvedMcpServers[name], resourceEnvironment, toolNames);
|
|
287
|
+
expectedMcpServers.push({ name: safeName, toolNames });
|
|
288
|
+
}
|
|
289
|
+
let mcpAccess;
|
|
290
|
+
let credentialIssuedAtMs;
|
|
291
|
+
let credentialExpiresAtMs;
|
|
292
|
+
try {
|
|
293
|
+
const portableDefinitions = input.portableTools?.createDefinitions().filter((tool) => tool.portable !== false) ?? [];
|
|
294
|
+
if (portableDefinitions.length > 0 && input.portableTools) {
|
|
295
|
+
const allowedToolNames = [...new Set(portableDefinitions.map((tool) => tool.name))];
|
|
296
|
+
credentialIssuedAtMs = Date.now();
|
|
297
|
+
mcpAccess = await input.portableTools.issueMcpAccess({
|
|
298
|
+
allowedToolNames,
|
|
299
|
+
ttlMs: PIBO_TOOL_TOKEN_TTL_MS,
|
|
300
|
+
});
|
|
301
|
+
credentialExpiresAtMs = Date.parse(mcpAccess.expiresAt);
|
|
302
|
+
if (!Number.isFinite(credentialExpiresAtMs) || credentialExpiresAtMs <= credentialIssuedAtMs) {
|
|
303
|
+
throw new Error("Pibo tool MCP returned an invalid credential expiry.");
|
|
304
|
+
}
|
|
305
|
+
const environmentName = `PIBO_CODEX_MCP_TOKEN_${mcpAccess.credentialId.toUpperCase().replace(/[^A-Z0-9_]/g, "_")}`;
|
|
306
|
+
resourceEnvironment[environmentName] = mcpAccess.token;
|
|
307
|
+
const serverName = uniquePiboServerName(externalNames);
|
|
308
|
+
codexMcpServers[serverName] = {
|
|
309
|
+
url: mcpAccess.url,
|
|
310
|
+
bearer_token_env_var: environmentName,
|
|
311
|
+
enabled: true,
|
|
312
|
+
required: true,
|
|
313
|
+
enabled_tools: [...mcpAccess.allowedToolNames],
|
|
314
|
+
// These are explicitly selected, session-scoped Pibo tools. Codex's native
|
|
315
|
+
// prompt path otherwise rejects model-initiated MCP calls when no MCP
|
|
316
|
+
// approval UI is active for the turn.
|
|
317
|
+
default_tools_approval_mode: "approve",
|
|
318
|
+
};
|
|
319
|
+
expectedMcpServers.push({ name: serverName, toolNames: [...mcpAccess.allowedToolNames] });
|
|
320
|
+
}
|
|
321
|
+
const skillPaths = input.resources
|
|
322
|
+
? await Promise.all(input.resources.getSkillPaths("materialized").map(async (path) => await realpath(path)))
|
|
323
|
+
: [];
|
|
324
|
+
const materializedSkillsRoot = input.resources?.getInspection().paths?.skills;
|
|
325
|
+
const skillRoots = skillPaths.length > 0 && materializedSkillsRoot
|
|
326
|
+
? [await realpath(materializedSkillsRoot)]
|
|
327
|
+
: [];
|
|
328
|
+
const threadConfig = Object.keys(codexMcpServers).length > 0
|
|
329
|
+
? { mcp_servers: asJsonValue(codexMcpServers) }
|
|
330
|
+
: undefined;
|
|
331
|
+
return new CodexNativeResourceDelivery(resourceEnvironment, threadConfig, buildDeveloperInstructions(input.resources), skillRoots, skillPaths, expectedMcpServers, input.portableTools, mcpAccess, credentialIssuedAtMs, credentialExpiresAtMs);
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
if (mcpAccess && input.portableTools)
|
|
335
|
+
input.portableTools.revokeMcpAccess(mcpAccess.token);
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
get hasMcpServers() {
|
|
340
|
+
return this.expectedMcpServers.length > 0;
|
|
341
|
+
}
|
|
342
|
+
get enabledToolNames() {
|
|
343
|
+
return this.expectedMcpServers
|
|
344
|
+
.flatMap((server) => server.toolNames.map((toolName) => `${server.name}/${toolName}`))
|
|
345
|
+
.sort();
|
|
346
|
+
}
|
|
347
|
+
get nextCredentialMaintenanceAt() {
|
|
348
|
+
if (!this.mcpAccess || this.credentialIssuedAtMs === undefined || this.credentialExpiresAtMs === undefined)
|
|
349
|
+
return undefined;
|
|
350
|
+
return Math.min(this.credentialExpiresAtMs - PIBO_TOOL_TOKEN_RENEW_LEAD_MS, this.credentialIssuedAtMs + PIBO_TOOL_TOKEN_ROLLOVER_MS);
|
|
351
|
+
}
|
|
352
|
+
needsCredentialRolloverForTurn(now = Date.now()) {
|
|
353
|
+
return this.credentialIssuedAtMs !== undefined
|
|
354
|
+
&& now >= this.credentialIssuedAtMs + PIBO_TOOL_TOKEN_TURN_ROLLOVER_MS;
|
|
355
|
+
}
|
|
356
|
+
shouldRolloverCredential(now = Date.now()) {
|
|
357
|
+
return this.credentialIssuedAtMs !== undefined
|
|
358
|
+
&& now >= this.credentialIssuedAtMs + PIBO_TOOL_TOKEN_ROLLOVER_MS;
|
|
359
|
+
}
|
|
360
|
+
renewCredential() {
|
|
361
|
+
if (!this.mcpAccess || !this.portableTools)
|
|
362
|
+
return;
|
|
363
|
+
const renewed = this.portableTools.renewMcpAccess(this.mcpAccess.token, PIBO_TOOL_TOKEN_TTL_MS);
|
|
364
|
+
const expiresAt = Date.parse(renewed.expiresAt);
|
|
365
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now()) {
|
|
366
|
+
throw new Error("Pibo tool MCP credential renewal returned an invalid expiry.");
|
|
367
|
+
}
|
|
368
|
+
this.credentialExpiresAtMs = expiresAt;
|
|
369
|
+
}
|
|
370
|
+
async configureProcess(client, workspace) {
|
|
371
|
+
if (this.skillRoots.length === 0)
|
|
372
|
+
return;
|
|
373
|
+
let response;
|
|
374
|
+
try {
|
|
375
|
+
await client.request("skills/extraRoots/set", { extraRoots: [...this.skillRoots] });
|
|
376
|
+
response = validateSkillsListResponse(await client.request("skills/list", {
|
|
377
|
+
cwds: [workspace],
|
|
378
|
+
forceReload: true,
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
throw new Error("Codex could not load the selected Pibo skills.");
|
|
383
|
+
}
|
|
384
|
+
const loadedPaths = new Set();
|
|
385
|
+
for (const entry of response.data) {
|
|
386
|
+
if (!isRecord(entry) || !Array.isArray(entry.skills) || !Array.isArray(entry.errors)) {
|
|
387
|
+
throw new Error("Codex skills/list returned an invalid entry.");
|
|
388
|
+
}
|
|
389
|
+
for (const skill of entry.skills) {
|
|
390
|
+
if (!isRecord(skill) || typeof skill.path !== "string" || typeof skill.enabled !== "boolean") {
|
|
391
|
+
throw new Error("Codex skills/list returned an invalid skill.");
|
|
392
|
+
}
|
|
393
|
+
if (skill.enabled)
|
|
394
|
+
loadedPaths.add(await realpath(skill.path).catch(() => skill.path));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (this.skillPaths.some((path) => !loadedPaths.has(path))) {
|
|
398
|
+
throw new Error("Codex did not load every selected Pibo skill.");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async verifyThread(client, threadId) {
|
|
402
|
+
if (this.expectedMcpServers.length === 0)
|
|
403
|
+
return;
|
|
404
|
+
const statuses = [];
|
|
405
|
+
const cursors = new Set();
|
|
406
|
+
let cursor;
|
|
407
|
+
for (let page = 0; page < MAX_MCP_STATUS_PAGES; page++) {
|
|
408
|
+
let response;
|
|
409
|
+
try {
|
|
410
|
+
response = validateMcpStatusResponse(await client.request("mcpServerStatus/list", {
|
|
411
|
+
threadId,
|
|
412
|
+
cursor: cursor ?? null,
|
|
413
|
+
limit: MCP_STATUS_PAGE_SIZE,
|
|
414
|
+
detail: "full",
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
throw new Error("Codex could not inspect selected MCP server status.");
|
|
419
|
+
}
|
|
420
|
+
statuses.push(...response.data);
|
|
421
|
+
cursor = response.nextCursor;
|
|
422
|
+
if (!cursor)
|
|
423
|
+
break;
|
|
424
|
+
if (cursors.has(cursor))
|
|
425
|
+
throw new Error("Codex MCP status repeated a pagination cursor.");
|
|
426
|
+
cursors.add(cursor);
|
|
427
|
+
if (page === MAX_MCP_STATUS_PAGES - 1)
|
|
428
|
+
throw new Error(`Codex MCP status exceeded ${MAX_MCP_STATUS_PAGES} pages.`);
|
|
429
|
+
}
|
|
430
|
+
const byName = new Map(statuses.flatMap((status) => isRecord(status) && typeof status.name === "string" ? [[status.name, status]] : []));
|
|
431
|
+
for (const expected of this.expectedMcpServers) {
|
|
432
|
+
const status = byName.get(expected.name);
|
|
433
|
+
if (!status || !isRecord(status.tools))
|
|
434
|
+
throw new Error("Codex did not initialize every selected MCP server.");
|
|
435
|
+
const toolNames = new Set(Object.keys(status.tools));
|
|
436
|
+
if (expected.toolNames.some((name) => !toolNames.has(name))) {
|
|
437
|
+
throw new Error("Codex did not expose every selected MCP tool.");
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
dispose() {
|
|
442
|
+
if (this.mcpAccess && this.portableTools)
|
|
443
|
+
this.portableTools.revokeMcpAccess(this.mcpAccess.token);
|
|
444
|
+
}
|
|
445
|
+
}
|