@kodelyth/codex 2026.5.42 → 2026.6.1

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 (138) hide show
  1. package/package.json +16 -1
  2. package/doctor-contract-api.test.ts +0 -44
  3. package/doctor-contract-api.ts +0 -68
  4. package/harness.ts +0 -72
  5. package/index.test.ts +0 -230
  6. package/index.ts +0 -66
  7. package/media-understanding-provider.test.ts +0 -486
  8. package/media-understanding-provider.ts +0 -521
  9. package/prompt-overlay-runtime-contract.test.ts +0 -48
  10. package/prompt-overlay.ts +0 -21
  11. package/provider-catalog.ts +0 -83
  12. package/provider-discovery.ts +0 -45
  13. package/provider.test.ts +0 -384
  14. package/provider.ts +0 -243
  15. package/src/app-server/app-inventory-cache.test.ts +0 -176
  16. package/src/app-server/app-inventory-cache.ts +0 -324
  17. package/src/app-server/approval-bridge.test.ts +0 -1471
  18. package/src/app-server/approval-bridge.ts +0 -1211
  19. package/src/app-server/auth-bridge.test.ts +0 -1449
  20. package/src/app-server/auth-bridge.ts +0 -614
  21. package/src/app-server/auth-profile-runtime-contract.test.ts +0 -239
  22. package/src/app-server/capabilities.ts +0 -27
  23. package/src/app-server/client-factory.ts +0 -24
  24. package/src/app-server/client.test.ts +0 -563
  25. package/src/app-server/client.ts +0 -715
  26. package/src/app-server/compact.test.ts +0 -710
  27. package/src/app-server/compact.ts +0 -500
  28. package/src/app-server/computer-use.test.ts +0 -788
  29. package/src/app-server/computer-use.ts +0 -683
  30. package/src/app-server/config.test.ts +0 -879
  31. package/src/app-server/config.ts +0 -1038
  32. package/src/app-server/context-engine-projection.test.ts +0 -252
  33. package/src/app-server/context-engine-projection.ts +0 -403
  34. package/src/app-server/delivery-no-reply-runtime-contract.test.ts +0 -80
  35. package/src/app-server/dynamic-tool-diagnostics.ts +0 -73
  36. package/src/app-server/dynamic-tool-profile.ts +0 -69
  37. package/src/app-server/dynamic-tools.test.ts +0 -1302
  38. package/src/app-server/dynamic-tools.ts +0 -623
  39. package/src/app-server/elicitation-bridge.test.ts +0 -1056
  40. package/src/app-server/elicitation-bridge.ts +0 -783
  41. package/src/app-server/event-projector.test.ts +0 -2668
  42. package/src/app-server/event-projector.ts +0 -2057
  43. package/src/app-server/image-payload-sanitizer.test.ts +0 -49
  44. package/src/app-server/image-payload-sanitizer.ts +0 -167
  45. package/src/app-server/klaw-owned-tool-runtime-contract.test.ts +0 -456
  46. package/src/app-server/local-runtime-attribution.ts +0 -39
  47. package/src/app-server/managed-binary.test.ts +0 -139
  48. package/src/app-server/managed-binary.ts +0 -193
  49. package/src/app-server/models.test.ts +0 -246
  50. package/src/app-server/models.ts +0 -172
  51. package/src/app-server/native-hook-relay.test.ts +0 -271
  52. package/src/app-server/native-hook-relay.ts +0 -150
  53. package/src/app-server/native-subagent-task-mirror.test.ts +0 -573
  54. package/src/app-server/native-subagent-task-mirror.ts +0 -497
  55. package/src/app-server/outcome-fallback-runtime-contract.test.ts +0 -404
  56. package/src/app-server/plugin-activation.test.ts +0 -336
  57. package/src/app-server/plugin-activation.ts +0 -283
  58. package/src/app-server/plugin-app-cache-key.ts +0 -74
  59. package/src/app-server/plugin-approval-roundtrip.ts +0 -122
  60. package/src/app-server/plugin-inventory.test.ts +0 -355
  61. package/src/app-server/plugin-inventory.ts +0 -357
  62. package/src/app-server/plugin-thread-config.test.ts +0 -865
  63. package/src/app-server/plugin-thread-config.ts +0 -455
  64. package/src/app-server/protocol-generated/json/DynamicToolCallParams.json +0 -33
  65. package/src/app-server/protocol-generated/json/v2/ErrorNotification.json +0 -199
  66. package/src/app-server/protocol-generated/json/v2/GetAccountResponse.json +0 -102
  67. package/src/app-server/protocol-generated/json/v2/ModelListResponse.json +0 -227
  68. package/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json +0 -2630
  69. package/src/app-server/protocol-generated/json/v2/ThreadStartResponse.json +0 -2630
  70. package/src/app-server/protocol-generated/json/v2/TurnCompletedNotification.json +0 -1659
  71. package/src/app-server/protocol-generated/json/v2/TurnStartResponse.json +0 -1655
  72. package/src/app-server/protocol-validators.test.ts +0 -75
  73. package/src/app-server/protocol-validators.ts +0 -203
  74. package/src/app-server/protocol.ts +0 -520
  75. package/src/app-server/rate-limit-cache.ts +0 -48
  76. package/src/app-server/rate-limits.test.ts +0 -202
  77. package/src/app-server/rate-limits.ts +0 -583
  78. package/src/app-server/request.ts +0 -73
  79. package/src/app-server/run-attempt.context-engine.test.ts +0 -1004
  80. package/src/app-server/run-attempt.test.ts +0 -9477
  81. package/src/app-server/run-attempt.ts +0 -4683
  82. package/src/app-server/run-attempt.vision-tools.test.ts +0 -35
  83. package/src/app-server/schema-normalization-runtime-contract.test.ts +0 -206
  84. package/src/app-server/session-binding.test.ts +0 -303
  85. package/src/app-server/session-binding.ts +0 -398
  86. package/src/app-server/session-history.ts +0 -44
  87. package/src/app-server/shared-client.test.ts +0 -589
  88. package/src/app-server/shared-client.ts +0 -289
  89. package/src/app-server/side-question.test.ts +0 -1175
  90. package/src/app-server/side-question.ts +0 -1007
  91. package/src/app-server/test-support.ts +0 -48
  92. package/src/app-server/thread-lifecycle.test.ts +0 -447
  93. package/src/app-server/thread-lifecycle.ts +0 -939
  94. package/src/app-server/thread-lifecycle.user-mcp-servers.test.ts +0 -442
  95. package/src/app-server/timeout.ts +0 -9
  96. package/src/app-server/tool-progress-normalization.ts +0 -77
  97. package/src/app-server/trajectory.test.ts +0 -205
  98. package/src/app-server/trajectory.ts +0 -365
  99. package/src/app-server/transcript-mirror.test.ts +0 -524
  100. package/src/app-server/transcript-mirror.ts +0 -208
  101. package/src/app-server/transcript-repair-runtime-contract.test.ts +0 -44
  102. package/src/app-server/transport-stdio.test.ts +0 -171
  103. package/src/app-server/transport-stdio.ts +0 -107
  104. package/src/app-server/transport-websocket.test.ts +0 -69
  105. package/src/app-server/transport-websocket.ts +0 -90
  106. package/src/app-server/transport.ts +0 -117
  107. package/src/app-server/user-input-bridge.test.ts +0 -249
  108. package/src/app-server/user-input-bridge.ts +0 -316
  109. package/src/app-server/version.ts +0 -4
  110. package/src/app-server/vision-tools.ts +0 -12
  111. package/src/command-account.ts +0 -544
  112. package/src/command-formatters.ts +0 -425
  113. package/src/command-handlers.ts +0 -2004
  114. package/src/command-rpc.test.ts +0 -16
  115. package/src/command-rpc.ts +0 -142
  116. package/src/commands.test.ts +0 -3312
  117. package/src/commands.ts +0 -65
  118. package/src/conversation-binding-data.ts +0 -124
  119. package/src/conversation-binding.test.ts +0 -599
  120. package/src/conversation-binding.ts +0 -561
  121. package/src/conversation-control.test.ts +0 -126
  122. package/src/conversation-control.ts +0 -303
  123. package/src/conversation-turn-collector.test.ts +0 -191
  124. package/src/conversation-turn-collector.ts +0 -186
  125. package/src/conversation-turn-input.test.ts +0 -141
  126. package/src/conversation-turn-input.ts +0 -106
  127. package/src/manifest.test.ts +0 -20
  128. package/src/migration/apply.ts +0 -501
  129. package/src/migration/helpers.ts +0 -55
  130. package/src/migration/plan.ts +0 -461
  131. package/src/migration/provider.test.ts +0 -1741
  132. package/src/migration/provider.ts +0 -41
  133. package/src/migration/source.ts +0 -643
  134. package/src/migration/targets.ts +0 -25
  135. package/src/node-cli-sessions.test.ts +0 -180
  136. package/src/node-cli-sessions.ts +0 -711
  137. package/test-api.ts +0 -82
  138. package/tsconfig.json +0 -16
