@opengeni/runtime 0.2.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 (65) hide show
  1. package/dist/chunk-2PO56VAL.js +3478 -0
  2. package/dist/chunk-2PO56VAL.js.map +1 -0
  3. package/dist/index.d.ts +912 -0
  4. package/dist/index.js +3663 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/sandbox/index.d.ts +1738 -0
  7. package/dist/sandbox/index.js +187 -0
  8. package/dist/sandbox/index.js.map +1 -0
  9. package/package.json +49 -0
  10. package/src/bundled_hashicorp_terraform_skills/LICENSE +373 -0
  11. package/src/bundled_hashicorp_terraform_skills/README.md +18 -0
  12. package/src/bundled_hashicorp_terraform_skills/UPSTREAM_GIT_SHA +1 -0
  13. package/src/bundled_hashicorp_terraform_skills/azure-verified-modules/SKILL.md +613 -0
  14. package/src/bundled_hashicorp_terraform_skills/checkov/SKILL.md +43 -0
  15. package/src/bundled_hashicorp_terraform_skills/refactor-module/SKILL.md +538 -0
  16. package/src/bundled_hashicorp_terraform_skills/social-media-marketing/SKILL.md +35 -0
  17. package/src/bundled_hashicorp_terraform_skills/terraform-search-import/SKILL.md +372 -0
  18. package/src/bundled_hashicorp_terraform_skills/terraform-search-import/references/MANUAL-IMPORT.md +113 -0
  19. package/src/bundled_hashicorp_terraform_skills/terraform-search-import/scripts/list_resources.sh +38 -0
  20. package/src/bundled_hashicorp_terraform_skills/terraform-stacks/SKILL.md +480 -0
  21. package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/api-monitoring.md +543 -0
  22. package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/component-blocks.md +476 -0
  23. package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/deployment-blocks.md +391 -0
  24. package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/examples.md +1529 -0
  25. package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/linked-stacks.md +187 -0
  26. package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/troubleshooting.md +671 -0
  27. package/src/bundled_hashicorp_terraform_skills/terraform-style-guide/SKILL.md +353 -0
  28. package/src/bundled_hashicorp_terraform_skills/terraform-test/SKILL.md +451 -0
  29. package/src/bundled_hashicorp_terraform_skills/terraform-test/references/CI_CD.md +80 -0
  30. package/src/bundled_hashicorp_terraform_skills/terraform-test/references/EXAMPLES.md +314 -0
  31. package/src/bundled_hashicorp_terraform_skills/terraform-test/references/MOCK_PROVIDERS.md +171 -0
  32. package/src/codex-tool-search.ts +267 -0
  33. package/src/context-compaction.ts +538 -0
  34. package/src/history-sanitizer.ts +719 -0
  35. package/src/index.ts +3299 -0
  36. package/src/sandbox/capabilities.ts +69 -0
  37. package/src/sandbox/channel-a.ts +1031 -0
  38. package/src/sandbox/display-stack.ts +231 -0
  39. package/src/sandbox/errors.ts +34 -0
  40. package/src/sandbox/index.ts +832 -0
  41. package/src/sandbox/providers/blaxel.ts +35 -0
  42. package/src/sandbox/providers/cloudflare.ts +24 -0
  43. package/src/sandbox/providers/daytona.ts +34 -0
  44. package/src/sandbox/providers/docker.ts +17 -0
  45. package/src/sandbox/providers/e2b.ts +36 -0
  46. package/src/sandbox/providers/index.ts +107 -0
  47. package/src/sandbox/providers/local.ts +13 -0
  48. package/src/sandbox/providers/modal.ts +55 -0
  49. package/src/sandbox/providers/none.ts +13 -0
  50. package/src/sandbox/providers/runloop.ts +32 -0
  51. package/src/sandbox/providers/selfhosted.ts +96 -0
  52. package/src/sandbox/providers/types.ts +38 -0
  53. package/src/sandbox/providers/vercel.ts +29 -0
  54. package/src/sandbox/recording.ts +286 -0
  55. package/src/sandbox/routing/backend-resolver.ts +189 -0
  56. package/src/sandbox/routing/routing-session.ts +455 -0
  57. package/src/sandbox/select.ts +371 -0
  58. package/src/sandbox/selfhosted/capabilities.ts +255 -0
  59. package/src/sandbox/selfhosted/control-rpc.ts +351 -0
  60. package/src/sandbox/selfhosted/session.ts +930 -0
  61. package/src/sandbox/selfhosted/testing.ts +230 -0
  62. package/src/sandbox/stream-port.ts +185 -0
  63. package/src/sandbox/stream-token.ts +90 -0
  64. package/src/sandbox/terminal-server.ts +203 -0
  65. package/src/sandbox-computer.ts +835 -0
