@opengeni/runtime 0.7.1 → 0.13.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 (61) hide show
  1. package/THIRD_PARTY_NOTICES +32 -0
  2. package/dist/agent-UMUIWWD2.js +8 -0
  3. package/dist/agent-UMUIWWD2.js.map +1 -0
  4. package/dist/chunk-4YJ5Z52Z.js +1789 -0
  5. package/dist/chunk-4YJ5Z52Z.js.map +1 -0
  6. package/dist/chunk-65UDUFUO.js +22133 -0
  7. package/dist/chunk-65UDUFUO.js.map +1 -0
  8. package/dist/chunk-DPULLAJG.js +215 -0
  9. package/dist/chunk-DPULLAJG.js.map +1 -0
  10. package/dist/chunk-HAN5D27J.js +292 -0
  11. package/dist/chunk-HAN5D27J.js.map +1 -0
  12. package/dist/chunk-K4V4QYCL.js +37683 -0
  13. package/dist/chunk-K4V4QYCL.js.map +1 -0
  14. package/dist/chunk-PZ5AY32C.js +10 -0
  15. package/dist/chunk-PZ5AY32C.js.map +1 -0
  16. package/dist/{index-DIiiwQY2.d.ts → index-DGUrY_yR.d.ts} +171 -18
  17. package/dist/index.d.ts +404 -125
  18. package/dist/index.js +8857 -412
  19. package/dist/index.js.map +1 -1
  20. package/dist/localSources-T3HZ6KY4.js +15 -0
  21. package/dist/localSources-T3HZ6KY4.js.map +1 -0
  22. package/dist/mcp-network.d.ts +78 -0
  23. package/dist/mcp-network.js +62 -0
  24. package/dist/mcp-network.js.map +1 -0
  25. package/dist/sandbox/index.d.ts +1 -1
  26. package/dist/sandbox/index.js +42 -5
  27. package/dist/skill-library.d.ts +74 -0
  28. package/dist/skill-library.js +18 -0
  29. package/dist/skill-library.js.map +1 -0
  30. package/package.json +18 -5
  31. package/src/bundled_hashicorp_terraform_skills/README.md +3 -3
  32. package/src/bundled_hashicorp_terraform_skills/checkov/SKILL.md +1 -1
  33. package/src/codex-tool-search.ts +20 -1
  34. package/src/context-compaction.ts +421 -24
  35. package/src/history-sanitizer.ts +14 -5
  36. package/src/index.ts +1806 -347
  37. package/src/mcp-network.ts +330 -0
  38. package/src/sandbox/channel-a.ts +526 -113
  39. package/src/sandbox/display-stack.ts +860 -23
  40. package/src/sandbox/exec-banner.ts +67 -0
  41. package/src/sandbox/index.ts +43 -2
  42. package/src/sandbox/providers/modal.ts +29 -2
  43. package/src/sandbox/routing/routing-session.ts +10 -2
  44. package/src/sandbox/run-credentials.ts +818 -0
  45. package/src/sandbox/select.ts +3 -3
  46. package/src/sandbox/selfhosted/capabilities.ts +1 -1
  47. package/src/sandbox/selfhosted/op-stream.ts +40 -0
  48. package/src/sandbox/selfhosted/op-testing.ts +50 -3
  49. package/src/sandbox/selfhosted/session.ts +17 -4
  50. package/src/sandbox/selfhosted/testing.ts +1 -1
  51. package/src/sandbox/stream-port.ts +1 -1
  52. package/src/sandbox/stream-token.ts +1 -1
  53. package/src/sandbox/terminal-server.ts +6 -2
  54. package/src/sandbox/toolspace-token.ts +147 -0
  55. package/src/sandbox/turn-tool-cancellation.ts +940 -0
  56. package/src/sandbox-computer.ts +3 -1
  57. package/src/skill-library.ts +323 -0
  58. package/src/usage-telemetry.ts +495 -51
  59. package/dist/chunk-RYDCVTMW.js +0 -5531
  60. package/dist/chunk-RYDCVTMW.js.map +0 -1
  61. /package/src/{bundled_hashicorp_terraform_skills → bundled_skill_library}/azure-verified-modules/SKILL.md +0 -0
