@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.
- package/dist/chunk-2PO56VAL.js +3478 -0
- package/dist/chunk-2PO56VAL.js.map +1 -0
- package/dist/index.d.ts +912 -0
- package/dist/index.js +3663 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +1738 -0
- package/dist/sandbox/index.js +187 -0
- package/dist/sandbox/index.js.map +1 -0
- package/package.json +49 -0
- package/src/bundled_hashicorp_terraform_skills/LICENSE +373 -0
- package/src/bundled_hashicorp_terraform_skills/README.md +18 -0
- package/src/bundled_hashicorp_terraform_skills/UPSTREAM_GIT_SHA +1 -0
- package/src/bundled_hashicorp_terraform_skills/azure-verified-modules/SKILL.md +613 -0
- package/src/bundled_hashicorp_terraform_skills/checkov/SKILL.md +43 -0
- package/src/bundled_hashicorp_terraform_skills/refactor-module/SKILL.md +538 -0
- package/src/bundled_hashicorp_terraform_skills/social-media-marketing/SKILL.md +35 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-search-import/SKILL.md +372 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-search-import/references/MANUAL-IMPORT.md +113 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-search-import/scripts/list_resources.sh +38 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/SKILL.md +480 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/api-monitoring.md +543 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/component-blocks.md +476 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/deployment-blocks.md +391 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/examples.md +1529 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/linked-stacks.md +187 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/troubleshooting.md +671 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-style-guide/SKILL.md +353 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/SKILL.md +451 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/references/CI_CD.md +80 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/references/EXAMPLES.md +314 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/references/MOCK_PROVIDERS.md +171 -0
- package/src/codex-tool-search.ts +267 -0
- package/src/context-compaction.ts +538 -0
- package/src/history-sanitizer.ts +719 -0
- package/src/index.ts +3299 -0
- package/src/sandbox/capabilities.ts +69 -0
- package/src/sandbox/channel-a.ts +1031 -0
- package/src/sandbox/display-stack.ts +231 -0
- package/src/sandbox/errors.ts +34 -0
- package/src/sandbox/index.ts +832 -0
- package/src/sandbox/providers/blaxel.ts +35 -0
- package/src/sandbox/providers/cloudflare.ts +24 -0
- package/src/sandbox/providers/daytona.ts +34 -0
- package/src/sandbox/providers/docker.ts +17 -0
- package/src/sandbox/providers/e2b.ts +36 -0
- package/src/sandbox/providers/index.ts +107 -0
- package/src/sandbox/providers/local.ts +13 -0
- package/src/sandbox/providers/modal.ts +55 -0
- package/src/sandbox/providers/none.ts +13 -0
- package/src/sandbox/providers/runloop.ts +32 -0
- package/src/sandbox/providers/selfhosted.ts +96 -0
- package/src/sandbox/providers/types.ts +38 -0
- package/src/sandbox/providers/vercel.ts +29 -0
- package/src/sandbox/recording.ts +286 -0
- package/src/sandbox/routing/backend-resolver.ts +189 -0
- package/src/sandbox/routing/routing-session.ts +455 -0
- package/src/sandbox/select.ts +371 -0
- package/src/sandbox/selfhosted/capabilities.ts +255 -0
- package/src/sandbox/selfhosted/control-rpc.ts +351 -0
- package/src/sandbox/selfhosted/session.ts +930 -0
- package/src/sandbox/selfhosted/testing.ts +230 -0
- package/src/sandbox/stream-port.ts +185 -0
- package/src/sandbox/stream-token.ts +90 -0
- package/src/sandbox/terminal-server.ts +203 -0
- 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
|
+
}
|