@@ -0,0 +1,267 @@
1
+ // Progressive connector disclosure for the ChatGPT/Codex backend — parity with
2
+ // how the Codex CLI surfaces its ~217 `codex_apps` connector tools WITHOUT paying
3
+ // their schemas in every turn's context.
4
+ //
5
+ // WHY. OpenGeni connects the codex_apps MCP server as a CLIENT and the SDK
6
+ // materializes EVERY connector tool as a `function` tool in request.tools[] on
7
+ // every codex turn (~217 tools ≈ tens of thousands of context tokens). The Codex
8
+ // CLI instead uses the backend's NATIVE tool-search: the model gets one compact
9
+ // search tool + all connector tools flagged `defer_loading:true` (schemas dropped
10
+ // from model context), searches by capability, and only the matched tools are
11
+ // disclosed back and become callable.
12
+ //
13
+ // PROVEN LIVE (Phase 0 probe against /codex/responses on gpt-5.5): the backend
14
+ // HONORS `defer_loading:true` on function tools — 50 fat tools dropped input_tokens
15
+ // 6766 → 108 — AND makes a tool callable purely because a prior `tool_search_output`
16
+ // disclosed it (V3). The model emits `tool_search_call` on our slug.
17
+ //
18
+ // HOW. We wrap the agent's `getAllTools` (codex turns only, flag-gated): tag every
19
+ // `codex_apps__*` function tool `deferLoading = true` (converTool then serializes
20
+ // `defer_loading:true`, dropping the schema from context) and append one
21
+ // client-executed `toolSearchTool`. The SDK routes a `tool_search_call` to our
22
+ // executor (ClientToolSearchExecutor), which BM25-ranks the deferred pool and
23
+ // returns the matched tools BY REFERENCE — the SDK emits the `tool_search_output`
24
+ // that discloses them; the subsequent `function_call` resolves through the normal
25
+ // PrefixedMcpServer.callTool path (auth + name-sanitize + structuredContent inline
26
+ // all unchanged). Invocation is untouched; only the wire tool-set shrinks.
27
+
28
+ import { toolSearchTool, type Tool } from "@openai/agents";
29
+
30
+ /** The prefix OpenGeni's PrefixedMcpServer stamps on codex_apps connector tools. */
31
+ export const CODEX_APPS_TOOL_PREFIX = "codex_apps__";
32
+ const DEFAULT_SEARCH_LIMIT = 8;
33
+ const MAX_SEARCH_LIMIT = 20;
34
+
35
+ /** True for a materialized codex_apps connector function tool. */
36
+ export function isCodexAppsFunctionTool(tool: unknown): tool is Tool & { name: string; deferLoading?: boolean } {
37
+ return (
38
+ !!tool
39
+ && typeof tool === "object"
40
+ && (tool as { type?: unknown }).type === "function"
41
+ && typeof (tool as { name?: unknown }).name === "string"
42
+ && (tool as { name: string }).name.startsWith(CODEX_APPS_TOOL_PREFIX)
43
+ );
44
+ }
45
+
46
+ // Minimal English stopword set: query phrasings like "send an email to someone"
47
+ // should match on capability words, not drown in glue words (parity with
48
+ // codex-rs, whose search normalizes tokens server-side).
49
+ const STOPWORDS = new Set([
50
+ "a", "an", "the", "and", "or", "of", "to", "in", "on", "for", "with", "by", "at",
51
+ "is", "are", "be", "do", "does", "my", "me", "your", "you", "it", "its", "this",
52
+ "that", "from", "as", "up", "out", "all", "some", "any", "can", "will", "would",
53
+ "should", "want", "need", "please", "user", "users",
54
+ ]);
55
+
56
+ /** Light suffix stemmer so "emails"/"email", "creating"/"create" co-match (min-stem guards, no over-stripping). */
57
+ function stem(token: string): string {
58
+ if (token.length > 5 && token.endsWith("ing")) return token.slice(0, -3);
59
+ if (token.length > 4 && token.endsWith("ed")) return token.slice(0, -2);
60
+ if (token.length > 4 && token.endsWith("es")) return token.slice(0, -2);
61
+ if (token.length > 3 && token.endsWith("s") && !token.endsWith("ss")) return token.slice(0, -1);
62
+ return token;
63
+ }
64
+
65
+ /** Split snake_case/camelCase/dotted text into stemmed lowercase word tokens (len ≥ 2, stopwords removed). */
66
+ function tokenize(text: string): string[] {
67
+ return text
68
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
69
+ .toLowerCase()
70
+ .split(/[^a-z0-9]+/)
71
+ .filter((t) => t.length > 1 && !STOPWORDS.has(t))
72
+ .map(stem);
73
+ }
74
+
75
+ /** The searchable text of a connector tool: name (weighted ×2) + description + param names. */
76
+ function toolSearchText(tool: Tool): string {
77
+ const raw = (tool as { name?: string }).name ?? "";
78
+ const name = raw.startsWith(CODEX_APPS_TOOL_PREFIX) ? raw.slice(CODEX_APPS_TOOL_PREFIX.length) : raw;
79
+ const description = typeof (tool as { description?: unknown }).description === "string" ? (tool as { description: string }).description : "";
80
+ const params = (tool as { parameters?: { properties?: Record<string, unknown> } }).parameters?.properties;
81
+ const paramNames = params && typeof params === "object" ? Object.keys(params).join(" ") : "";
82
+ return `${name} ${name} ${description} ${paramNames}`;
83
+ }
84
+
85
+ /**
86
+ * Rank connector tools against a plain-language query with BM25 (Okapi, k1=1.5,
87
+ * b=0.75) over tokenized name+description+params. Returns the top `limit` tools
88
+ * with a positive score, most-relevant first. An empty or no-match query returns
89
+ * [] — matching codex-rs, whose search returns empty rather than arbitrary tools;
90
+ * disclosing unrelated (and thereby CALLABLE) tools on a miss feeds the model
91
+ * noise. The SDK normalizes an empty executor result into an empty
92
+ * tool_search_output, which the model reads as "nothing matched — rephrase".
93
+ */
94
+ export function bm25RankTools(tools: Tool[], query: string, limit: number): Tool[] {
95
+ const qTokens = Array.from(new Set(tokenize(query)));
96
+ if (tools.length === 0 || qTokens.length === 0) return [];
97
+
98
+ const docs = tools.map((tool) => {
99
+ const tokens = tokenize(toolSearchText(tool));
100
+ const tf = new Map<string, number>();
101
+ for (const t of tokens) tf.set(t, (tf.get(t) ?? 0) + 1);
102
+ return { tool, len: tokens.length, tf };
103
+ });
104
+ const N = docs.length;
105
+ const avgdl = Math.max(1, docs.reduce((s, d) => s + d.len, 0) / N);
106
+ const df = new Map<string, number>();
107
+ for (const d of docs) for (const t of d.tf.keys()) df.set(t, (df.get(t) ?? 0) + 1);
108
+
109
+ const k1 = 1.5;
110
+ const b = 0.75;
111
+ const scored = docs.map((d) => {
112
+ let score = 0;
113
+ for (const qt of qTokens) {
114
+ const n = df.get(qt);
115
+ const f = d.tf.get(qt);
116
+ if (!n || !f) continue;
117
+ const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
118
+ score += idf * ((f * (k1 + 1)) / (f + k1 * (1 - b + (b * d.len) / avgdl)));
119
+ }
120
+ return { tool: d.tool, score };
121
+ });
122
+ const hits = scored.filter((s) => s.score > 0).sort((a, b2) => b2.score - a.score);
123
+ return hits.slice(0, limit).map((s) => s.tool); // no hits ⇒ [] (codex-rs parity; see doc)
124
+ }
125
+
126
+ /** Parse `{query, limit}` from a tool_search_call's arguments (string or object). */
127
+ function parseSearchArgs(raw: unknown): { query: string; limit: number } {
128
+ let obj: Record<string, unknown> = {};
129
+ try {
130
+ obj = typeof raw === "string" ? (raw.length ? JSON.parse(raw) : {}) : (raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {});
131
+ } catch {
132
+ obj = {};
133
+ }
134
+ const query = typeof obj.query === "string" ? obj.query : "";
135
+ const limitRaw = typeof obj.limit === "number" && Number.isFinite(obj.limit) ? obj.limit : DEFAULT_SEARCH_LIMIT;
136
+ return { query, limit: Math.max(1, Math.min(MAX_SEARCH_LIMIT, Math.round(limitRaw))) };
137
+ }
138
+
139
+ /**
140
+ * Render the search tool's description from the account's ACTUALLY-connected
141
+ * sources — codex-rs parity (its create_tool_search_tool builds "You have access
142
+ * to tools from the following sources:\n- <name>" from the live enabled-source
143
+ * list). With defer_loading stripping every connector schema (and name) from
144
+ * model context, this description is the model's ONLY signal of which apps
145
+ * exist, so a hardcoded list would both advertise absent connectors and hide
146
+ * present ones.
147
+ */
148
+ export function renderSearchToolDescription(connectorNamespaces: ReadonlySet<string>): string {
149
+ const base =
150
+ "Search the user's connected app tools by capability. "
151
+ + "Describe in plain language WHAT you need to do (for example: \"send an email\", \"create a calendar event\") "
152
+ + "rather than guessing exact tool names. Returns the matching connector tools, which then become callable.";
153
+ const sources = Array.from(connectorNamespaces).filter((n) => typeof n === "string" && n.length > 0).sort();
154
+ if (sources.length === 0) {
155
+ return `${base}\nConnected sources: none currently available.`;
156
+ }
157
+ return `${base}\nYou have access to tools from the following sources:\n${sources.map((s) => `- ${s}`).join("\n")}`;
158
+ }
159
+
160
+ const SEARCH_TOOL_PARAMETERS = {
161
+ type: "object",
162
+ properties: {
163
+ query: { type: "string", description: "Plain-language description of the capability you need." },
164
+ limit: { type: "number", description: "Maximum number of tools to return (default 8)." },
165
+ },
166
+ required: ["query"],
167
+ additionalProperties: false,
168
+ } as const;
169
+
170
+ /**
171
+ * The client tool-search executor: BM25 over the deferred codex_apps pool from the
172
+ * turn's live `availableTools`, returning matched tools BY REFERENCE (the only legal
173
+ * return — the SDK emits the disclosing `tool_search_output` and flips the loaded
174
+ * gate; returning copies would throw). Stateless — reads the pool per call.
175
+ */
176
+ function codexToolSearchExecutor(args: { availableTools?: Tool[]; toolCall?: { arguments?: unknown } }): Tool[] {
177
+ const deferred = (args.availableTools ?? []).filter(isCodexAppsFunctionTool);
178
+ if (deferred.length === 0) return [];
179
+ const { query, limit } = parseSearchArgs(args.toolCall?.arguments);
180
+ return bm25RankTools(deferred, query, limit);
181
+ }
182
+
183
+ const NO_NAMESPACES: ReadonlySet<string> = new Set();
184
+
185
+ /** Build the client-executed tool_search tool that discloses codex_apps connectors on demand. */
186
+ export function buildCodexToolSearchTool(connectorNamespaces: ReadonlySet<string> = NO_NAMESPACES): Tool {
187
+ return toolSearchTool({
188
+ execution: "client",
189
+ description: renderSearchToolDescription(connectorNamespaces),
190
+ parameters: SEARCH_TOOL_PARAMETERS as unknown as Record<string, unknown>,
191
+ execute: codexToolSearchExecutor as never,
192
+ }) as unknown as Tool;
193
+ }
194
+
195
+ /** True for the built-in tool_search tool (so we never add a second one). */
196
+ function isToolSearchTool(tool: unknown): boolean {
197
+ const t = tool as { name?: unknown; providerData?: { type?: unknown } } | null;
198
+ return !!t && (t.name === "tool_search" || t.providerData?.type === "tool_search");
199
+ }
200
+
201
+ /**
202
+ * The transform applied to a turn's resolved tool list: tag every codex_apps
203
+ * connector function tool `deferLoading = true`, and — unless one is already
204
+ * present — append the client tool_search tool. Pure (mutates the passed tools +
205
+ * returns the possibly-extended array); the SDK's `getTools` gate requires a
206
+ * tool_search whenever a deferred tool is present, which appending here satisfies
207
+ * in the same request.
208
+ *
209
+ * The search tool is appended UNCONDITIONALLY (even when the turn has no
210
+ * codex_apps tools, e.g. the best-effort codex_apps connect was dropped this
211
+ * turn): a prior turn's history may carry tool_search_call/output items, and
212
+ * replaying those without a tool_search tool in the request risks a backend
213
+ * reject; a search over an empty pool simply discloses nothing. The description
214
+ * reflects the LIVE connector namespaces, so an empty turn reads
215
+ * "none currently available".
216
+ */
217
+ export function applyCodexToolSearch(tools: Tool[], connectorNamespaces: ReadonlySet<string> = NO_NAMESPACES): Tool[] {
218
+ for (const tool of tools) {
219
+ if (isCodexAppsFunctionTool(tool) && (tool as { deferLoading?: boolean }).deferLoading !== true) {
220
+ (tool as { deferLoading?: boolean }).deferLoading = true;
221
+ }
222
+ }
223
+ if (tools.some(isToolSearchTool)) {
224
+ return tools;
225
+ }
226
+ return [...tools, buildCodexToolSearchTool(connectorNamespaces)];
227
+ }
228
+
229
+ type CloneCapableAgent = {
230
+ getAllTools: (runContext: unknown) => Promise<Tool[]>;
231
+ clone?: (config: unknown) => CloneCapableAgent;
232
+ };
233
+
234
+ /**
235
+ * Install progressive connector disclosure on a codex-path agent by wrapping
236
+ * `getAllTools` so every per-model-call tool resolution runs
237
+ * {@link applyCodexToolSearch}. Idempotent-per-call (re-tags each
238
+ * freshly-materialized MCP tool under cacheToolsList:false). Gated by the caller
239
+ * (flag + codex path).
240
+ *
241
+ * CLONE SURVIVAL (the part that makes this work on the REAL sandbox path): the
242
+ * SDK's sandbox runtime routes EVERY model call through
243
+ * `prepareSandboxAgent → agent.clone(...)` (agentPreparation.js), and
244
+ * `SandboxAgent.clone` constructs a FRESH agent from a fixed field list — an
245
+ * instance-own `getAllTools` override is NOT copied, so patching only this
246
+ * instance would silently no-op the whole feature on sandbox turns (the run
247
+ * loop resolves tools on the CLONE). We therefore also wrap `clone` to
248
+ * RE-INSTALL onto every clone, recursively — covering clone-of-clone and the
249
+ * RunState resume paths. `connectorNamespaces` is the LIVE, by-reference Set the
250
+ * codex_apps sanitizing transport fills during each turn's tools/list
251
+ * (prepareAgentTools), so by the time the wrapper post-processes getAllTools the
252
+ * current turn's connector sources are known.
253
+ */
254
+ export function installCodexToolSearch(agent: CloneCapableAgent, connectorNamespaces: ReadonlySet<string> = NO_NAMESPACES): void {
255
+ const originalGetAllTools = agent.getAllTools.bind(agent);
256
+ agent.getAllTools = (async (runContext: unknown) =>
257
+ applyCodexToolSearch(await originalGetAllTools(runContext), connectorNamespaces)) as typeof agent.getAllTools;
258
+ const originalClone = agent.clone?.bind(agent);
259
+ if (originalClone) {
260
+ const cloneWithToolSearch: NonNullable<CloneCapableAgent["clone"]> = (config: unknown) => {
261
+ const cloned = originalClone(config);
262
+ installCodexToolSearch(cloned, connectorNamespaces);
263
+ return cloned;
264
+ };
265
+ agent.clone = cloneWithToolSearch;
266
+ }
267
+ }