@@ -0,0 +1,330 @@
1
+ import type { Settings } from "@opengeni/config";
2
+ import {
3
+ isNonPublicAddress,
4
+ pinnedFetch,
5
+ resolvePinnedDestination,
6
+ type DnsLookup,
7
+ type FetchLike,
8
+ } from "@opengeni/network";
9
+
10
+ export { undiciFetch } from "@opengeni/network";
11
+
12
+ export const MCP_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
13
+ export const MCP_MAX_INBOUND_REQUEST_BYTES = 1024 * 1024;
14
+ export const MCP_MAX_TOOL_DEFINITION_BYTES = 128 * 1024;
15
+ export const MCP_MAX_TOOL_LIST_BYTES = 4 * 1024 * 1024;
16
+ export const MCP_MAX_TOOL_LIST_ENTRIES = 1_000;
17
+ export const MCP_MAX_TOOL_RESULT_BYTES = 1024 * 1024;
18
+ export const MCP_MAX_TOOL_SEARCH_DISCLOSURE_BYTES = 256 * 1024;
19
+ export const MCP_MAX_SELECTED_SERVERS = 64;
20
+ export const MCP_MAX_CONCURRENT_SERVER_OPERATIONS = 8;
21
+ export const MCP_MAX_AGGREGATE_TOOL_LIST_ENTRIES = 4_096;
22
+ export const MCP_MAX_AGGREGATE_TOOL_LIST_BYTES = 16 * 1024 * 1024;
23
+ export const MCP_MAX_TOOL_SEARCH_SOURCES = 64;
24
+ export const MCP_MAX_TOOL_SEARCH_SOURCE_LABEL_LENGTH = 120;
25
+ export const MCP_MAX_TOOL_SEARCH_DESCRIPTION_BYTES = 16 * 1024;
26
+
27
+ export class McpPayloadTooLargeError extends Error {
28
+ constructor(
29
+ readonly label: string,
30
+ readonly actualBytes: number,
31
+ readonly maxBytes: number,
32
+ ) {
33
+ super(`${label} exceeds the ${maxBytes}-byte safety limit`);
34
+ this.name = "McpPayloadTooLargeError";
35
+ }
36
+ }
37
+
38
+ export function mcpSerializedSizeBytes(value: unknown): number {
39
+ let serialized: string;
40
+ try {
41
+ serialized = JSON.stringify(value) ?? "null";
42
+ } catch {
43
+ throw new Error("MCP payload is not JSON serializable");
44
+ }
45
+ return Buffer.byteLength(serialized);
46
+ }
47
+
48
+ export function assertMcpPayloadWithinBytes(value: unknown, maxBytes: number, label: string): void {
49
+ const actualBytes = mcpSerializedSizeBytes(value);
50
+ if (actualBytes > maxBytes) {
51
+ throw new McpPayloadTooLargeError(label, actualBytes, maxBytes);
52
+ }
53
+ }
54
+
55
+ export function assertMcpToolListWithinBounds<T>(tools: readonly T[]): readonly T[] {
56
+ if (tools.length > MCP_MAX_TOOL_LIST_ENTRIES) {
57
+ throw new McpPayloadTooLargeError("MCP tool list", tools.length, MCP_MAX_TOOL_LIST_ENTRIES);
58
+ }
59
+ for (const tool of tools) {
60
+ const toolBytes = mcpSerializedSizeBytes(tool);
61
+ if (toolBytes > MCP_MAX_TOOL_DEFINITION_BYTES) {
62
+ throw new McpPayloadTooLargeError(
63
+ "MCP tool definition",
64
+ toolBytes,
65
+ MCP_MAX_TOOL_DEFINITION_BYTES,
66
+ );
67
+ }
68
+ }
69
+ // Measure the complete JSON array, including brackets and separators. Summing
70
+ // definitions alone under-counts by one byte per separator plus the array
71
+ // delimiters and makes the advertised serialized-byte ceiling porous.
72
+ const totalBytes = mcpSerializedSizeBytes(tools);
73
+ if (totalBytes > MCP_MAX_TOOL_LIST_BYTES) {
74
+ throw new McpPayloadTooLargeError("MCP tool list", totalBytes, MCP_MAX_TOOL_LIST_BYTES);
75
+ }
76
+ return tools;
77
+ }
78
+
79
+ export function assertMcpServerSelectionWithinBounds<T>(servers: readonly T[]): readonly T[] {
80
+ if (servers.length > MCP_MAX_SELECTED_SERVERS) {
81
+ throw new McpPayloadTooLargeError(
82
+ "selected MCP server count",
83
+ servers.length,
84
+ MCP_MAX_SELECTED_SERVERS,
85
+ );
86
+ }
87
+ return servers;
88
+ }
89
+
90
+ type McpToolListContribution = {
91
+ entries: number;
92
+ bytes: number;
93
+ };
94
+
95
+ /**
96
+ * Atomic aggregate accounting shared by every exposed tools/list in one run.
97
+ * A repeated list for the same source REPLACES its prior contribution, so
98
+ * cache invalidation/relisting cannot ratchet the budget upward. Failed
99
+ * replacements leave the previous accounting intact.
100
+ */
101
+ export class McpAggregateToolListBudget {
102
+ private readonly contributions = new Map<string, McpToolListContribution>();
103
+ private totalEntries = 0;
104
+ private totalBytes = 0;
105
+
106
+ constructor(
107
+ private readonly label = "aggregate MCP tool list",
108
+ private readonly maxEntries = MCP_MAX_AGGREGATE_TOOL_LIST_ENTRIES,
109
+ private readonly maxBytes = MCP_MAX_AGGREGATE_TOOL_LIST_BYTES,
110
+ ) {}
111
+
112
+ replace<T>(sourceId: string, tools: readonly T[]): readonly T[] {
113
+ assertMcpToolListWithinBounds(tools);
114
+ const contribution = {
115
+ entries: tools.length,
116
+ // Conservatively account each server as its complete serialized array.
117
+ // Summing these arrays slightly over-counts the brackets relative to one
118
+ // flattened array, which is intentional for a fail-closed hard ceiling.
119
+ bytes: mcpSerializedSizeBytes(tools),
120
+ };
121
+ const previous = this.contributions.get(sourceId) ?? { entries: 0, bytes: 0 };
122
+ const nextEntries = this.totalEntries - previous.entries + contribution.entries;
123
+ if (nextEntries > this.maxEntries) {
124
+ throw new McpPayloadTooLargeError(this.label, nextEntries, this.maxEntries);
125
+ }
126
+ const nextBytes = this.totalBytes - previous.bytes + contribution.bytes;
127
+ if (nextBytes > this.maxBytes) {
128
+ throw new McpPayloadTooLargeError(this.label, nextBytes, this.maxBytes);
129
+ }
130
+ this.contributions.set(sourceId, contribution);
131
+ this.totalEntries = nextEntries;
132
+ this.totalBytes = nextBytes;
133
+ return tools;
134
+ }
135
+
136
+ remove(sourceId: string): void {
137
+ const previous = this.contributions.get(sourceId);
138
+ if (!previous) return;
139
+ this.contributions.delete(sourceId);
140
+ this.totalEntries -= previous.entries;
141
+ this.totalBytes -= previous.bytes;
142
+ }
143
+
144
+ snapshot(): Readonly<McpToolListContribution> {
145
+ return { entries: this.totalEntries, bytes: this.totalBytes };
146
+ }
147
+ }
148
+
149
+ /** Stable-order parallel map with a hard in-flight ceiling and fail-stop drain. */
150
+ export async function boundedParallelMap<T, R>(
151
+ values: readonly T[],
152
+ concurrency: number,
153
+ mapper: (value: T, index: number) => Promise<R>,
154
+ ): Promise<R[]> {
155
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
156
+ throw new Error("bounded parallel map concurrency must be a positive integer");
157
+ }
158
+ if (values.length === 0) return [];
159
+
160
+ const results = new Array<R>(values.length);
161
+ const errors: Array<{ index: number; error: unknown }> = [];
162
+ let nextIndex = 0;
163
+ let stopped = false;
164
+ const worker = async () => {
165
+ while (!stopped) {
166
+ const index = nextIndex;
167
+ if (index >= values.length) return;
168
+ nextIndex += 1;
169
+ try {
170
+ results[index] = await mapper(values[index]!, index);
171
+ } catch (error) {
172
+ errors.push({ index, error });
173
+ stopped = true;
174
+ }
175
+ }
176
+ };
177
+ await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, () => worker()));
178
+ if (errors.length > 0) {
179
+ errors.sort((left, right) => left.index - right.index);
180
+ throw errors[0]!.error;
181
+ }
182
+ return results;
183
+ }
184
+
185
+ /**
186
+ * Guard normal MCP traffic with the same deployment policy as OAuth traffic.
187
+ * The pinned transport resolves and policy-checks the destination after the
188
+ * caller has composed any credential headers, then performs the final network
189
+ * call through the vetted Agent. It forces manual redirects, so an
190
+ * Authorization-bearing request never follows a provider-controlled Location
191
+ * to an unvalidated host.
192
+ *
193
+ * This module is deliberately agent-loop-free. API code imports the explicit
194
+ * `@opengeni/runtime/mcp-network` leaf rather than the runtime root barrel.
195
+ */
196
+ export function guardedMcpFetch<TInput extends string | URL | Request>(
197
+ settings: Pick<Settings, "environment" | "integrationsAllowPrivateNetworkTargets">,
198
+ fetchImpl: (input: TInput, init?: RequestInit) => Promise<Response>,
199
+ options: {
200
+ maxResponseBytes?: number;
201
+ dnsLookup?: DnsLookup;
202
+ requireHttpsOutsideLocalTest?: boolean;
203
+ } = {},
204
+ ): (input: TInput, init?: RequestInit) => Promise<Response> {
205
+ return async (input: TInput, init?: RequestInit) => {
206
+ const response = await pinnedFetch(input, init, settings, {
207
+ fetchImpl: fetchImpl as FetchLike,
208
+ ...(options.dnsLookup ? { dnsLookup: options.dnsLookup } : {}),
209
+ label: "MCP endpoint",
210
+ requireHttpsOutsideLocalTest: options.requireHttpsOutsideLocalTest ?? true,
211
+ });
212
+ return boundMcpResponseBody(response, options.maxResponseBytes ?? MCP_MAX_RESPONSE_BYTES);
213
+ };
214
+ }
215
+
216
+ export function boundMcpResponseBody(response: Response, maxBytes: number): Response {
217
+ const declaredLength = Number(response.headers.get("content-length"));
218
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
219
+ void response.body?.cancel().catch(() => undefined);
220
+ throw new McpPayloadTooLargeError("MCP response", declaredLength, maxBytes);
221
+ }
222
+ if (!response.body) {
223
+ return response;
224
+ }
225
+
226
+ const reader = response.body.getReader();
227
+ let receivedBytes = 0;
228
+ const body = new ReadableStream<Uint8Array>({
229
+ async pull(controller) {
230
+ try {
231
+ const chunk = await reader.read();
232
+ if (chunk.done) {
233
+ controller.close();
234
+ return;
235
+ }
236
+ receivedBytes += chunk.value.byteLength;
237
+ if (receivedBytes > maxBytes) {
238
+ await reader.cancel().catch(() => undefined);
239
+ controller.error(new McpPayloadTooLargeError("MCP response", receivedBytes, maxBytes));
240
+ return;
241
+ }
242
+ controller.enqueue(chunk.value);
243
+ } catch (error) {
244
+ controller.error(error);
245
+ }
246
+ },
247
+ async cancel(reason) {
248
+ await reader.cancel(reason).catch(() => undefined);
249
+ },
250
+ });
251
+ const bounded = new Response(body, {
252
+ status: response.status,
253
+ statusText: response.statusText,
254
+ headers: response.headers,
255
+ });
256
+ Object.defineProperties(bounded, {
257
+ redirected: { value: response.redirected },
258
+ type: { value: response.type },
259
+ url: { value: response.url },
260
+ });
261
+ return bounded;
262
+ }
263
+
264
+ export async function cancelMcpResponseBody(response: Response): Promise<void> {
265
+ await response.body?.cancel().catch(() => undefined);
266
+ }
267
+
268
+ /** Read and rebuild an inbound MCP request before the SDK parses it. */
269
+ export async function boundedMcpRequest(
270
+ request: Request,
271
+ maxBytes = MCP_MAX_INBOUND_REQUEST_BYTES,
272
+ ): Promise<Request> {
273
+ const declared = request.headers.get("content-length");
274
+ if (declared !== null) {
275
+ const normalized = declared.trim();
276
+ if (!/^\d+$/.test(normalized)) {
277
+ await request.body?.cancel().catch(() => undefined);
278
+ throw new McpPayloadTooLargeError("MCP request", 0, maxBytes);
279
+ }
280
+ const declaredBytes = Number(normalized);
281
+ if (!Number.isSafeInteger(declaredBytes) || declaredBytes > maxBytes) {
282
+ await request.body?.cancel().catch(() => undefined);
283
+ throw new McpPayloadTooLargeError("MCP request", declaredBytes, maxBytes);
284
+ }
285
+ }
286
+ if (!request.body) {
287
+ return request;
288
+ }
289
+ const reader = request.body.getReader();
290
+ const chunks: Uint8Array[] = [];
291
+ let receivedBytes = 0;
292
+ try {
293
+ for (;;) {
294
+ const result = await reader.read();
295
+ if (result.done) break;
296
+ receivedBytes += result.value.byteLength;
297
+ if (receivedBytes > maxBytes) {
298
+ await reader.cancel().catch(() => undefined);
299
+ throw new McpPayloadTooLargeError("MCP request", receivedBytes, maxBytes);
300
+ }
301
+ chunks.push(result.value);
302
+ }
303
+ } catch (error) {
304
+ await reader.cancel().catch(() => undefined);
305
+ throw error;
306
+ } finally {
307
+ reader.releaseLock();
308
+ }
309
+ const body = new Uint8Array(receivedBytes);
310
+ let offset = 0;
311
+ for (const chunk of chunks) {
312
+ body.set(chunk, offset);
313
+ offset += chunk.byteLength;
314
+ }
315
+ return new Request(request, { body, headers: request.headers });
316
+ }
317
+
318
+ export async function assertMcpDestinationAllowed(
319
+ rawUrl: string,
320
+ settings: Pick<Settings, "environment" | "integrationsAllowPrivateNetworkTargets">,
321
+ options: { dnsLookup?: DnsLookup } = {},
322
+ ): Promise<void> {
323
+ await resolvePinnedDestination(rawUrl, settings, {
324
+ ...(options.dnsLookup ? { dnsLookup: options.dnsLookup } : {}),
325
+ label: "MCP endpoint",
326
+ requireHttpsOutsideLocalTest: true,
327
+ });
328
+ }
329
+
330
+ export const isNonPublicMcpAddress = isNonPublicAddress;