@@ -1,783 +0,0 @@
1
- import {
2
- embeddedAgentLog,
3
- type EmbeddedRunAttemptParams,
4
- } from "klaw/plugin-sdk/agent-harness-runtime";
5
- import { formatCodexDisplayText } from "../command-formatters.js";
6
- import {
7
- approvalRequestExplicitlyUnavailable,
8
- mapExecDecisionToOutcome,
9
- requestPluginApproval,
10
- type AppServerApprovalOutcome,
11
- waitForPluginApprovalDecision,
12
- } from "./plugin-approval-roundtrip.js";
13
- import type {
14
- PluginAppPolicyContext,
15
- PluginAppPolicyContextEntry,
16
- } from "./plugin-thread-config.js";
17
- import { isJsonObject, type JsonObject, type JsonValue } from "./protocol.js";
18
-
19
- type ApprovalPropertyContext = {
20
- name: string;
21
- schema: JsonObject;
22
- required: boolean;
23
- };
24
-
25
- type BridgeableApprovalElicitation = {
26
- title: string;
27
- description: string;
28
- requestedSchema: JsonObject;
29
- meta: JsonObject;
30
- };
31
-
32
- type PluginElicitationResolution =
33
- | { kind: "not_plugin" }
34
- | { kind: "matched"; entry: PluginAppPolicyContextEntry }
35
- | { kind: "decline"; reason: string };
36
-
37
- const MCP_TOOL_APPROVAL_KIND = "mcp_tool_call";
38
- const MCP_TOOL_APPROVAL_KIND_KEY = "codex_approval_kind";
39
- const MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY = "connector_name";
40
- const MCP_TOOL_APPROVAL_TOOL_TITLE_KEY = "tool_title";
41
- const MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY = "tool_description";
42
- const MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY = "tool_params_display";
43
- const MCP_TOOL_APPROVAL_SOURCE_KEY = "source";
44
- const MCP_TOOL_APPROVAL_CONNECTOR_SOURCE = "connector";
45
- const CODEX_APPS_SERVER_NAME = "codex_apps";
46
- const PLUGIN_APP_ID_META_KEYS = ["app_id", "appId", "codex_app_id", "codexAppId"];
47
- const PLUGIN_CONNECTOR_ID_META_KEYS = ["connector_id", "connectorId"];
48
- const PLUGIN_NAME_META_KEYS = ["plugin_name", "pluginName", "codex_plugin_name", "codexPluginName"];
49
- const PLUGIN_CONFIG_KEY_META_KEYS = ["config_key", "configKey", "codex_config_key"];
50
- const PLUGIN_MARKETPLACE_NAME_META_KEYS = [
51
- "marketplace_name",
52
- "marketplaceName",
53
- "codex_marketplace_name",
54
- "codexMarketplaceName",
55
- ];
56
- const MAX_DISPLAY_PARAM_ENTRIES = 8;
57
- const MAX_DISPLAY_PARAM_VALUE_LENGTH = 120;
58
- const MAX_DISPLAY_VALUE_ARRAY_ITEMS = 8;
59
- const MAX_DISPLAY_VALUE_OBJECT_KEYS = 8;
60
- const MAX_DISPLAY_VALUE_DEPTH = 3;
61
- const DISPLAY_TEXT_SCAN_MAX_LENGTH = 4096;
62
- const ANSI_OSC_SEQUENCE_RE = new RegExp(
63
- String.raw`(?:\u001b]|\u009d)[^\u001b\u009c\u0007]*(?:\u0007|\u001b\\|\u009c)`,
64
- "g",
65
- );
66
- const ANSI_CONTROL_SEQUENCE_RE = new RegExp(
67
- String.raw`(?:\u001b\[[0-?]*[ -/]*[@-~]|\u009b[0-?]*[ -/]*[@-~]|\u001b[@-Z\\-_])`,
68
- "g",
69
- );
70
- const CONTROL_CHARACTER_RE = new RegExp(String.raw`[\u0000-\u001f\u007f-\u009f]+`, "g");
71
- const INVISIBLE_FORMATTING_CONTROL_RE = new RegExp(
72
- String.raw`[\u00ad\u034f\u061c\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff\ufe00-\ufe0f\u{e0100}-\u{e01ef}]`,
73
- "gu",
74
- );
75
- const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE = new RegExp(
76
- String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`,
77
- );
78
-
79
- export async function handleCodexAppServerElicitationRequest(params: {
80
- requestParams: JsonValue | undefined;
81
- paramsForRun: EmbeddedRunAttemptParams;
82
- threadId: string;
83
- turnId: string;
84
- pluginAppPolicyContext?: PluginAppPolicyContext;
85
- signal?: AbortSignal;
86
- }): Promise<JsonValue | undefined> {
87
- const requestParams = isJsonObject(params.requestParams) ? params.requestParams : undefined;
88
- if (!requestParams) {
89
- return undefined;
90
- }
91
- if (!matchesCurrentThread(requestParams, params.threadId)) {
92
- return undefined;
93
- }
94
- if (turnIdMismatches(requestParams, params.turnId)) {
95
- return undefined;
96
- }
97
- const pluginResolution = resolvePluginElicitation({
98
- requestParams,
99
- pluginAppPolicyContext: params.pluginAppPolicyContext,
100
- });
101
- if (pluginResolution.kind !== "not_plugin") {
102
- if (pluginResolution.kind === "decline") {
103
- logPluginElicitationDecline(pluginResolution.reason, requestParams);
104
- return declineElicitationResponse();
105
- }
106
- if (!hasExactTurnId(requestParams, params.turnId)) {
107
- logPluginElicitationDecline("missing_active_turn", requestParams);
108
- return declineElicitationResponse();
109
- }
110
- return buildPluginPolicyElicitationResponse(pluginResolution.entry, requestParams);
111
- }
112
-
113
- const approvalPrompt = readBridgeableApprovalElicitation(requestParams);
114
- if (!approvalPrompt) {
115
- return undefined;
116
- }
117
-
118
- const outcome = await requestPluginApprovalOutcome({
119
- paramsForRun: params.paramsForRun,
120
- title: approvalPrompt.title,
121
- description: approvalPrompt.description,
122
- signal: params.signal,
123
- });
124
- return buildElicitationResponse(approvalPrompt.requestedSchema, approvalPrompt.meta, outcome);
125
- }
126
-
127
- function matchesCurrentThread(requestParams: JsonObject | undefined, threadId: string): boolean {
128
- if (!requestParams) {
129
- return false;
130
- }
131
- const requestThreadId = readString(requestParams, "threadId");
132
- return requestThreadId === threadId;
133
- }
134
-
135
- function turnIdMismatches(requestParams: JsonObject | undefined, turnId: string): boolean {
136
- const rawTurnId = requestParams?.turnId;
137
- return rawTurnId !== null && rawTurnId !== undefined && rawTurnId !== turnId;
138
- }
139
-
140
- function hasExactTurnId(requestParams: JsonObject | undefined, turnId: string): boolean {
141
- return requestParams?.turnId === turnId;
142
- }
143
-
144
- function resolvePluginElicitation(params: {
145
- requestParams: JsonObject | undefined;
146
- pluginAppPolicyContext?: PluginAppPolicyContext;
147
- }): PluginElicitationResolution {
148
- const requestParams = params.requestParams;
149
- if (!requestParams) {
150
- return { kind: "not_plugin" };
151
- }
152
- const meta = isJsonObject(requestParams["_meta"]) ? requestParams["_meta"] : {};
153
- const context = params.pluginAppPolicyContext;
154
- const entries = context ? Object.values(context.apps) : [];
155
-
156
- const appId =
157
- readFirstString(meta, PLUGIN_APP_ID_META_KEYS) ??
158
- readFirstString(requestParams, PLUGIN_APP_ID_META_KEYS);
159
- const connectorId = readFirstString(meta, PLUGIN_CONNECTOR_ID_META_KEYS);
160
- const isCodexConnectorApproval = isCodexConnectorApprovalElicitation(requestParams, meta);
161
- if (isCodexConnectorApproval && appId && connectorId && appId !== connectorId) {
162
- return { kind: "decline", reason: "app_id_connector_id_mismatch" };
163
- }
164
- if (appId) {
165
- if (!context) {
166
- return { kind: "decline", reason: "missing_policy_context" };
167
- }
168
- const entry = context.apps[appId];
169
- return uniquePluginMatch(entry ? [entry] : [], "app_id");
170
- }
171
- if (isCodexConnectorApproval && connectorId) {
172
- if (!context) {
173
- return { kind: "decline", reason: "missing_policy_context" };
174
- }
175
- const entry = context.apps[connectorId];
176
- return uniquePluginMatch(entry ? [entry] : [], "connector_id");
177
- }
178
-
179
- const serverName = readString(requestParams, "serverName");
180
- if (serverName && context) {
181
- const matches = entries.filter((entry) => entry.mcpServerNames.includes(serverName));
182
- if (matches.length > 0) {
183
- return uniquePluginMatch(matches, "server_name");
184
- }
185
- }
186
-
187
- const metadataResolution = resolvePluginStableMetadataMatch({
188
- meta,
189
- requestParams,
190
- entries,
191
- context,
192
- });
193
- if (metadataResolution.kind !== "not_plugin") {
194
- return metadataResolution;
195
- }
196
-
197
- if (context && hasDisplayNameOnlyPluginMatch(meta, entries)) {
198
- return { kind: "decline", reason: "display_name_only" };
199
- }
200
-
201
- return { kind: "not_plugin" };
202
- }
203
-
204
- function isCodexConnectorApprovalElicitation(requestParams: JsonObject, meta: JsonObject): boolean {
205
- return (
206
- readString(requestParams, "serverName") === CODEX_APPS_SERVER_NAME &&
207
- readString(meta, MCP_TOOL_APPROVAL_KIND_KEY) === MCP_TOOL_APPROVAL_KIND &&
208
- readString(meta, MCP_TOOL_APPROVAL_SOURCE_KEY) === MCP_TOOL_APPROVAL_CONNECTOR_SOURCE
209
- );
210
- }
211
-
212
- function resolvePluginStableMetadataMatch(params: {
213
- meta: JsonObject;
214
- requestParams: JsonObject;
215
- entries: PluginAppPolicyContextEntry[];
216
- context?: PluginAppPolicyContext;
217
- }): PluginElicitationResolution {
218
- const pluginName =
219
- readFirstString(params.meta, PLUGIN_NAME_META_KEYS) ??
220
- readFirstString(params.requestParams, PLUGIN_NAME_META_KEYS);
221
- const configKey =
222
- readFirstString(params.meta, PLUGIN_CONFIG_KEY_META_KEYS) ??
223
- readFirstString(params.requestParams, PLUGIN_CONFIG_KEY_META_KEYS);
224
- const marketplaceName =
225
- readFirstString(params.meta, PLUGIN_MARKETPLACE_NAME_META_KEYS) ??
226
- readFirstString(params.requestParams, PLUGIN_MARKETPLACE_NAME_META_KEYS);
227
- if (!pluginName && !configKey) {
228
- return { kind: "not_plugin" };
229
- }
230
- if (!params.context) {
231
- return { kind: "decline", reason: "missing_policy_context" };
232
- }
233
- const matches = params.entries.filter((entry) => {
234
- if (marketplaceName && entry.marketplaceName !== marketplaceName) {
235
- return false;
236
- }
237
- if (pluginName && entry.pluginName !== pluginName) {
238
- return false;
239
- }
240
- if (configKey && entry.configKey !== configKey) {
241
- return false;
242
- }
243
- return true;
244
- });
245
- return uniquePluginMatch(matches, "metadata");
246
- }
247
-
248
- function uniquePluginMatch(
249
- matches: PluginAppPolicyContextEntry[],
250
- source: string,
251
- ): PluginElicitationResolution {
252
- if (matches.length === 1 && matches[0]) {
253
- return { kind: "matched", entry: matches[0] };
254
- }
255
- return {
256
- kind: "decline",
257
- reason: matches.length === 0 ? `${source}_not_enabled` : `${source}_ambiguous`,
258
- };
259
- }
260
-
261
- function hasDisplayNameOnlyPluginMatch(
262
- meta: JsonObject,
263
- entries: PluginAppPolicyContextEntry[],
264
- ): boolean {
265
- const connectorName = readString(meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY);
266
- if (!connectorName) {
267
- return false;
268
- }
269
- const normalized = normalizePluginIdentityText(connectorName);
270
- return entries.some(
271
- (entry) =>
272
- normalizePluginIdentityText(entry.pluginName) === normalized ||
273
- normalizePluginIdentityText(entry.configKey) === normalized,
274
- );
275
- }
276
-
277
- function normalizePluginIdentityText(value: string): string {
278
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
279
- }
280
-
281
- function buildPluginPolicyElicitationResponse(
282
- entry: PluginAppPolicyContextEntry,
283
- requestParams: JsonObject,
284
- ): JsonValue {
285
- if (!entry.allowDestructiveActions) {
286
- logPluginElicitationDecline("destructive_actions_disabled", requestParams);
287
- return declineElicitationResponse();
288
- }
289
- if (
290
- readString(requestParams, "mode") !== "form" ||
291
- !isJsonObject(requestParams.requestedSchema)
292
- ) {
293
- logPluginElicitationDecline("unsupported_schema", requestParams);
294
- return declineElicitationResponse();
295
- }
296
- const meta = isJsonObject(requestParams["_meta"]) ? requestParams["_meta"] : {};
297
- const response = buildElicitationResponse(requestParams.requestedSchema, meta, "approved-once");
298
- if (isJsonObject(response) && response.action === "accept") {
299
- return response;
300
- }
301
- logPluginElicitationDecline("unmappable_schema", requestParams);
302
- return declineElicitationResponse();
303
- }
304
-
305
- function declineElicitationResponse(): JsonValue {
306
- return { action: "decline", content: null, _meta: null };
307
- }
308
-
309
- function logPluginElicitationDecline(reason: string, requestParams: JsonObject | undefined): void {
310
- embeddedAgentLog.debug("codex plugin elicitation declined", {
311
- reason,
312
- serverName: readString(requestParams, "serverName"),
313
- mode: readString(requestParams, "mode"),
314
- });
315
- }
316
-
317
- function readBridgeableApprovalElicitation(
318
- requestParams: JsonObject | undefined,
319
- ): BridgeableApprovalElicitation | undefined {
320
- if (
321
- !requestParams ||
322
- readString(requestParams, "mode") !== "form" ||
323
- !isJsonObject(requestParams["_meta"]) ||
324
- requestParams["_meta"][MCP_TOOL_APPROVAL_KIND_KEY] !== MCP_TOOL_APPROVAL_KIND ||
325
- !isJsonObject(requestParams.requestedSchema)
326
- ) {
327
- return undefined;
328
- }
329
-
330
- const requestedSchema = requestParams.requestedSchema;
331
- if (
332
- readString(requestedSchema, "type") !== "object" ||
333
- !isJsonObject(requestedSchema.properties)
334
- ) {
335
- return undefined;
336
- }
337
-
338
- const title =
339
- sanitizeDisplayText(readString(requestParams, "message") ?? "") || "Codex MCP tool approval";
340
- return {
341
- title,
342
- description: buildApprovalDescription({
343
- title,
344
- meta: requestParams["_meta"],
345
- requestedSchema,
346
- serverName: sanitizeOptionalDisplayText(readString(requestParams, "serverName")),
347
- }),
348
- requestedSchema,
349
- meta: requestParams["_meta"],
350
- };
351
- }
352
-
353
- function buildApprovalDescription(params: {
354
- title: string;
355
- meta: JsonObject;
356
- requestedSchema: JsonObject;
357
- serverName: string | undefined;
358
- }): string {
359
- const connectorName = sanitizeOptionalDisplayText(
360
- readString(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY),
361
- );
362
- const toolTitle = sanitizeOptionalDisplayText(
363
- readString(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY),
364
- );
365
- const toolDescription = sanitizeOptionalDisplayText(
366
- readString(params.meta, MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY),
367
- );
368
- const summaryLines = [
369
- connectorName && `App: ${connectorName}`,
370
- toolTitle && `Tool: ${toolTitle}`,
371
- params.serverName && `MCP server: ${params.serverName}`,
372
- toolDescription,
373
- ].filter((line): line is string => Boolean(line));
374
- const paramLines = readDisplayParamLines(params.meta);
375
- const propertyLines = readPropertyDescriptionLines(params.requestedSchema);
376
- return [
377
- params.title,
378
- summaryLines.join("\n"),
379
- paramLines.length > 0 ? ["Parameters:", ...paramLines].join("\n") : "",
380
- propertyLines.length > 0 ? ["Fields:", ...propertyLines].join("\n") : "",
381
- ]
382
- .filter(Boolean)
383
- .join("\n\n");
384
- }
385
-
386
- function readPropertyDescriptionLines(requestedSchema: JsonObject): string[] {
387
- const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
388
- return Object.entries(properties)
389
- .map(([name, value]) => {
390
- const schema = isJsonObject(value) ? value : undefined;
391
- if (!schema) {
392
- return undefined;
393
- }
394
- const propTitle =
395
- sanitizeDisplayText(readString(schema, "title") ?? "") ||
396
- sanitizeDisplayText(name) ||
397
- "field";
398
- const description = sanitizeOptionalDisplayText(readString(schema, "description"));
399
- return description ? `- ${propTitle}: ${description}` : `- ${propTitle}`;
400
- })
401
- .filter((line): line is string => Boolean(line));
402
- }
403
-
404
- function readDisplayParamLines(meta: JsonObject): string[] {
405
- const displayParams = meta[MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY];
406
- if (!Array.isArray(displayParams)) {
407
- return [];
408
- }
409
- const lines = displayParams
410
- .slice(0, MAX_DISPLAY_PARAM_ENTRIES)
411
- .map((entry) => {
412
- const param = isJsonObject(entry) ? entry : undefined;
413
- if (!param) {
414
- return undefined;
415
- }
416
- const name =
417
- sanitizeOptionalDisplayText(readString(param, "display_name")) ??
418
- sanitizeOptionalDisplayText(readString(param, "name"));
419
- if (!name) {
420
- return undefined;
421
- }
422
- return `- ${name}: ${formatDisplayParamValue(param.value)}`;
423
- })
424
- .filter((line): line is string => Boolean(line));
425
- const remaining = displayParams.length - MAX_DISPLAY_PARAM_ENTRIES;
426
- return remaining > 0 ? [...lines, `- Additional parameters: ${remaining} more`] : lines;
427
- }
428
-
429
- function formatDisplayParamValue(value: JsonValue | undefined): string {
430
- const formatted = typeof value === "string" ? value : formatDisplayJsonValue(value ?? null);
431
- return truncateDisplayText(sanitizeDisplayText(formatted), MAX_DISPLAY_PARAM_VALUE_LENGTH);
432
- }
433
-
434
- function formatDisplayJsonValue(value: JsonValue, depth = MAX_DISPLAY_VALUE_DEPTH): string {
435
- if (value === null) {
436
- return "null";
437
- }
438
- if (typeof value === "string") {
439
- return JSON.stringify(truncateDisplayText(sanitizeDisplayText(value), 80));
440
- }
441
- if (typeof value === "number" || typeof value === "boolean") {
442
- return String(value);
443
- }
444
- if (Array.isArray(value)) {
445
- if (depth <= 0) {
446
- return "[truncated]";
447
- }
448
- const parts: string[] = [];
449
- const limit = Math.min(value.length, MAX_DISPLAY_VALUE_ARRAY_ITEMS);
450
- for (let i = 0; i < limit; i += 1) {
451
- parts.push(formatDisplayJsonValue(value[i] ?? null, depth - 1));
452
- }
453
- if (value.length > MAX_DISPLAY_VALUE_ARRAY_ITEMS) {
454
- parts.push("...");
455
- }
456
- return `[${parts.join(",")}]`;
457
- }
458
- if (typeof value === "object") {
459
- if (depth <= 0) {
460
- return "{truncated}";
461
- }
462
- const parts: string[] = [];
463
- let count = 0;
464
- let truncated = false;
465
- for (const key in value) {
466
- if (!Object.prototype.hasOwnProperty.call(value, key)) {
467
- continue;
468
- }
469
- if (count >= MAX_DISPLAY_VALUE_OBJECT_KEYS) {
470
- truncated = true;
471
- break;
472
- }
473
- const safeKey = truncateDisplayText(sanitizeDisplayText(key), 80);
474
- parts.push(
475
- `${JSON.stringify(safeKey)}:${formatDisplayJsonValue(value[key] ?? null, depth - 1)}`,
476
- );
477
- count += 1;
478
- }
479
- if (truncated) {
480
- parts.push("...");
481
- }
482
- return `{${parts.join(",")}}`;
483
- }
484
- return "null";
485
- }
486
-
487
- function sanitizeOptionalDisplayText(value: string | undefined): string | undefined {
488
- const sanitized = value === undefined ? "" : sanitizeDisplayText(value);
489
- return sanitized || undefined;
490
- }
491
-
492
- function sanitizeDisplayText(value: string): string {
493
- const scanned = value.slice(0, DISPLAY_TEXT_SCAN_MAX_LENGTH);
494
- const clipped = value.length > DISPLAY_TEXT_SCAN_MAX_LENGTH;
495
- const sanitized = scanned
496
- .replace(ANSI_OSC_SEQUENCE_RE, "")
497
- .replace(ANSI_CONTROL_SEQUENCE_RE, "")
498
- .replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE, "")
499
- .replace(INVISIBLE_FORMATTING_CONTROL_RE, " ")
500
- .replace(CONTROL_CHARACTER_RE, " ")
501
- .replace(/\s+/g, " ")
502
- .trim();
503
- const escaped = sanitized ? formatCodexDisplayText(sanitized) : "";
504
- return clipped && escaped ? `${escaped}...` : escaped;
505
- }
506
-
507
- function truncateDisplayText(value: string, maxLength: number): string {
508
- return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
509
- }
510
-
511
- async function requestPluginApprovalOutcome(params: {
512
- paramsForRun: EmbeddedRunAttemptParams;
513
- title: string;
514
- description: string;
515
- signal?: AbortSignal;
516
- }): Promise<AppServerApprovalOutcome> {
517
- try {
518
- const requestResult = await requestPluginApproval({
519
- paramsForRun: params.paramsForRun,
520
- title: params.title,
521
- description: params.description,
522
- severity: "warning",
523
- toolName: "codex_mcp_tool_approval",
524
- });
525
-
526
- const approvalId = requestResult?.id;
527
- if (!approvalId) {
528
- return "unavailable";
529
- }
530
-
531
- const decision = approvalRequestExplicitlyUnavailable(requestResult)
532
- ? null
533
- : await waitForPluginApprovalDecision({ approvalId, signal: params.signal });
534
- return mapExecDecisionToOutcome(decision);
535
- } catch {
536
- return params.signal?.aborted ? "cancelled" : "denied";
537
- }
538
- }
539
-
540
- function buildElicitationResponse(
541
- requestedSchema: JsonObject,
542
- meta: JsonObject,
543
- outcome: AppServerApprovalOutcome,
544
- ): JsonValue {
545
- if (outcome === "cancelled") {
546
- return { action: "cancel", content: null, _meta: null };
547
- }
548
- if (outcome === "denied" || outcome === "unavailable") {
549
- return { action: "decline", content: null, _meta: null };
550
- }
551
-
552
- const content = buildAcceptedContent(requestedSchema, meta, outcome);
553
- if (!content) {
554
- if (hasNoSchemaProperties(requestedSchema)) {
555
- return {
556
- action: "accept",
557
- content: null,
558
- _meta: buildAcceptedMeta(meta, outcome),
559
- };
560
- }
561
- embeddedAgentLog.warn("codex MCP approval elicitation approved without a mappable response", {
562
- approvalKind: meta[MCP_TOOL_APPROVAL_KIND_KEY],
563
- fields: Object.keys(requestedSchema.properties ?? {}),
564
- outcome,
565
- });
566
- return { action: "decline", content: null, _meta: null };
567
- }
568
- return { action: "accept", content, _meta: buildAcceptedMeta(meta, outcome) };
569
- }
570
-
571
- function buildAcceptedContent(
572
- requestedSchema: JsonObject,
573
- meta: JsonObject,
574
- outcome: AppServerApprovalOutcome,
575
- ): JsonObject | undefined {
576
- const properties = isJsonObject(requestedSchema.properties)
577
- ? requestedSchema.properties
578
- : undefined;
579
- if (!properties) {
580
- return undefined;
581
- }
582
- const required = Array.isArray(requestedSchema.required)
583
- ? new Set(
584
- requestedSchema.required.filter((entry): entry is string => typeof entry === "string"),
585
- )
586
- : new Set<string>();
587
- const content: JsonObject = {};
588
- let sawApprovalField = false;
589
-
590
- for (const [name, value] of Object.entries(properties)) {
591
- const schema = isJsonObject(value) ? value : undefined;
592
- if (!schema) {
593
- continue;
594
- }
595
- const property = { name, schema, required: required.has(name) };
596
- const next =
597
- readApprovalFieldValue(property, outcome) ??
598
- readPersistFieldValue(property, meta, outcome) ??
599
- readFallbackFieldValue(property, outcome);
600
-
601
- if (next === undefined) {
602
- if (isApprovalField(property)) {
603
- sawApprovalField = true;
604
- }
605
- if (property.required) {
606
- return undefined;
607
- }
608
- continue;
609
- }
610
-
611
- if (isApprovalField(property)) {
612
- sawApprovalField = true;
613
- }
614
- content[name] = next;
615
- }
616
-
617
- return sawApprovalField ? content : undefined;
618
- }
619
-
620
- function readApprovalFieldValue(
621
- property: ApprovalPropertyContext,
622
- outcome: AppServerApprovalOutcome,
623
- ): JsonValue | undefined {
624
- if (!isApprovalField(property)) {
625
- return undefined;
626
- }
627
- const type = readString(property.schema, "type");
628
- if (type === "boolean") {
629
- return true;
630
- }
631
- const options = readEnumOptions(property.schema);
632
- if (options.length === 0) {
633
- return undefined;
634
- }
635
-
636
- const sessionChoice = options.find((option) => isSessionApprovalOption(option));
637
- const acceptChoice = options.find((option) => isPositiveApprovalOption(option));
638
- if (outcome === "approved-session") {
639
- return sessionChoice?.value ?? acceptChoice?.value;
640
- }
641
- return acceptChoice?.value ?? sessionChoice?.value;
642
- }
643
-
644
- function readPersistFieldValue(
645
- property: ApprovalPropertyContext,
646
- meta: JsonObject,
647
- outcome: AppServerApprovalOutcome,
648
- ): JsonValue | undefined {
649
- if (!isPersistField(property) || outcome !== "approved-session") {
650
- return undefined;
651
- }
652
- const persistHints = readPersistHints(meta);
653
- const options = readEnumOptions(property.schema);
654
- if (options.length === 0) {
655
- return undefined;
656
- }
657
- const preferred = choosePersistHint(persistHints);
658
- if (preferred) {
659
- const match = options.find(
660
- (option) => option.value === preferred || option.label === preferred,
661
- );
662
- return match?.value;
663
- }
664
- return undefined;
665
- }
666
-
667
- function readDefaultValue(schema: JsonObject): JsonValue | undefined {
668
- return schema.default as JsonValue | undefined;
669
- }
670
-
671
- function readFallbackFieldValue(
672
- property: ApprovalPropertyContext,
673
- outcome: AppServerApprovalOutcome,
674
- ): JsonValue | undefined {
675
- if (outcome === "approved-once" && isPersistField(property)) {
676
- return undefined;
677
- }
678
- return readDefaultValue(property.schema);
679
- }
680
-
681
- function isApprovalField(property: ApprovalPropertyContext): boolean {
682
- const haystack = propertyText(property).toLowerCase();
683
- return /\b(approve|approval|allow|accept|decision)\b/.test(haystack);
684
- }
685
-
686
- function isPersistField(property: ApprovalPropertyContext): boolean {
687
- const haystack = propertyText(property).toLowerCase();
688
- return /\b(persist|session|always|scope)\b/.test(haystack);
689
- }
690
-
691
- function propertyText(property: ApprovalPropertyContext): string {
692
- return [
693
- property.name,
694
- readString(property.schema, "title"),
695
- readString(property.schema, "description"),
696
- ]
697
- .filter(Boolean)
698
- .join(" ");
699
- }
700
-
701
- function readPersistHints(meta: JsonObject): string[] {
702
- const raw = meta.persist;
703
- if (typeof raw === "string") {
704
- return [raw];
705
- }
706
- if (Array.isArray(raw)) {
707
- return raw.filter((entry): entry is string => typeof entry === "string");
708
- }
709
- return ["session", "always"];
710
- }
711
-
712
- function buildAcceptedMeta(meta: JsonObject, outcome: AppServerApprovalOutcome): JsonObject | null {
713
- if (outcome !== "approved-session") {
714
- return null;
715
- }
716
- const persist = choosePersistHint(readPersistHints(meta));
717
- return persist ? { persist } : null;
718
- }
719
-
720
- function choosePersistHint(persistHints: string[]): "always" | "session" | undefined {
721
- if (persistHints.includes("always")) {
722
- return "always";
723
- }
724
- if (persistHints.includes("session")) {
725
- return "session";
726
- }
727
- return undefined;
728
- }
729
-
730
- function hasNoSchemaProperties(requestedSchema: JsonObject): boolean {
731
- const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
732
- return Object.keys(properties).length === 0;
733
- }
734
-
735
- function readEnumOptions(schema: JsonObject): Array<{ value: string; label: string }> {
736
- if (Array.isArray(schema.enum)) {
737
- const values = schema.enum.filter((entry): entry is string => typeof entry === "string");
738
- const labels = Array.isArray(schema.enumNames)
739
- ? schema.enumNames.filter((entry): entry is string => typeof entry === "string")
740
- : [];
741
- return values.map((value, index) => ({ value, label: labels[index] ?? value }));
742
- }
743
- if (Array.isArray(schema.oneOf)) {
744
- return schema.oneOf
745
- .map((entry) => {
746
- const option = isJsonObject(entry) ? entry : undefined;
747
- const value = readString(option, "const");
748
- if (!value) {
749
- return undefined;
750
- }
751
- return { value, label: readString(option, "title") ?? value };
752
- })
753
- .filter((entry): entry is { value: string; label: string } => Boolean(entry));
754
- }
755
- return [];
756
- }
757
-
758
- function isPositiveApprovalOption(option: { value: string; label: string }): boolean {
759
- const haystack = `${option.value} ${option.label}`.toLowerCase();
760
- return /\b(allow|approve|accept|yes|continue|proceed|true)\b/.test(haystack);
761
- }
762
-
763
- function isSessionApprovalOption(option: { value: string; label: string }): boolean {
764
- const haystack = `${option.value} ${option.label}`.toLowerCase();
765
- return (
766
- /\b(session|always|persistent)\b/.test(haystack) && /\b(allow|approve|accept)\b/.test(haystack)
767
- );
768
- }
769
-
770
- function readString(record: JsonObject | undefined, key: string): string | undefined {
771
- const value = record?.[key];
772
- return typeof value === "string" && value.trim() ? value : undefined;
773
- }
774
-
775
- function readFirstString(record: JsonObject | undefined, keys: string[]): string | undefined {
776
- for (const key of keys) {
777
- const value = readString(record, key);
778
- if (value) {
779
- return value;
780
- }
781
- }
782
- return undefined;
783
- }