maka-agent 0.2.0-dev.46.20260922 → 0.2.0-dev.48.20260923

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 (131) hide show
  1. package/DISCLAIMER-WIP +6 -1
  2. package/README.md +6 -1
  3. package/dist/acp/maka-acp-agent.js +1 -1
  4. package/dist/acp/session-mcp.js +212 -0
  5. package/dist/acp/session-registry.js +52 -8
  6. package/dist/acp/stdio-server.js +3 -0
  7. package/dist/cli-core.js +1 -1
  8. package/dist/mcp-capability-provider.js +6 -1
  9. package/dist/mcp-capability-publication.js +139 -0
  10. package/dist/pi-tui-layout.js +46 -17
  11. package/dist/pi-tui-pickers.js +25 -14
  12. package/dist/pi-tui-runner.js +24 -15
  13. package/dist/runtime-host-capability-provider-command.js +25 -6
  14. package/dist/runtime-host-service-manager.js +26 -2
  15. package/dist/tui-context-refresh.js +15 -26
  16. package/dist/tui-mcp-control.js +66 -112
  17. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  18. package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
  19. package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
  20. package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
  21. package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
  22. package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
  23. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
  24. package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
  25. package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
  26. package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
  27. package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
  28. package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
  29. package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
  30. package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
  31. package/node_modules/@maka/core/dist/mcp.js +4 -1
  32. package/node_modules/@maka/core/dist/model-metadata.js +28 -1
  33. package/node_modules/@maka/core/dist/model-thinking.js +36 -1
  34. package/node_modules/@maka/core/dist/provider-registry.js +0 -1
  35. package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
  36. package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
  37. package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
  38. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
  39. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
  40. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
  41. package/node_modules/@maka/core/dist/session.js +19 -1
  42. package/node_modules/@maka/core/dist/settings.js +3 -0
  43. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
  44. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  45. package/node_modules/@maka/core/package.json +3 -0
  46. package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
  47. package/node_modules/@maka/mcp/dist/index.js +31 -5
  48. package/node_modules/@maka/mcp/dist/oauth.js +54 -6
  49. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
  50. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
  51. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
  52. package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
  53. package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
  54. package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
  55. package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
  56. package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
  57. package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
  58. package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
  59. package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
  60. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +122 -3
  61. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
  62. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
  63. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  64. package/node_modules/@maka/runtime/dist/session-manager.js +184 -126
  65. package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
  66. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
  67. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
  68. package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
  69. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
  70. package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
  71. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  72. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
  74. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
  75. package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
  76. package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
  77. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
  78. package/node_modules/@maka/runtime-host/dist/protocol/index.js +20 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
  80. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +75 -5
  81. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
  82. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
  83. package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
  84. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
  85. package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
  86. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
  87. package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
  88. package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
  89. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +188 -21
  90. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +6 -0
  91. package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
  92. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
  93. package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
  94. package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
  95. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
  96. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
  97. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +5 -1
  98. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
  99. package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
  100. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
  101. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
  102. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
  103. package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
  104. package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
  105. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
  106. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
  107. package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
  108. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +128 -13
  109. package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
  110. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
  111. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
  112. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
  113. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
  114. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
  115. package/node_modules/@maka/runtime-host/package.json +2 -0
  116. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
  117. package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
  118. package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
  119. package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
  120. package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
  121. package/node_modules/@maka/storage/dist/plan-store.js +30 -0
  122. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  123. package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
  124. package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
  125. package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
  126. package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
  127. package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
  128. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
  129. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
  130. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
  131. package/package.json +1 -1
