@opengeni/runtime 0.13.3 → 0.13.10
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-SSB7VOLE.js → chunk-QYXOVIUK.js} +15 -4
- package/dist/chunk-QYXOVIUK.js.map +1 -0
- package/dist/{chunk-JGUQSAG5.js → chunk-RHB7ETON.js} +3078 -3030
- package/dist/chunk-RHB7ETON.js.map +1 -0
- package/dist/{index-DU-KXVlx.d.ts → index-Ciehex_q.d.ts} +13 -2
- package/dist/index.d.ts +11 -3
- package/dist/index.js +255 -43
- package/dist/index.js.map +1 -1
- package/dist/mcp-network.d.ts +1 -0
- package/dist/mcp-network.js +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +1 -1
- package/package.json +3 -3
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/troubleshooting.md +1 -1
- package/src/codex-tool-search.ts +161 -36
- package/src/index.ts +209 -25
- package/src/mcp-network.ts +15 -3
- package/src/sandbox/index.ts +1 -0
- package/src/sandbox/routing/routing-session.ts +49 -4
- package/src/sandbox/run-credentials.ts +2 -2
- package/src/sandbox/turn-tool-cancellation.ts +29 -0
- package/src/sandbox/workspace-archive.ts +4 -1
- package/src/sandbox-computer.ts +5 -2
- package/dist/chunk-JGUQSAG5.js.map +0 -1
- package/dist/chunk-SSB7VOLE.js.map +0 -1
package/dist/mcp-network.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ declare function boundedParallelMap<T, R>(values: readonly T[], concurrency: num
|
|
|
66
66
|
declare function guardedMcpFetch<TInput extends string | URL | Request>(settings: Pick<Settings, "environment" | "integrationsAllowPrivateNetworkTargets">, fetchImpl: (input: TInput, init?: RequestInit) => Promise<Response>, options?: {
|
|
67
67
|
maxResponseBytes?: number;
|
|
68
68
|
dnsLookup?: DnsLookup;
|
|
69
|
+
pinResolvedDestination?: boolean;
|
|
69
70
|
requireHttpsOutsideLocalTest?: boolean;
|
|
70
71
|
}): (input: TInput, init?: RequestInit) => Promise<Response>;
|
|
71
72
|
declare function boundMcpResponseBody(response: Response, maxBytes: number): Response;
|
package/dist/mcp-network.js
CHANGED
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { collectSandboxEnvironment, parseExposedPorts } from '@opengeni/config';
|
|
2
2
|
export { CAPABILITY_DESCRIPTORS, CapabilityDescriptor, DESKTOP_STREAM_PORT, StreamTokenPayload, StreamTokenPayload as StreamTokenPayloadType, TERMINAL_STREAM_PORT } from '@opengeni/contracts';
|
|
3
3
|
import '@openai/agents/sandbox';
|
|
4
|
-
export { A as ActiveBackendResolverDeps, b as ActiveBackendUnresolvableError, c as ActivePointer, B as BackendUnresolvableCode, C as ChannelAConflictError, d as ChannelAEmitter, e as ChannelAExecArgs, f as ChannelAExecResult, g as ChannelANotFoundError, h as ChannelASession, i as ChannelAUnavailableError, j as ChannelAUnsupportedError, k as ChannelAValidationError, l as ControlRpc, D as DEFAULT_DESKTOP_GEOMETRY, m as DISPLAY_STACK_TIMEOUT_MS, n as DefaultBackendLossResult, o as DesktopGeometry, p as DisplayStackCallerKind, q as DisplayStackClassification, r as DisplayStackError, s as DisplayStackTelemetryContext, t as DisplayStackTelemetryEvent, u as DisplayStackTelemetryStatus, v as DisplayStackUnsupportedError, E as EnsureDisplayStackOptions, w as EnsureDisplayStackResult, x as EnsureTerminalServerOptions, y as EnsureTerminalServerResult, z as EstablishedSandboxSession, F as ExposeStreamPortInput, G as ExposeStreamPortResult, H as ExposedPortEndpoint, I as FAULT_FIELD_WHAT_HAPPENED, J as FAULT_FIELD_WHAT_PRESERVED, K as FAULT_FIELD_WHAT_TO_TRY, L as FAULT_FIELD_WHICH_LAYER, M as LiveModalSandboxLeaseAttribution, N as MaterializeRunCredentialsOptions, O as MintStreamTokenInput, P as MockAgentResponder, Q as MockAgentResponderOptions, S as MockExecHandler, T as ModalModuleLoader, U as ModalOrphanSweepResult, V as ModalOrphanSweepTermination, W as ModalSandboxAttribution, X as NEVER_SENT_DETAIL_KEY, Y as NatsControlRpc, Z as NatsOpStreamConnection, _ as NatsOpStreamTransport, $ as NatsRequestConnection, a0 as NegotiationContext, a1 as NormalizedRunCredentialMaterial, a2 as NumstatEntry, a3 as OP_STREAM_ACK_INTERVAL_MS, a4 as OP_STREAM_DEFAULT_WINDOW_BYTES, a5 as OP_STREAM_RECONNECT_HOLD_MS, a6 as OP_STREAM_SILENCE_TIMEOUT_MS, a7 as OpStreamExecOutcome, a8 as OpStreamJournal, a9 as OpStreamSubscription, aa as OpStreamTransport, ab as OpStreamUnavailableError, ac as PROVIDER_REGISTRY, ad as ProviderConstructionContext, ae as ProviderRegistration, af as ProviderSandboxFailure, ag as ProviderSandboxFailureKind, ah as REPOSITORY_DISCOVERY_LIMIT, ai as RecordingArtifactMetadata, aj as RecordingCodec, ak as RecordingContentType, al as RecordingError, am as RecordingProcess, an as RecordingUnavailableError, ao as RepositoryDiscoveryDegradedReason, ap as RepositoryDiscoveryResult, aq as ResolvedActiveBackend, ar as RevalidateModalOrphanTermination, as as RoutableBackendSession, at as RoutableSandbox, au as RoutingBackendRecoveryRequiredError, av as RoutingMutationOutcomeUnknownError, aw as
|
|
4
|
+
export { A as ActiveBackendResolverDeps, b as ActiveBackendUnresolvableError, c as ActivePointer, B as BackendUnresolvableCode, C as ChannelAConflictError, d as ChannelAEmitter, e as ChannelAExecArgs, f as ChannelAExecResult, g as ChannelANotFoundError, h as ChannelASession, i as ChannelAUnavailableError, j as ChannelAUnsupportedError, k as ChannelAValidationError, l as ControlRpc, D as DEFAULT_DESKTOP_GEOMETRY, m as DISPLAY_STACK_TIMEOUT_MS, n as DefaultBackendLossResult, o as DesktopGeometry, p as DisplayStackCallerKind, q as DisplayStackClassification, r as DisplayStackError, s as DisplayStackTelemetryContext, t as DisplayStackTelemetryEvent, u as DisplayStackTelemetryStatus, v as DisplayStackUnsupportedError, E as EnsureDisplayStackOptions, w as EnsureDisplayStackResult, x as EnsureTerminalServerOptions, y as EnsureTerminalServerResult, z as EstablishedSandboxSession, F as ExposeStreamPortInput, G as ExposeStreamPortResult, H as ExposedPortEndpoint, I as FAULT_FIELD_WHAT_HAPPENED, J as FAULT_FIELD_WHAT_PRESERVED, K as FAULT_FIELD_WHAT_TO_TRY, L as FAULT_FIELD_WHICH_LAYER, M as LiveModalSandboxLeaseAttribution, N as MaterializeRunCredentialsOptions, O as MintStreamTokenInput, P as MockAgentResponder, Q as MockAgentResponderOptions, S as MockExecHandler, T as ModalModuleLoader, U as ModalOrphanSweepResult, V as ModalOrphanSweepTermination, W as ModalSandboxAttribution, X as NEVER_SENT_DETAIL_KEY, Y as NatsControlRpc, Z as NatsOpStreamConnection, _ as NatsOpStreamTransport, $ as NatsRequestConnection, a0 as NegotiationContext, a1 as NormalizedRunCredentialMaterial, a2 as NumstatEntry, a3 as OP_STREAM_ACK_INTERVAL_MS, a4 as OP_STREAM_DEFAULT_WINDOW_BYTES, a5 as OP_STREAM_RECONNECT_HOLD_MS, a6 as OP_STREAM_SILENCE_TIMEOUT_MS, a7 as OpStreamExecOutcome, a8 as OpStreamJournal, a9 as OpStreamSubscription, aa as OpStreamTransport, ab as OpStreamUnavailableError, ac as PROVIDER_REGISTRY, ad as ProviderConstructionContext, ae as ProviderRegistration, af as ProviderSandboxFailure, ag as ProviderSandboxFailureKind, ah as REPOSITORY_DISCOVERY_LIMIT, ai as RecordingArtifactMetadata, aj as RecordingCodec, ak as RecordingContentType, al as RecordingError, am as RecordingProcess, an as RecordingUnavailableError, ao as RepositoryDiscoveryDegradedReason, ap as RepositoryDiscoveryResult, aq as ResolvedActiveBackend, ar as RevalidateModalOrphanTermination, as as RoutableBackendSession, at as RoutableSandbox, au as RoutingBackendRecoveryRequiredError, av as RoutingMutationOutcomeUnknownError, aw as RoutingMutationSettlementResult, ax as RoutingRetainedProcess, ay as RoutingRetainedProcessAdoption, az as RoutingRetainedProcessNotFoundError, aA as RoutingRetainedProcessTerminalProof, aB as RoutingSandboxSession, aC as RoutingSandboxSessionDeps, aD as RoutingTransitionEvent, aE as RoutingUnsupportedError, aF as RunCredentialCommandRunner, aG as RunCredentialCommandSession, aH as RunCredentialExpectedScope, R as RunCredentialSessionReady, aI as RunCredentialValidationError, aJ as SELFHOSTED_DEFAULT_TIMEOUT_MS, aK as SELFHOSTED_DRAINING_MAX_RETRIES, aL as SELFHOSTED_EXEC_DRAINING_MAX_RETRIES, aM as SELFHOSTED_IDEMPOTENT_READONLY_OPS, aN as SELFHOSTED_INFRASTRUCTURE_FAULT_CLASSES, aO as SELFHOSTED_NEVER_SENT_MAX_RETRIES, aP as SELFHOSTED_RECONNECT_WINDOW_MS, aQ as SELFHOSTED_RELAY_STREAM_PATH, aR as SELFHOSTED_RETRY_BACKOFF_BASE_MS, aS as SELFHOSTED_RETRY_BACKOFF_CAP_MS, aT as SELFHOSTED_RETRY_BACKOFF_FACTOR, aU as SELFHOSTED_TIMEOUT_MAX_RETRIES, aV as STREAM_PORT, aW as STREAM_TOKEN_DEFAULT_TTL_SECONDS, aX as SandboxChannelAService, aY as SandboxChannelAServiceOptions, aZ as SandboxConfigError, a_ as SandboxCreatedCallback, a$ as SandboxExecReadinessError, b0 as SandboxProviderUnavailableError, b1 as SandboxReplacementProviderStateError, b2 as SandboxResumeStateUnavailableError, b3 as SelfhostedApplyDiff, b4 as SelfhostedControlError, b5 as SelfhostedEditor, b6 as SelfhostedEnrollment, b7 as SelfhostedExecArgs, b8 as SelfhostedExecResult, b9 as SelfhostedImageOutput, ba as SelfhostedLivenessState, bb as SelfhostedNegotiationInput, bc as SelfhostedOpObservation, bd as SelfhostedOpObserver, be as SelfhostedOpStreamDeps, bf as SelfhostedRelayConfig, bg as SelfhostedRetryClock, bh as SelfhostedRetryDecision, bi as SelfhostedSandboxClient, bj as SelfhostedSession, bk as SelfhostedSessionBuild, bl as SelfhostedSessionDeps, bm as SelfhostedSessionState, bn as SelfhostedUnavailableReason, bo as StartRecordingInput, bp as StreamPortUnavailableError, bq as SwapTargetEstablishability, br as TERMINAL_SERVER_TIMEOUT_MS, bs as TerminalServerError, bt as TerminalServerUnsupportedError, bu as ToolspaceTokenPathError, bv as VerifiedWorkspaceArchive, bw as WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION, bx as WorkspaceArchiveDescriptor, by as WorkspaceArchiveIntegrityCode, bz as WorkspaceArchiveIntegrityError, bA as WorkspaceTreeFingerprint, bB as agentErrorToControlError, bC as assertDescriptorRegistryInvariants, bD as assertProviderRegistryInvariants, bE as assertSafeRelPath, bF as backendSupportsOs, bG as buildDisplayStackScript, bH as buildSelfhostedBackendSession, bI as buildStreamUrl, bJ as buildTerminalServerScript, bK as captureVerifiedWorkspaceArchive, bL as classifyProviderSandboxFailure, bM as clearRunCredentials, bN as clearRunCredentialsForAttempt, bO as contentTypeForCodec, bP as createSandboxClient, bQ as createSandboxClientForBackend, bR as decideSelfhostedRetry, bS as decodeModalSnapshotId, bT as defaultSelfhostedRetryClock, bU as deletePriorPersistedSnapshot, bV as deleteRecordingArtifacts, bW as deserializeSandboxSessionStateEnvelope, bX as desktopCapableBackend, bY as drainingExhaustedError, bZ as drainingMessage, b_ as ensureDisplayStack, b$ as ensureModalRegistryImage, c0 as ensureTerminalServer, c1 as establishSandboxSessionFromEnvelope, c2 as execDeadlineHint, c3 as exposeStreamPort, c4 as extForCodec, c5 as fingerprintSandboxWorkspace, c6 as hasPersistableSandboxProviderIdentity, c7 as inspectRecordingArtifact, c8 as isExecSessionLostBanner, c9 as isProviderSandboxGoneDuringRoutedOperation, ca as isProviderSandboxNotFoundError, cb as isProviderSandboxTransientError, cc as isSelfhostedProviderNotFoundError, cd as makeActiveBackendResolver, ce as materializeRunCredentials, cf as mintStreamToken, cg as modalSandboxAttributionEnvironment, ch as modalSandboxAttributionTags, ci as negotiateCapabilities, cj as negotiateSelfhostedCapabilities, ck as nextDurableOpId, cl as normalizeRunCredentialsResolution, cm as offlineAgentError, cn as offlineControlResponse, co as opAckSubject, cp as opFrameSubject, cq as parseExecBannerExitCode, cr as parseExecBannerSessionId, cs as parseNumstatZ, ct as parsePorcelainV2, cu as parseUnifiedPatch, cv as parseWorkspaceArchiveDescriptor, cw as payloadTooLargeMessage, cx as readVerifiedWorkspaceArchive, cy as readWorkspaceArchiveFromEnvelopeSessionState, cz as recordingStorageKey, cA as renderSelfhostedFault, cB as requirePersistableReplacementSandboxEnvelope, cC as restoredSandboxSessionStateFromEntry, cD as runCredentialPointerFile, cE as runCredentialRoot, cF as runWithToolCallCorrelation, cG as sandboxStateEntryFromRunState, cH as sanitizeOpIdToken, cI as selectBackend, cJ as selfhostedFaultClass, cK as selfhostedLiveness, cL as selfhostedRetryBackoffMs, cM as serializeEstablishedSandboxEnvelope, cN as serializeReplacementSandboxEnvelope, cO as setSelfhostedApplyDiff, cP as startRecording, cQ as stopRecording, cR as stripExecBanner, cS as subjectFor, cT as swapTargetEstablishability, cU as sweepModalOrphanSandboxes, cV as tagModalSandbox, cW as tearDownDisplayStack, cX as tearDownTerminalServer, cY as terminateModalSandboxById, cZ as timeoutAgentError, c_ as timeoutControlResponse, c$ as toolspaceTokenFileForSession, d0 as toolspaceTokenFileFromEnvironment, d1 as uploadRecordingArtifact, d2 as verifyRestoredWorkspace, d3 as verifySandboxExecReadiness, d4 as verifyStreamToken, d5 as withRunCredentialEnvironment, d6 as withRunCredentialsClient, d7 as withRunCredentialsSession, d8 as withToolspaceTokenClient, d9 as withToolspaceTokenEnvironment, da as withToolspaceTokenSession } from '../index-Ciehex_q.js';
|
|
5
5
|
import 'modal';
|
|
6
6
|
import '@opengeni/agent-proto';
|
package/dist/sandbox/index.js
CHANGED
|
@@ -170,7 +170,7 @@ import {
|
|
|
170
170
|
withToolspaceTokenClient,
|
|
171
171
|
withToolspaceTokenEnvironment,
|
|
172
172
|
withToolspaceTokenSession
|
|
173
|
-
} from "../chunk-
|
|
173
|
+
} from "../chunk-RHB7ETON.js";
|
|
174
174
|
import "../chunk-4YJ5Z52Z.js";
|
|
175
175
|
import "../chunk-K4V4QYCL.js";
|
|
176
176
|
import "../chunk-PZ5AY32C.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/runtime",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.10",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"@openai/agents-extensions": "0.13.3",
|
|
51
51
|
"@opengeni/agent-proto": "^0.3.0",
|
|
52
52
|
"@opengeni/codex": "^0.2.7",
|
|
53
|
-
"@opengeni/config": "^0.7.
|
|
54
|
-
"@opengeni/contracts": "^0.19.
|
|
53
|
+
"@opengeni/config": "^0.7.6",
|
|
54
|
+
"@opengeni/contracts": "^0.19.4",
|
|
55
55
|
"@opengeni/network": "^0.1.1",
|
|
56
56
|
"debug": "4.4.3",
|
|
57
57
|
"modal": "0.7.6",
|
package/src/codex-tool-search.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// Progressive
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Progressive MCP disclosure for the ChatGPT/Codex backend — parity with how the
|
|
2
|
+
// Codex CLI surfaces its ~217 `codex_apps` connector tools WITHOUT paying their
|
|
3
|
+
// schemas in every turn's context, while applying the same bounded search seam to
|
|
4
|
+
// other selected MCP servers.
|
|
4
5
|
//
|
|
5
|
-
// WHY. OpenGeni connects
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// disclosed back and become callable.
|
|
6
|
+
// WHY. OpenGeni connects MCP servers as CLIENTs and the SDK materializes every
|
|
7
|
+
// selected MCP tool as a `function` tool in request.tools[] on a Codex turn. The
|
|
8
|
+
// Codex CLI instead uses the backend's NATIVE tool-search: the model gets one
|
|
9
|
+
// compact search tool + deferred MCP tools (schemas dropped from model context),
|
|
10
|
+
// searches by capability, and only matched tools are disclosed back and become
|
|
11
|
+
// callable.
|
|
12
12
|
//
|
|
13
13
|
// PROVEN LIVE (Phase 0 probe against /codex/responses on gpt-5.6-sol): the backend
|
|
14
14
|
// HONORS `defer_loading:true` on function tools — 50 fat tools dropped input_tokens
|
|
@@ -16,19 +16,22 @@
|
|
|
16
16
|
// disclosed it (V3). The model emits `tool_search_call` on our slug.
|
|
17
17
|
//
|
|
18
18
|
// HOW. We wrap the agent's `getAllTools` (codex turns only, flag-gated): tag every
|
|
19
|
-
//
|
|
19
|
+
// non-mandatory MCP function tool `deferLoading = true` (converTool then serializes
|
|
20
20
|
// `defer_loading:true`, dropping the schema from context) and append one
|
|
21
21
|
// client-executed `toolSearchTool`. The SDK routes a `tool_search_call` to our
|
|
22
22
|
// executor (ClientToolSearchExecutor), which BM25-ranks the deferred pool and
|
|
23
|
-
// returns
|
|
24
|
-
//
|
|
25
|
-
// PrefixedMcpServer.callTool path (auth + name-sanitize + structuredContent
|
|
26
|
-
// all unchanged). Invocation is untouched; only the wire tool-set shrinks.
|
|
23
|
+
// returns matched tools BY REFERENCE — the SDK emits the `tool_search_output` that
|
|
24
|
+
// discloses them; the subsequent `function_call` resolves through the normal
|
|
25
|
+
// `PrefixedMcpServer.callTool` path (auth + name-sanitize + structuredContent
|
|
26
|
+
// inline all unchanged). Invocation is untouched; only the wire tool-set shrinks.
|
|
27
27
|
|
|
28
28
|
import { toolSearchTool, type Tool } from "@openai/agents";
|
|
29
29
|
import {
|
|
30
30
|
MCP_MAX_TOOL_DEFINITION_BYTES,
|
|
31
|
+
MCP_MAX_TOOL_SEARCH_DESCRIPTION_BYTES,
|
|
31
32
|
MCP_MAX_TOOL_SEARCH_DISCLOSURE_BYTES,
|
|
33
|
+
MCP_MAX_TOOL_SEARCH_SOURCE_LABEL_LENGTH,
|
|
34
|
+
MCP_MAX_TOOL_SEARCH_SOURCES,
|
|
32
35
|
mcpSerializedSizeBytes,
|
|
33
36
|
} from "./mcp-network";
|
|
34
37
|
|
|
@@ -50,6 +53,91 @@ export function isCodexAppsFunctionTool(
|
|
|
50
53
|
);
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
const MCP_TOOL_NAME_SEPARATOR = "__";
|
|
57
|
+
const MANDATORY_FIRST_PARTY_MCP_SERVER_ID = "opengeni";
|
|
58
|
+
const NO_MCP_SERVER_IDS: ReadonlySet<string> = new Set();
|
|
59
|
+
|
|
60
|
+
/** Return the authorized server id for a runtime MCP function tool name. */
|
|
61
|
+
function mcpServerIdForTool(tool: unknown, mcpServerIds: ReadonlySet<string>): string | null {
|
|
62
|
+
if (!tool || typeof tool !== "object") return null;
|
|
63
|
+
if ((tool as { type?: unknown }).type !== "function") return null;
|
|
64
|
+
const name = (tool as { name?: unknown }).name;
|
|
65
|
+
if (typeof name !== "string") return null;
|
|
66
|
+
let match: string | null = null;
|
|
67
|
+
for (const serverId of mcpServerIds) {
|
|
68
|
+
if (
|
|
69
|
+
typeof serverId !== "string" ||
|
|
70
|
+
serverId.length === 0 ||
|
|
71
|
+
!name.startsWith(`${serverId}${MCP_TOOL_NAME_SEPARATOR}`)
|
|
72
|
+
) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (match === null || serverId.length > match.length) {
|
|
76
|
+
match = serverId;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return match;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* True for non-mandatory MCP function tools on the effective agent surface.
|
|
84
|
+
* The prefix is the same runtime namespace used by PrefixedMcpServer; this
|
|
85
|
+
* intentionally does not inspect the global registry, connection metadata, or
|
|
86
|
+
* credentials, so search cannot widen the already-authorized tool set.
|
|
87
|
+
*/
|
|
88
|
+
export function isSearchableMcpFunctionTool(
|
|
89
|
+
tool: unknown,
|
|
90
|
+
mcpServerIds: ReadonlySet<string> = NO_MCP_SERVER_IDS,
|
|
91
|
+
): tool is Tool & { name: string; deferLoading?: boolean } {
|
|
92
|
+
const serverId = mcpServerIdForTool(tool, mcpServerIds);
|
|
93
|
+
return serverId !== null && serverId !== MANDATORY_FIRST_PARTY_MCP_SERVER_ID;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function truncateUtf8(value: string, maxBytes: number): string {
|
|
97
|
+
if (Buffer.byteLength(value) <= maxBytes) return value;
|
|
98
|
+
let output = "";
|
|
99
|
+
for (const character of value) {
|
|
100
|
+
const candidate = output + character;
|
|
101
|
+
if (Buffer.byteLength(candidate) > maxBytes) break;
|
|
102
|
+
output = candidate;
|
|
103
|
+
}
|
|
104
|
+
return output;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function searchSourceForTool(tool: Tool, mcpServerIds: ReadonlySet<string>): string | null {
|
|
108
|
+
const serverId = mcpServerIdForTool(tool, mcpServerIds);
|
|
109
|
+
return serverId && serverId !== MANDATORY_FIRST_PARTY_MCP_SERVER_ID
|
|
110
|
+
? truncateUtf8(serverId, MCP_MAX_TOOL_SEARCH_SOURCE_LABEL_LENGTH)
|
|
111
|
+
: null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function searchSourcesForTools(
|
|
115
|
+
connectorNamespaces: ReadonlySet<string>,
|
|
116
|
+
tools: readonly Tool[],
|
|
117
|
+
mcpServerIds: ReadonlySet<string>,
|
|
118
|
+
): Set<string> {
|
|
119
|
+
const sources = new Set<string>();
|
|
120
|
+
for (const source of connectorNamespaces) {
|
|
121
|
+
if (typeof source !== "string" || source.length === 0) continue;
|
|
122
|
+
if (sources.size >= MCP_MAX_TOOL_SEARCH_SOURCES) break;
|
|
123
|
+
sources.add(truncateUtf8(source, MCP_MAX_TOOL_SEARCH_SOURCE_LABEL_LENGTH));
|
|
124
|
+
}
|
|
125
|
+
for (const tool of tools) {
|
|
126
|
+
// Codex connector labels come from the transport's original namespace set
|
|
127
|
+
// (for example, `gmail`), not from the synthetic `codex_apps__` prefix.
|
|
128
|
+
// Keep that authoritative path intact; derive fallback labels only for
|
|
129
|
+
// directly materialized non-Codex MCP tools such as selected Slack tools.
|
|
130
|
+
const source =
|
|
131
|
+
isSearchableMcpFunctionTool(tool, mcpServerIds) && !isCodexAppsFunctionTool(tool)
|
|
132
|
+
? searchSourceForTool(tool, mcpServerIds)
|
|
133
|
+
: null;
|
|
134
|
+
if (!source || sources.has(source)) continue;
|
|
135
|
+
if (sources.size >= MCP_MAX_TOOL_SEARCH_SOURCES) break;
|
|
136
|
+
sources.add(source);
|
|
137
|
+
}
|
|
138
|
+
return sources;
|
|
139
|
+
}
|
|
140
|
+
|
|
53
141
|
// Minimal English stopword set: query phrasings like "send an email to someone"
|
|
54
142
|
// should match on capability words, not drown in glue words (parity with
|
|
55
143
|
// codex-rs, whose search normalizes tokens server-side).
|
|
@@ -211,11 +299,23 @@ export function renderSearchToolDescription(connectorNamespaces: ReadonlySet<str
|
|
|
211
299
|
"rather than guessing exact tool names. Returns the matching connector tools, which then become callable.";
|
|
212
300
|
const sources = Array.from(connectorNamespaces)
|
|
213
301
|
.filter((n) => typeof n === "string" && n.length > 0)
|
|
302
|
+
.slice(0, MCP_MAX_TOOL_SEARCH_SOURCES)
|
|
303
|
+
.map((source) => truncateUtf8(source, MCP_MAX_TOOL_SEARCH_SOURCE_LABEL_LENGTH))
|
|
304
|
+
.filter(Boolean)
|
|
214
305
|
.sort();
|
|
215
306
|
if (sources.length === 0) {
|
|
216
307
|
return `${base}\nConnected sources: none currently available.`;
|
|
217
308
|
}
|
|
218
|
-
|
|
309
|
+
const header = `${base}\nYou have access to tools from the following sources:`;
|
|
310
|
+
const lines: string[] = [];
|
|
311
|
+
for (const source of sources) {
|
|
312
|
+
const candidate = `${header}${[...lines, `- ${source}`].map((line) => `\n${line}`).join("")}`;
|
|
313
|
+
if (Buffer.byteLength(candidate) > MCP_MAX_TOOL_SEARCH_DESCRIPTION_BYTES) break;
|
|
314
|
+
lines.push(`- ${source}`);
|
|
315
|
+
}
|
|
316
|
+
return lines.length > 0
|
|
317
|
+
? `${header}\n${lines.join("\n")}`
|
|
318
|
+
: `${base}\nConnected sources: none currently available.`;
|
|
219
319
|
}
|
|
220
320
|
|
|
221
321
|
const SEARCH_TOOL_PARAMETERS = {
|
|
@@ -232,16 +332,21 @@ const SEARCH_TOOL_PARAMETERS = {
|
|
|
232
332
|
} as const;
|
|
233
333
|
|
|
234
334
|
/**
|
|
235
|
-
* The client tool-search executor: BM25 over the deferred
|
|
335
|
+
* The client tool-search executor: BM25 over the deferred MCP pool from the
|
|
236
336
|
* turn's live `availableTools`, returning matched tools BY REFERENCE (the only legal
|
|
237
337
|
* return — the SDK emits the disclosing `tool_search_output` and flips the loaded
|
|
238
338
|
* gate; returning copies would throw). Stateless — reads the pool per call.
|
|
239
339
|
*/
|
|
240
|
-
function codexToolSearchExecutor(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
340
|
+
function codexToolSearchExecutor(
|
|
341
|
+
args: {
|
|
342
|
+
availableTools?: Tool[];
|
|
343
|
+
toolCall?: { arguments?: unknown };
|
|
344
|
+
},
|
|
345
|
+
mcpServerIds: ReadonlySet<string>,
|
|
346
|
+
): Tool[] {
|
|
347
|
+
const deferred = (args.availableTools ?? []).filter((tool) =>
|
|
348
|
+
isSearchableMcpFunctionTool(tool, mcpServerIds),
|
|
349
|
+
);
|
|
245
350
|
if (deferred.length === 0) return [];
|
|
246
351
|
const { query, limit } = parseSearchArgs(args.toolCall?.arguments);
|
|
247
352
|
const ranked = bm25RankTools(deferred, query, limit);
|
|
@@ -320,20 +425,28 @@ function resolveSearchToolDescription(
|
|
|
320
425
|
}
|
|
321
426
|
|
|
322
427
|
/** Build the client-executed tool_search tool from an already-rendered description. */
|
|
323
|
-
function buildCodexToolSearchToolFromDescription(
|
|
428
|
+
function buildCodexToolSearchToolFromDescription(
|
|
429
|
+
description: string,
|
|
430
|
+
mcpServerIds: ReadonlySet<string>,
|
|
431
|
+
): Tool {
|
|
324
432
|
return toolSearchTool({
|
|
325
433
|
execution: "client",
|
|
326
434
|
description,
|
|
327
435
|
parameters: SEARCH_TOOL_PARAMETERS as unknown as Record<string, unknown>,
|
|
328
|
-
execute: codexToolSearchExecutor
|
|
436
|
+
execute: ((args: Parameters<typeof codexToolSearchExecutor>[0]) =>
|
|
437
|
+
codexToolSearchExecutor(args, mcpServerIds)) as never,
|
|
329
438
|
}) as unknown as Tool;
|
|
330
439
|
}
|
|
331
440
|
|
|
332
|
-
/** Build the client-executed tool_search tool that discloses
|
|
441
|
+
/** Build the client-executed tool_search tool that discloses deferred MCP tools on demand. */
|
|
333
442
|
export function buildCodexToolSearchTool(
|
|
334
443
|
connectorNamespaces: ReadonlySet<string> = NO_NAMESPACES,
|
|
444
|
+
mcpServerIds: ReadonlySet<string> = NO_MCP_SERVER_IDS,
|
|
335
445
|
): Tool {
|
|
336
|
-
return buildCodexToolSearchToolFromDescription(
|
|
446
|
+
return buildCodexToolSearchToolFromDescription(
|
|
447
|
+
renderSearchToolDescription(connectorNamespaces),
|
|
448
|
+
mcpServerIds,
|
|
449
|
+
);
|
|
337
450
|
}
|
|
338
451
|
|
|
339
452
|
/** True for the built-in tool_search tool (so we never add a second one). */
|
|
@@ -343,15 +456,15 @@ function isToolSearchTool(tool: unknown): boolean {
|
|
|
343
456
|
}
|
|
344
457
|
|
|
345
458
|
/**
|
|
346
|
-
* The transform applied to a turn's resolved tool list: tag every
|
|
347
|
-
*
|
|
459
|
+
* The transform applied to a turn's resolved tool list: tag every searchable
|
|
460
|
+
* non-mandatory MCP function tool `deferLoading = true`, and — unless one is already
|
|
348
461
|
* present — append the client tool_search tool. Pure (mutates the passed tools +
|
|
349
462
|
* returns the possibly-extended array); the SDK's `getTools` gate requires a
|
|
350
463
|
* tool_search whenever a deferred tool is present, which appending here satisfies
|
|
351
464
|
* in the same request.
|
|
352
465
|
*
|
|
353
466
|
* The search tool is appended UNCONDITIONALLY (even when the turn has no
|
|
354
|
-
*
|
|
467
|
+
* searchable MCP tools, e.g. a best-effort connector was dropped this
|
|
355
468
|
* turn): a prior turn's history may carry tool_search_call/output items, and
|
|
356
469
|
* replaying those without a tool_search tool in the request risks a backend
|
|
357
470
|
* reject; a search over an empty pool simply discloses nothing. The description
|
|
@@ -362,10 +475,11 @@ export function applyCodexToolSearch(
|
|
|
362
475
|
tools: Tool[],
|
|
363
476
|
connectorNamespaces: ReadonlySet<string> = NO_NAMESPACES,
|
|
364
477
|
descriptionFreeze?: CodexToolSearchDescriptionFreeze,
|
|
478
|
+
mcpServerIds: ReadonlySet<string> = NO_MCP_SERVER_IDS,
|
|
365
479
|
): Tool[] {
|
|
366
480
|
for (const tool of tools) {
|
|
367
481
|
if (
|
|
368
|
-
|
|
482
|
+
isSearchableMcpFunctionTool(tool, mcpServerIds) &&
|
|
369
483
|
(tool as { deferLoading?: boolean }).deferLoading !== true
|
|
370
484
|
) {
|
|
371
485
|
(tool as { deferLoading?: boolean }).deferLoading = true;
|
|
@@ -376,8 +490,11 @@ export function applyCodexToolSearch(
|
|
|
376
490
|
}
|
|
377
491
|
// AM-8: render through the per-turn freeze so the tools-block prefix stays
|
|
378
492
|
// byte-stable across a turn's model calls once connectors are discovered.
|
|
379
|
-
const description = resolveSearchToolDescription(
|
|
380
|
-
|
|
493
|
+
const description = resolveSearchToolDescription(
|
|
494
|
+
searchSourcesForTools(connectorNamespaces, tools, mcpServerIds),
|
|
495
|
+
descriptionFreeze,
|
|
496
|
+
);
|
|
497
|
+
return [...tools, buildCodexToolSearchToolFromDescription(description, mcpServerIds)];
|
|
381
498
|
}
|
|
382
499
|
|
|
383
500
|
type CloneCapableAgent = {
|
|
@@ -386,11 +503,11 @@ type CloneCapableAgent = {
|
|
|
386
503
|
};
|
|
387
504
|
|
|
388
505
|
/**
|
|
389
|
-
* Install progressive
|
|
506
|
+
* Install progressive MCP disclosure on a Codex-path agent by wrapping
|
|
390
507
|
* `getAllTools` so every per-model-call tool resolution runs
|
|
391
508
|
* {@link applyCodexToolSearch}. Idempotent-per-call (re-tags each
|
|
392
509
|
* freshly-materialized MCP tool under cacheToolsList:false). Gated by the caller
|
|
393
|
-
* (flag +
|
|
510
|
+
* (flag + Codex path).
|
|
394
511
|
*
|
|
395
512
|
* CLONE SURVIVAL (the part that makes this work on the REAL sandbox path): the
|
|
396
513
|
* SDK's sandbox runtime routes EVERY model call through
|
|
@@ -408,17 +525,19 @@ type CloneCapableAgent = {
|
|
|
408
525
|
export function installCodexToolSearch(
|
|
409
526
|
agent: CloneCapableAgent,
|
|
410
527
|
connectorNamespaces: ReadonlySet<string> = NO_NAMESPACES,
|
|
528
|
+
mcpServerIds: ReadonlySet<string> = NO_MCP_SERVER_IDS,
|
|
411
529
|
): void {
|
|
412
530
|
// ONE freeze cell per install (i.e. per turn), shared BY REFERENCE into every
|
|
413
531
|
// clone re-install below so the whole turn — the built agent and every clone the
|
|
414
532
|
// sandbox runtime resolves tools on — agrees on the same frozen description
|
|
415
533
|
// (AM-8). Created here, at the top-level install, never per clone.
|
|
416
|
-
installCodexToolSearchWithFreeze(agent, connectorNamespaces, { value: null });
|
|
534
|
+
installCodexToolSearchWithFreeze(agent, connectorNamespaces, mcpServerIds, { value: null });
|
|
417
535
|
}
|
|
418
536
|
|
|
419
537
|
function installCodexToolSearchWithFreeze(
|
|
420
538
|
agent: CloneCapableAgent,
|
|
421
539
|
connectorNamespaces: ReadonlySet<string>,
|
|
540
|
+
mcpServerIds: ReadonlySet<string>,
|
|
422
541
|
descriptionFreeze: CodexToolSearchDescriptionFreeze,
|
|
423
542
|
): void {
|
|
424
543
|
const originalGetAllTools = agent.getAllTools.bind(agent);
|
|
@@ -427,12 +546,18 @@ function installCodexToolSearchWithFreeze(
|
|
|
427
546
|
await originalGetAllTools(runContext),
|
|
428
547
|
connectorNamespaces,
|
|
429
548
|
descriptionFreeze,
|
|
549
|
+
mcpServerIds,
|
|
430
550
|
)) as typeof agent.getAllTools;
|
|
431
551
|
const originalClone = agent.clone?.bind(agent);
|
|
432
552
|
if (originalClone) {
|
|
433
553
|
const cloneWithToolSearch: NonNullable<CloneCapableAgent["clone"]> = (config: unknown) => {
|
|
434
554
|
const cloned = originalClone(config);
|
|
435
|
-
installCodexToolSearchWithFreeze(
|
|
555
|
+
installCodexToolSearchWithFreeze(
|
|
556
|
+
cloned,
|
|
557
|
+
connectorNamespaces,
|
|
558
|
+
mcpServerIds,
|
|
559
|
+
descriptionFreeze,
|
|
560
|
+
);
|
|
436
561
|
return cloned;
|
|
437
562
|
};
|
|
438
563
|
agent.clone = cloneWithToolSearch;
|