@@ -6,6 +6,7 @@
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "exports": {
9
+ "./executor-catalog": "./dist/executor-catalog.js",
9
10
  "./executor-id": "./dist/executor-id.js",
10
11
  "./durable-tool-result-projection": "./dist/durable-tool-result-projection.js",
11
12
  "./model-projection-transition": "./dist/model-projection-transition.js",
@@ -29,6 +30,7 @@
29
30
  "./provider-retry-countdown": "./dist/provider-retry-countdown.js",
30
31
  "./interaction": "./dist/interaction.js",
31
32
  "./session": "./dist/session.js",
33
+ "./turn-origin": "./dist/turn-origin.js",
32
34
  "./session-reference": "./dist/session-reference.js",
33
35
  "./session-revisions": "./dist/session-revisions.js",
34
36
  "./collaboration": "./dist/collaboration.js",
@@ -133,6 +135,7 @@
133
135
  "./computer-use": "./dist/computer-use.js",
134
136
  "./connection-error-copy": "./dist/connection-error-copy.js",
135
137
  "./connection-usage": "./dist/connection-usage.js",
138
+ "./refresh-read-coordinator": "./dist/refresh-read-coordinator.js",
136
139
  "./git-review": "./dist/git-review.js",
137
140
  "./graph-command": "./dist/graph-command.js",
138
141
  "./project": "./dist/project.js",
@@ -44,7 +44,10 @@ export class McpCredentialCoordinator {
44
44
  *
45
45
  * The optional signal fences an ABANDONED erase: a logout whose round
46
46
  * timed out must not resume later, adopt whatever record a newer login
47
- * just stored as its basis, and tombstone the fresh tokens. */
47
+ * just stored as its basis, and tombstone the fresh tokens.
48
+ *
49
+ * `spare` must be decided here, on the lane's read: checked outside, a
50
+ * login could land between the check and the erase. */
48
51
  async erase(serverId, options = {}) {
49
52
  this.epochs.set(serverId, this.epoch(serverId) + 1);
50
53
  await this.run(serverId, async () => {
@@ -54,6 +57,8 @@ export class McpCredentialCoordinator {
54
57
  // storage read was in flight — committing past it would erase a
55
58
  // record the caller no longer owns.
56
59
  this.assertNotAbandoned(serverId, options.signal);
60
+ if (basis && options.spare?.(basis))
61
+ return;
57
62
  // Absence still gets a tombstone: a cross-process flow that captured
58
63
  // generation 0 on absence must not CAS its credentials back in after
59
64
  // this revocation.
@@ -17,7 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { randomBytes } from 'node:crypto';
20
- import { auth, Client, extractWWWAuthenticateParams, LATEST_PROTOCOL_VERSION, CLIENT_CAPABILITIES_META_KEY, isInputRequiredResult, SdkErrorCode, SdkHttpError, SSEClientTransport, StreamableHTTPClientTransport, } from '@modelcontextprotocol/client';
20
+ import { auth, validateAuthorizationResponseIssuer, Client, extractWWWAuthenticateParams, LATEST_PROTOCOL_VERSION, CLIENT_CAPABILITIES_META_KEY, isInputRequiredResult, SdkErrorCode, SdkHttpError, SSEClientTransport, StreamableHTTPClientTransport, } from '@modelcontextprotocol/client';
21
21
  import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
22
22
  import { setTimeout as delay } from 'node:timers/promises';
23
23
  import { containsMcpFormState, prepareMcpForm } from './form-elicitation.js';
@@ -34,7 +34,7 @@ import { McpToolCallPreparer } from './tool-output-validation.js';
34
34
  import { mapMcpToolProgress } from './tool-progress.js';
35
35
  import { McpCredentialCoordinator } from './credential-coordinator.js';
36
36
  import { assertTransportSecurity, urlProvenance, } from './transport-security.js';
37
- import { McpAuthRequiredError, McpOAuthProvider, } from './oauth.js';
37
+ import { McpAuthRequiredError, McpOAuthProvider, authorizationCallbackError, mcpOAuthRecordBoundTo, } from './oauth.js';
38
38
  export { formatMcpDiagnosticText } from './diagnostic-text.js';
39
39
  export { McpToolCallError } from './tool-call-error.js';
40
40
  export { createCredentialMcpOAuthStorage, } from './credential-oauth-storage.js';
@@ -259,7 +259,7 @@ export class McpClientManager {
259
259
  : undefined;
260
260
  if (owed) {
261
261
  try {
262
- await this.forgetAuthorization(serverId, owed);
262
+ await this.forgetAuthorization(serverId, owed, { successor: serverConfig });
263
263
  }
264
264
  catch (error) {
265
265
  await this.blockForCredentialCleanup(serverId, current, owed, error);
@@ -1272,7 +1272,6 @@ export class McpClientManager {
1272
1272
  }
1273
1273
  }
1274
1274
  async finishAuthorizationRound(serverId, callback, options = {}) {
1275
- const authorizationCode = callback.code;
1276
1275
  const { config } = this.requireRemoteEntry(serverId);
1277
1276
  // The immediate read doubles as the flow's generation/version pin.
1278
1277
  const storage = this.flowStorage(serverId, options.signal);
@@ -1298,6 +1297,25 @@ export class McpClientManager {
1298
1297
  if (record?.pendingServerUrl !== config.url) {
1299
1298
  throw new Error(`MCP server "${serverId}" changed its URL during authorization`);
1300
1299
  }
1300
+ const metadata = record.discovery?.authorizationServerMetadata;
1301
+ const expectedIssuer = metadata?.issuer ?? record.discovery?.authorizationServerUrl;
1302
+ if (!expectedIssuer)
1303
+ throw new Error('OAuth callback has no recorded issuer');
1304
+ try {
1305
+ validateAuthorizationResponseIssuer({
1306
+ iss: callback.iss,
1307
+ expectedIssuer: String(expectedIssuer),
1308
+ issParameterSupported: metadata?.authorization_response_iss_parameter_supported === true,
1309
+ });
1310
+ }
1311
+ catch {
1312
+ // SDK errors echo the untrusted iss parameter; it must not cross IPC.
1313
+ throw new Error('OAuth callback issuer validation failed');
1314
+ }
1315
+ if ('error' in callback) {
1316
+ throw authorizationCallbackError(callback.error);
1317
+ }
1318
+ const authorizationCode = callback.code;
1301
1319
  const provider = new McpOAuthProvider({
1302
1320
  serverId,
1303
1321
  serverUrl: config.url,
@@ -1432,7 +1450,15 @@ export class McpClientManager {
1432
1450
  return;
1433
1451
  if (config && isMcpStdioConfig(config) && !(await this.coordinator.read(serverId)))
1434
1452
  return;
1435
- await this.coordinator.erase(serverId, options);
1453
+ const { successor, ...eraseOptions } = options;
1454
+ await this.coordinator.erase(serverId, {
1455
+ ...eraseOptions,
1456
+ // Another process may have applied the same change and signed in first.
1457
+ ...(successor &&
1458
+ !isMcpStdioConfig(successor) && {
1459
+ spare: (record) => mcpOAuthRecordBoundTo(record, successor.url, successor.oauth),
1460
+ }),
1461
+ });
1436
1462
  }
1437
1463
  /** Drops stored tokens and registration, returning the server to needs-auth. */
1438
1464
  async clearAuthorization(serverId, options = {}) {
@@ -16,6 +16,25 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
+ import { createHash } from 'node:crypto';
20
+ const OAUTH_ERROR_CODES = new Set([
21
+ 'invalid_request',
22
+ 'unauthorized_client',
23
+ 'access_denied',
24
+ 'unsupported_response_type',
25
+ 'invalid_scope',
26
+ 'server_error',
27
+ 'temporarily_unavailable',
28
+ 'invalid_client',
29
+ 'invalid_grant',
30
+ 'unsupported_grant_type',
31
+ 'interaction_required',
32
+ 'login_required',
33
+ 'consent_required',
34
+ ]);
35
+ export function authorizationCallbackError(code) {
36
+ return new Error(`Authorization failed: ${OAUTH_ERROR_CODES.has(code) ? code : 'unknown_error'}`);
37
+ }
19
38
  export function createMemoryMcpOAuthStorage() {
20
39
  const records = new Map();
21
40
  return {
@@ -31,6 +50,16 @@ export function createMemoryMcpOAuthStorage() {
31
50
  },
32
51
  };
33
52
  }
53
+ function mcpOAuthClientConfigHash(config) {
54
+ if (!config?.clientId)
55
+ return undefined;
56
+ return createHash('sha256')
57
+ .update(JSON.stringify([config.issuer, config.clientId, config.clientSecret]))
58
+ .digest('hex');
59
+ }
60
+ export function mcpOAuthRecordBoundTo(record, serverUrl, config) {
61
+ return (record.serverUrl === serverUrl && record.clientConfigHash === mcpOAuthClientConfigHash(config));
62
+ }
34
63
  /** Thrown (via the SDK's UnauthorizedError path) when a background connect
35
64
  * would need the user in a browser. The manager maps it to `needs-auth`. */
36
65
  export class McpAuthRequiredError extends Error {
@@ -64,6 +93,9 @@ export class McpOAuthProvider {
64
93
  if (state)
65
94
  this.state = () => state;
66
95
  }
96
+ get clientConfigHash() {
97
+ return mcpOAuthClientConfigHash(this.options.config);
98
+ }
67
99
  get redirectUrl() {
68
100
  return this.options.interactive?.redirectUrl ?? BACKGROUND_REDIRECT_URL;
69
101
  }
@@ -82,10 +114,17 @@ export class McpOAuthProvider {
82
114
  : {}),
83
115
  };
84
116
  }
85
- async clientInformation() {
117
+ async clientInformation(context) {
86
118
  const configured = this.options.config;
87
119
  if (configured?.clientId) {
120
+ if (!configured.issuer) {
121
+ throw new Error(`MCP server "${this.options.serverId}" requires oauth.issuer for its pre-registered client`);
122
+ }
123
+ if (context && context.issuer !== configured.issuer) {
124
+ throw new Error(`MCP server "${this.options.serverId}" discovered a different OAuth issuer; reconfigure its client credentials`);
125
+ }
88
126
  return {
127
+ issuer: configured.issuer,
89
128
  client_id: configured.clientId,
90
129
  ...(configured.clientSecret ? { client_secret: configured.clientSecret } : {}),
91
130
  };
@@ -106,7 +145,13 @@ export class McpOAuthProvider {
106
145
  });
107
146
  }
108
147
  async tokens() {
109
- return (await this.read()).tokens;
148
+ if (this.options.config?.clientId)
149
+ await this.clientInformation();
150
+ const record = await this.read();
151
+ const tokens = record.tokens;
152
+ if (this.options.config?.issuer && tokens?.issuer !== this.options.config.issuer)
153
+ return undefined;
154
+ return tokens;
110
155
  }
111
156
  async saveTokens(tokens) {
112
157
  await this.mutate((record) => {
@@ -148,9 +193,7 @@ export class McpOAuthProvider {
148
193
  // A dynamically registered client belongs to the authorization server
149
194
  // that issued it. When discovery moves the resource to a DIFFERENT
150
195
  // authorization server, carrying the old registration over would send
151
- // one AS's client credentials (and any secret) to another. Static
152
- // config-supplied clients are unaffected — they never live in the
153
- // record.
196
+ // one AS's client credentials (and any secret) to another.
154
197
  const previousIssuer = record.discovery?.authorizationServerUrl;
155
198
  const nextIssuer = state.authorizationServerUrl;
156
199
  if (previousIssuer && nextIssuer && `${previousIssuer}` !== `${nextIssuer}`) {
@@ -217,7 +260,8 @@ export class McpOAuthProvider {
217
260
  record.pendingRedirectUrl ||
218
261
  record.pendingServerUrl ||
219
262
  record.pendingState;
220
- return Boolean(boundable) && record.serverUrl !== this.options.serverUrl;
263
+ return (Boolean(boundable) &&
264
+ !mcpOAuthRecordBoundTo(record, this.options.serverUrl, this.options.config));
221
265
  }
222
266
  /** The same fail-closed binding rule as read(), applied to a mutation
223
267
  * basis: a record bound elsewhere contributes NOTHING to the new write —
@@ -239,6 +283,10 @@ export class McpOAuthProvider {
239
283
  const stamp = (record) => {
240
284
  apply(record);
241
285
  record.serverUrl = this.options.serverUrl;
286
+ if (this.clientConfigHash)
287
+ record.clientConfigHash = this.clientConfigHash;
288
+ else
289
+ delete record.clientConfigHash;
242
290
  return record;
243
291
  };
244
292
  // The coordinator-provided storage view makes read-apply-write one
@@ -38,7 +38,7 @@ export async function inspectAgentRunReadModel(runStore, runtimeEventStore, opti
38
38
  diagnostics.push(inspectDiagnostic(invocation, 'runtime_terminal_missing', 'runtime ledger has no complete terminal RuntimeEvent fact'));
39
39
  }
40
40
  }
41
- const projection = runtimeEvents.length > 0
41
+ const projection = runtimeEvents.length > 0 && options.includeProjection !== false
42
42
  ? projectRuntimeEventsToStoredMessages(runtimeEvents, { invocations: [invocation] })
43
43
  : undefined;
44
44
  if (projection) {
@@ -59,7 +59,7 @@ function isImageToolResult(value) {
59
59
  typeof image.ref === 'object');
60
60
  }
61
61
  function toolResultText(text) {
62
- return { type: 'content', value: [{ type: 'text', text }] };
62
+ return { type: 'text', text };
63
63
  }
64
64
  function nativeApplyPatchFailureOutput(output) {
65
65
  const value = output.type === 'json' || output.type === 'error-json' ? output.value : undefined;
@@ -538,18 +538,24 @@ export class AiSdkMessageProjection {
538
538
  async materializeToolResultOutput(budget, output, isError, decisionKey) {
539
539
  if (isError || !isImageToolResult(output))
540
540
  return toolResultOutput(output, isError);
541
+ return {
542
+ type: 'content',
543
+ value: [await this.materializeImage(budget, output.ref, output.mimeType, decisionKey)],
544
+ };
545
+ }
546
+ async materializeImage(budget, ref, mediaType, decisionKey) {
541
547
  if (this.input.supportsVision !== true) {
542
548
  return toolResultText('Image was read, but the selected model does not support image input.');
543
549
  }
544
550
  if (!this.input.readAttachmentBytes) {
545
551
  return toolResultText('Image was read, but its stored bytes are unavailable.');
546
552
  }
547
- if (budget && budget.decisions.get(decisionKey) === false) {
553
+ if (budget.decisions.get(decisionKey) === false) {
548
554
  return toolResultText(PROVIDER_IMAGE_BUDGET_EXCEEDED_MESSAGE);
549
555
  }
550
556
  let read;
551
557
  try {
552
- read = await this.input.readAttachmentBytes(output.ref);
558
+ read = await this.input.readAttachmentBytes(ref);
553
559
  }
554
560
  catch {
555
561
  return toolResultText('Image could not be loaded from artifact storage: read_failed.');
@@ -561,18 +567,9 @@ export class AiSdkMessageProjection {
561
567
  return toolResultText(PROVIDER_IMAGE_BUDGET_EXCEEDED_MESSAGE);
562
568
  }
563
569
  return {
564
- type: 'content',
565
- value: [
566
- { type: 'text', text: 'Image read successfully.' },
567
- {
568
- type: 'file',
569
- data: {
570
- type: 'data',
571
- data: Buffer.from(read.bytes).toString('base64'),
572
- },
573
- mediaType: output.mimeType,
574
- },
575
- ],
570
+ type: 'file',
571
+ data: { type: 'data', data: Buffer.from(read.bytes).toString('base64') },
572
+ mediaType,
576
573
  };
577
574
  }
578
575
  async materializeDurableToolResultProjection(budget, projection, decisionKey) {
@@ -580,51 +577,9 @@ export class AiSdkMessageProjection {
580
577
  return durableProjectionToToolResultOutput(projection);
581
578
  const value = [];
582
579
  for (const [index, part] of projection.parts.entries()) {
583
- if (part.kind === 'text') {
584
- value.push({ type: 'text', text: part.text });
585
- continue;
586
- }
587
- if (this.input.supportsVision !== true) {
588
- value.push({
589
- type: 'text',
590
- text: 'Image was read, but the selected model does not support image input.',
591
- });
592
- continue;
593
- }
594
- if (!this.input.readAttachmentBytes) {
595
- value.push({
596
- type: 'text',
597
- text: 'Image was read, but its stored bytes are unavailable.',
598
- });
599
- continue;
600
- }
601
- let read;
602
- try {
603
- read = await this.input.readAttachmentBytes(part.ref);
604
- }
605
- catch {
606
- value.push({
607
- type: 'text',
608
- text: 'Image could not be loaded from artifact storage: read_failed.',
609
- });
610
- continue;
611
- }
612
- if (!read.ok) {
613
- value.push({
614
- type: 'text',
615
- text: `Image could not be loaded from artifact storage: ${read.reason}.`,
616
- });
617
- continue;
618
- }
619
- if (!this.chargeImageBudget(budget, read.bytes.length, `${decisionKey}:artifact:${index}`)) {
620
- value.push({ type: 'text', text: PROVIDER_IMAGE_BUDGET_EXCEEDED_MESSAGE });
621
- continue;
622
- }
623
- value.push({
624
- type: 'file',
625
- data: { type: 'data', data: Buffer.from(read.bytes).toString('base64') },
626
- mediaType: part.mediaType,
627
- });
580
+ value.push(part.kind === 'text'
581
+ ? toolResultText(part.text)
582
+ : await this.materializeImage(budget, part.ref, part.mediaType, `${decisionKey}:artifact:${index}`));
628
583
  }
629
584
  return { type: 'content', value };
630
585
  }
@@ -35,6 +35,7 @@ import { decodePlaintextResponsesReasoningState, responsesReasoningItemId, } fro
35
35
  import { finitePositive } from './context-budget-helpers.js';
36
36
  import { contextDiagnosticsCompactionOf, } from './context-diagnostics.js';
37
37
  import { hasBlockingReplayDiagnostics } from './ai-sdk-compaction.js';
38
+ import { portableApplyPatchTool } from './apply-patch-profile.js';
38
39
  import { RunTrace } from './run-trace.js';
39
40
  import { REQUEST_SANDBOX_BOUNDARY_TOOL_NAME, SANDBOX_BOUNDARY_DENIED_FOR_TURN, SANDBOX_BOUNDARY_FINALIZATION_PROMPT, } from './sandbox-boundary-tool.js';
40
41
  import { buildRuntimeEventModelReplayPlan, buildSteeringEnvelope, collectToolActivityTurnIds, compatibleProviderReasoningReplayEventIds, formatTextWithInlineRefs, steeringMessagesMissingFromBase, steeringModelMessage, } from './model-history.js';
@@ -251,6 +252,10 @@ function renderCodeModeCatalogPrompt(nested) {
251
252
  function nestableToolSnapshot(providerTools, activeToolNames) {
252
253
  const active = new Set(activeToolNames);
253
254
  return new Map(providerTools
255
+ .map((tool) => tool.providerTool?.kind === 'openai-apply-patch' ||
256
+ tool.providerTool?.kind === 'codex-apply-patch'
257
+ ? portableApplyPatchTool(tool)
258
+ : tool)
254
259
  .filter((tool) => active.has(tool.name) &&
255
260
  tool.name !== INVALID_TOOL_NAME &&
256
261
  tool.name !== 'exec' &&
@@ -348,6 +353,7 @@ function joinPromptFragments(fragments) {
348
353
  }
349
354
  const MAX_WAITING_CODE_MODE_CELLS = 1;
350
355
  const MAX_PROVIDER_ATTEMPTS_PER_STEP = 10;
356
+ const CONTEXT_RECOVERY_MAX_OUTPUT_TOKENS = 8_000;
351
357
  const PROVIDER_RETRY_BASE_DELAY_MS = 1_000;
352
358
  const PROVIDER_RETRY_MAX_DELAY_MS = 32_000;
353
359
  const PROVIDER_RETRY_JITTER_FACTOR = 0.25;
@@ -1168,6 +1174,7 @@ export class AiSdkTurn {
1168
1174
  : undefined;
1169
1175
  providerRequestTracker?.setStep(runtimeSteps, requestCompositionId);
1170
1176
  let attemptMessages = projectedMessages;
1177
+ let overflowRecoveryMaxOutputTokens;
1171
1178
  let providerAttempt = 0;
1172
1179
  const returnedToolCalls = [];
1173
1180
  const providerToolInputs = new Map();
@@ -1205,6 +1212,8 @@ export class AiSdkTurn {
1205
1212
  ? nestedTools
1206
1213
  : undefined;
1207
1214
  const requestWatchdog = watchdogState.current;
1215
+ const requestMaxOutputTokens = overflowRecoveryMaxOutputTokens ??
1216
+ this.deps.modelAdapter.maxOutputTokensForInput(midTurnState?.baselineTokens ?? midTurnState?.lastAcceptedTotalTokens);
1208
1217
  // Read here, beside the messages it describes: `attemptMessages` is
1209
1218
  // rebuilt in place by overflow recovery, and the boundary it folded
1210
1219
  // under must travel with that rebuild, not with the step.
@@ -1232,6 +1241,9 @@ export class AiSdkTurn {
1232
1241
  ...(providerRequestTracker ? { providerRequestTracker } : {}),
1233
1242
  ...(historyCompactBoundary ? { historyCompactBoundary } : {}),
1234
1243
  continuationKey: this.turnId,
1244
+ ...(requestMaxOutputTokens !== undefined
1245
+ ? { maxOutputTokens: requestMaxOutputTokens }
1246
+ : {}),
1235
1247
  });
1236
1248
  for await (const event of result.events) {
1237
1249
  if (this.aborted)
@@ -1418,6 +1430,11 @@ export class AiSdkTurn {
1418
1430
  consumeWatchdogTimeout();
1419
1431
  providerOutcome = await result.outcome;
1420
1432
  if (providerOutcome.kind === 'completed') {
1433
+ // A compacted overflow retry only needs the conservative cap for
1434
+ // that one request. Once the provider accepts the request, the
1435
+ // next step can use the fresh provider count to derive a larger
1436
+ // safe cap again.
1437
+ overflowRecoveryMaxOutputTokens = undefined;
1421
1438
  runtimeSteps += 1;
1422
1439
  const stepUsage = providerOutcome.usage;
1423
1440
  providerStepUsage = stepUsage;
@@ -1616,6 +1633,7 @@ export class AiSdkTurn {
1616
1633
  : undefined;
1617
1634
  if (recovered) {
1618
1635
  attemptMessages = recovered.messages;
1636
+ overflowRecoveryMaxOutputTokens = Math.min(this.deps.modelAdapter.maxOutputTokens() ?? CONTEXT_RECOVERY_MAX_OUTPUT_TOKENS, CONTEXT_RECOVERY_MAX_OUTPUT_TOKENS);
1619
1637
  continue;
1620
1638
  }
1621
1639
  // Window suggestion (#4559): the provider rejected a request and
@@ -16,17 +16,30 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { parseCodexV4aPatch } from './codex-v4a-patch.js';
19
+ import { modelApplyPatchEnabled } from '@maka/core/model-thinking';
20
+ import { z } from 'zod';
21
+ import { CODEX_PATCH_DESCRIPTION, parseCodexV4aPatch } from './codex-v4a-patch.js';
20
22
  import { openAiModelSupportsApplyPatch } from './openai-apply-patch.js';
21
- /** Resolve the exact provider/model/wire contract; unknown combinations fail closed. */
23
+ const portableApplyPatchParameters = z.object({ patch: z.string() });
24
+ /** Project a provider-native ApplyPatch tool into the portable client-executed shape. */
25
+ export function portableApplyPatchTool(tool) {
26
+ return {
27
+ ...tool,
28
+ description: CODEX_PATCH_DESCRIPTION,
29
+ parameters: portableApplyPatchParameters,
30
+ providerTool: undefined,
31
+ };
32
+ }
33
+ /** User overrides take precedence; new models can opt in through ordinary function calling. */
22
34
  export function resolveApplyPatchProfile(runtime, modelId) {
23
- if (runtime.wire !== 'openai-responses' || !runtime.applyPatchProtocol)
35
+ if (!modelApplyPatchEnabled(modelId, { applyPatch: runtime.enabled }))
24
36
  return null;
25
- const id = modelId.trim().toLowerCase();
26
- if (runtime.applyPatchProtocol === 'openai-structured' && openAiModelSupportsApplyPatch(id)) {
37
+ const structured = runtime.wire === 'openai-responses' &&
38
+ runtime.applyPatchProtocol === 'openai-structured' &&
39
+ openAiModelSupportsApplyPatch(modelId.trim().toLowerCase());
40
+ if (structured)
27
41
  return { kind: 'openai-structured' };
28
- }
29
- return null;
42
+ return runtime.customTools ? { kind: 'codex-v4a-freeform' } : { kind: 'portable-v4a' };
30
43
  }
31
44
  /** Project one verified profile into an exclusive model-facing editing surface. */
32
45
  export function routeApplyPatchTools(tools, profile) {
@@ -35,19 +48,40 @@ export function routeApplyPatchTools(tools, profile) {
35
48
  return [...tools];
36
49
  if (!profile)
37
50
  return tools.filter((tool) => tool !== applyPatchTool);
38
- return tools.filter((tool) => tool.name !== 'Write' && tool.name !== 'Edit');
51
+ const routed = tools.filter((tool) => tool.name !== 'Write' && tool.name !== 'Edit');
52
+ if (profile.kind === 'openai-structured')
53
+ return routed;
54
+ return routed.map((tool) => tool !== applyPatchTool
55
+ ? tool
56
+ : profile.kind === 'codex-v4a-freeform'
57
+ ? {
58
+ ...tool,
59
+ description: CODEX_PATCH_DESCRIPTION,
60
+ parameters: z.string(),
61
+ providerTool: { kind: 'codex-apply-patch' },
62
+ }
63
+ : portableApplyPatchTool(tool));
39
64
  }
40
- /** Convert historical freeform calls for a structured target, or reject an undeclared target. */
65
+ /** Re-encode history for the current tool transport, or preserve it as facts when disabled. */
41
66
  export function normalizeApplyPatchReplayInput(profile, toolCallId, input) {
42
67
  // A missing profile means the target request does not advertise ApplyPatch.
43
68
  // Returning the historical input would serialize a call to an undeclared
44
69
  // tool; route it through the durable-fact downgrade instead.
45
70
  if (!profile)
46
71
  return null;
47
- if (typeof input !== 'string')
48
- return input;
72
+ const patch = patchText(input);
73
+ if (profile.kind !== 'openai-structured') {
74
+ const text = patch ?? structuredPatchText(input);
75
+ if (text === null)
76
+ return null;
77
+ if (profile.kind === 'codex-v4a-freeform')
78
+ return text;
79
+ return patch !== null && typeof input === 'object' ? input : { patch: text };
80
+ }
81
+ if (patch === null)
82
+ return structuredApplyPatchOperation(input) ? input : null;
49
83
  try {
50
- const operations = parseCodexV4aPatch(input);
84
+ const operations = parseCodexV4aPatch(patch);
51
85
  return operations.length === 1 ? { callId: toolCallId, operation: operations[0] } : null;
52
86
  }
53
87
  catch {
@@ -57,9 +91,10 @@ export function normalizeApplyPatchReplayInput(profile, toolCallId, input) {
57
91
  /** Preserve an executed multi-file patch when the target wire cannot replay one call for it. */
58
92
  export function applyPatchReplayFactText(input, output, isError) {
59
93
  let operations;
60
- if (typeof input === 'string') {
94
+ const patch = patchText(input);
95
+ if (patch !== null) {
61
96
  try {
62
- operations = parseCodexV4aPatch(input);
97
+ operations = parseCodexV4aPatch(patch);
63
98
  }
64
99
  catch {
65
100
  return null;
@@ -129,3 +164,18 @@ function structuredApplyPatchOperation(input) {
129
164
  }
130
165
  return null;
131
166
  }
167
+ function patchText(input) {
168
+ if (typeof input === 'string')
169
+ return input;
170
+ if (input && typeof input === 'object' && 'patch' in input && typeof input.patch === 'string')
171
+ return input.patch;
172
+ return null;
173
+ }
174
+ function structuredPatchText(input) {
175
+ const op = structuredApplyPatchOperation(input);
176
+ if (!op)
177
+ return null;
178
+ const action = op.type === 'create_file' ? 'Add' : op.type === 'delete_file' ? 'Delete' : 'Update';
179
+ const body = op.type === 'delete_file' ? '' : op.diff.endsWith('\n') ? op.diff : `${op.diff}\n`;
180
+ return `*** Begin Patch\n*** ${action} File: ${op.path}\n${body}*** End Patch`;
181
+ }
@@ -161,10 +161,11 @@ export function buildBuiltinTools(options = {}) {
161
161
  providerTool: { kind: 'openai-apply-patch' },
162
162
  executionFacts,
163
163
  impl: async (input, ctx) => {
164
- if (typeof input !== 'string') {
164
+ const patch = typeof input === 'string' ? input : input.patch;
165
+ if (typeof patch !== 'string') {
165
166
  return await filesystem.applyPatch({ operation: input.operation, ...filesystemCall(ctx) });
166
167
  }
167
- const operations = parseCodexV4aPatch(input);
168
+ const operations = parseCodexV4aPatch(patch);
168
169
  return await executeApplyPatchOperations(operations, async (operation) => {
169
170
  await filesystem.applyPatch({ operation, ...filesystemCall(ctx) });
170
171
  }, ctx.abortSignal);
@@ -16,6 +16,24 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
+ export const CODEX_PATCH_DESCRIPTION = `Edit files using a patch enclosed in *** Begin Patch and *** End Patch.
20
+ Use *** Add File: path with each content line prefixed by +; *** Delete File: path;
21
+ or *** Update File: path with @@ context markers and lines prefixed by space (unchanged),
22
+ - (removed), or + (added). Include enough unchanged context to locate each edit.
23
+ One patch may contain multiple file operations. Moves are not supported: add the destination
24
+ and delete the source instead. Read existing files before patching them.`;
25
+ // Match the executor's supported subset: intentionally no Move to production.
26
+ export const CODEX_PATCH_GRAMMAR = String.raw `start: begin_patch hunk+ end_patch
27
+ begin_patch: "*** Begin Patch" LF
28
+ end_patch: "*** End Patch" LF?
29
+ hunk: add_hunk | delete_hunk | update_hunk
30
+ add_hunk: "*** Add File: " filename LF add_line+
31
+ delete_hunk: "*** Delete File: " filename LF
32
+ update_hunk: "*** Update File: " filename LF change+
33
+ filename: /[^\r\n]+/
34
+ add_line: "+" /[^\r\n]+/? LF
35
+ change: ("@@" | "@@ " /[^\r\n]+/) LF | ("+" | "-" | " ") /[^\r\n]+/? LF | "*** End of File" LF
36
+ %import common.LF`;
19
37
  export class CodexV4aPatchError extends Error {
20
38
  constructor(message) {
21
39
  super(message);