maka-agent 0.2.0-dev.46.20260922 → 0.2.0-dev.47.20260922
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/DISCLAIMER-WIP +6 -1
- package/README.md +6 -1
- package/dist/acp/maka-acp-agent.js +1 -1
- package/dist/acp/session-mcp.js +212 -0
- package/dist/acp/session-registry.js +52 -8
- package/dist/acp/stdio-server.js +3 -0
- package/dist/cli-core.js +1 -1
- package/dist/mcp-capability-provider.js +6 -1
- package/dist/mcp-capability-publication.js +139 -0
- package/dist/pi-tui-layout.js +46 -17
- package/dist/pi-tui-pickers.js +25 -14
- package/dist/pi-tui-runner.js +24 -15
- package/dist/runtime-host-service-manager.js +26 -2
- package/dist/tui-context-refresh.js +15 -26
- package/dist/tui-mcp-control.js +29 -88
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
- package/node_modules/@maka/core/dist/model-thinking.js +36 -1
- package/node_modules/@maka/core/dist/provider-registry.js +0 -1
- package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
- package/node_modules/@maka/core/dist/session.js +17 -1
- package/node_modules/@maka/core/dist/settings.js +2 -0
- package/node_modules/@maka/core/package.json +1 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
- package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
- package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
- package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
- package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
- package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
- package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
- package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +6 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +15 -2
- package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
- package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
- package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
- package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
- package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +11 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +35 -3
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
- package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
- package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +29 -2
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +32 -4
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
- package/package.json +1 -1
|
@@ -614,7 +614,6 @@ const providerRegistry = {
|
|
|
614
614
|
runtimeAdapter: {
|
|
615
615
|
kind: 'openai-compatible',
|
|
616
616
|
name: 'provider',
|
|
617
|
-
applyPatchProtocol: 'codex-v4a-freeform',
|
|
618
617
|
responses: { adapter: 'open-responses', reasoningReplay: 'plaintext-content' },
|
|
619
618
|
},
|
|
620
619
|
modelDiscovery: { kind: 'protocol' },
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Coordinates a debounced read of an authority-owned projection.
|
|
21
|
+
*
|
|
22
|
+
* The freshness invariant is intentionally stronger than "only the latest
|
|
23
|
+
* query may apply": an authority event also invalidates a read that is
|
|
24
|
+
* already in flight. That read may have captured the projection before the
|
|
25
|
+
* event and can otherwise resolve after the newer state has reached the
|
|
26
|
+
* caller. Callers decide which events are relevant; this module owns the
|
|
27
|
+
* ordering and cancellation rule behind that small interface.
|
|
28
|
+
*/
|
|
29
|
+
export function createRefreshReadCoordinator(input) {
|
|
30
|
+
let cancelScheduled;
|
|
31
|
+
let revision = 0;
|
|
32
|
+
const dropScheduled = () => {
|
|
33
|
+
cancelScheduled?.();
|
|
34
|
+
cancelScheduled = undefined;
|
|
35
|
+
};
|
|
36
|
+
const run = () => {
|
|
37
|
+
cancelScheduled = undefined;
|
|
38
|
+
const readRevision = ++revision;
|
|
39
|
+
void input.read().then((result) => {
|
|
40
|
+
if (readRevision !== revision)
|
|
41
|
+
return;
|
|
42
|
+
input.apply(result);
|
|
43
|
+
}, () => { });
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
observe() {
|
|
47
|
+
// Invalidate before scheduling. The previous read can resolve during the
|
|
48
|
+
// debounce window, before the replacement read is issued.
|
|
49
|
+
revision += 1;
|
|
50
|
+
dropScheduled();
|
|
51
|
+
cancelScheduled = input.schedule(run, input.delayMs);
|
|
52
|
+
},
|
|
53
|
+
refresh() {
|
|
54
|
+
dropScheduled();
|
|
55
|
+
run();
|
|
56
|
+
},
|
|
57
|
+
cancel() {
|
|
58
|
+
dropScheduled();
|
|
59
|
+
revision += 1;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -170,6 +170,7 @@ export function decodeModelOverridesTable(value) {
|
|
|
170
170
|
'thinkingLevels',
|
|
171
171
|
'defaultThinkingLevel',
|
|
172
172
|
'vision',
|
|
173
|
+
'applyPatch',
|
|
173
174
|
'contextWindow',
|
|
174
175
|
'serviceTier',
|
|
175
176
|
'compactionThreshold',
|
|
@@ -210,6 +211,9 @@ export function decodeModelOverridesTable(value) {
|
|
|
210
211
|
if (entry.vision !== undefined) {
|
|
211
212
|
declared.vision = booleanValue(entry.vision, `declared vision for ${modelId}`);
|
|
212
213
|
}
|
|
214
|
+
if (entry.applyPatch !== undefined) {
|
|
215
|
+
declared.applyPatch = booleanValue(entry.applyPatch, `declared ApplyPatch for ${modelId}`);
|
|
216
|
+
}
|
|
213
217
|
for (const field of [
|
|
214
218
|
'contextWindow',
|
|
215
219
|
'compactionThreshold',
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { isWorkHubActionReceipt } from './workhub-action-result.js';
|
|
20
20
|
import { isExecutorId } from './executor-id.js';
|
|
21
|
+
import { isThinkingLevel } from './model-thinking.js';
|
|
21
22
|
import { MODEL_FAILURE_MESSAGE_MAX_BYTES, isModelRetryDecision, } from './model-failure.js';
|
|
22
23
|
import { decodeMessageContent, TOOL_ACTIVITY_KINDS, } from './events.js';
|
|
23
24
|
import { isPermissionMode, isToolCategory, } from './permission.js';
|
|
@@ -304,16 +305,31 @@ export function isConversationTextMessage(message) {
|
|
|
304
305
|
export const WORKHUB_COORDINATION_RECORD_SCHEMA_VERSION = 1;
|
|
305
306
|
export const WORKHUB_COORDINATION_REPLACEMENT_SCHEMA_VERSION = 2;
|
|
306
307
|
export const WORKHUB_COORDINATION_STOP_SCHEMA_VERSION = 3;
|
|
308
|
+
/** UTF-8 wire limit shared by persisted and protocol Session model identifiers. */
|
|
309
|
+
export const SESSION_MODEL_ID_MAX_BYTES = 512;
|
|
307
310
|
export function isWorkHubCreateDefaults(value) {
|
|
308
311
|
if (!isRecord(value) ||
|
|
309
|
-
Object.keys(value).some((key) => key !== 'executorId' &&
|
|
312
|
+
Object.keys(value).some((key) => key !== 'executorId' &&
|
|
313
|
+
key !== 'executorModel' &&
|
|
314
|
+
key !== 'model' &&
|
|
315
|
+
key !== 'thinkingLevel' &&
|
|
316
|
+
key !== 'permissionMode'))
|
|
310
317
|
return false;
|
|
311
318
|
if (value.permissionMode !== undefined && !isPermissionMode(value.permissionMode))
|
|
312
319
|
return false;
|
|
313
320
|
if (value.executorId !== undefined && !isExecutorId(value.executorId))
|
|
314
321
|
return false;
|
|
322
|
+
if (value.executorModel !== undefined &&
|
|
323
|
+
(typeof value.executorModel !== 'string' ||
|
|
324
|
+
value.executorModel.trim().length === 0 ||
|
|
325
|
+
new TextEncoder().encode(value.executorModel).byteLength > SESSION_MODEL_ID_MAX_BYTES))
|
|
326
|
+
return false;
|
|
327
|
+
if (value.executorModel !== undefined && value.executorId === undefined)
|
|
328
|
+
return false;
|
|
315
329
|
if (value.executorId !== undefined && value.model !== undefined)
|
|
316
330
|
return false;
|
|
331
|
+
if (value.thinkingLevel !== undefined && !isThinkingLevel(value.thinkingLevel))
|
|
332
|
+
return false;
|
|
317
333
|
if (value.model === undefined)
|
|
318
334
|
return true;
|
|
319
335
|
const model = value.model;
|
|
@@ -340,6 +340,7 @@ export function createDefaultSettings() {
|
|
|
340
340
|
activeTab: 'requests',
|
|
341
341
|
},
|
|
342
342
|
appearance: {
|
|
343
|
+
workbarTogglePosition: 'edge',
|
|
343
344
|
theme: 'auto',
|
|
344
345
|
palette: 'default',
|
|
345
346
|
appIcon: DEFAULT_APP_ICON,
|
|
@@ -515,6 +516,7 @@ export function normalizeSettings(input) {
|
|
|
515
516
|
// position; UI density is no longer a product setting.
|
|
516
517
|
appearance: {
|
|
517
518
|
...appearanceWithoutLegacyFields,
|
|
519
|
+
workbarTogglePosition: base.appearance.workbarTogglePosition === 'titlebar' ? 'titlebar' : 'edge',
|
|
518
520
|
palette: isThemePalette(base.appearance.palette) ? base.appearance.palette : 'default',
|
|
519
521
|
// Same fail-closed rule as `palette` above, for the same reason: an
|
|
520
522
|
// unknown id would otherwise reach the main process and resolve to a
|
|
@@ -133,6 +133,7 @@
|
|
|
133
133
|
"./computer-use": "./dist/computer-use.js",
|
|
134
134
|
"./connection-error-copy": "./dist/connection-error-copy.js",
|
|
135
135
|
"./connection-usage": "./dist/connection-usage.js",
|
|
136
|
+
"./refresh-read-coordinator": "./dist/refresh-read-coordinator.js",
|
|
136
137
|
"./git-review": "./dist/git-review.js",
|
|
137
138
|
"./graph-command": "./dist/graph-command.js",
|
|
138
139
|
"./project": "./dist/project.js",
|
|
@@ -59,7 +59,7 @@ function isImageToolResult(value) {
|
|
|
59
59
|
typeof image.ref === 'object');
|
|
60
60
|
}
|
|
61
61
|
function toolResultText(text) {
|
|
62
|
-
return { type: '
|
|
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
|
|
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(
|
|
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: '
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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 {
|
|
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
|
-
|
|
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 (
|
|
35
|
+
if (!modelApplyPatchEnabled(modelId, { applyPatch: runtime.enabled }))
|
|
24
36
|
return null;
|
|
25
|
-
const
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
48
|
-
|
|
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(
|
|
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
|
-
|
|
94
|
+
const patch = patchText(input);
|
|
95
|
+
if (patch !== null) {
|
|
61
96
|
try {
|
|
62
|
-
operations = parseCodexV4aPatch(
|
|
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
|
-
|
|
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(
|
|
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);
|
|
@@ -22,13 +22,14 @@ import { providerAuthRequiresSecret, } from '@maka/core/llm-connections';
|
|
|
22
22
|
import { lookupModelMetadata } from '@maka/core/model-metadata';
|
|
23
23
|
import { rawFinishReasonString } from './model-protocol.js';
|
|
24
24
|
import { resolveModelRuntime } from './model-runtime.js';
|
|
25
|
+
import { resolveSelectedModelContextWindow } from './context-budget-policy.js';
|
|
25
26
|
import { plaintextResponsesReasoningProviderOptions, withoutMakaResponsesState, safePlaintextResponsesReasoningItemId, } from './responses-reasoning-state.js';
|
|
26
27
|
import { classifyError, providerModelFailure } from './provider-error-classification.js';
|
|
27
28
|
import { withProviderStreamTracking, } from './provider-request-telemetry.js';
|
|
28
29
|
import { createOpenAiChatReasoningTransportState, openAiChatReasoningFieldProviderOptions, restoreOpenAiChatEmptyReasoning, } from './openai-chat-reasoning-transport.js';
|
|
29
30
|
import { mergeOpenAiResponsesProviderOptions, planOpenAiResponsesContinuation, } from './openai-responses-continuation.js';
|
|
30
31
|
import { createOpenAiResponsesTransportState, OPENAI_RESPONSES_LANE_HEADER, } from './openai-responses-websocket.js';
|
|
31
|
-
import { openAiApplyPatchProviderTool } from './openai-apply-patch.js';
|
|
32
|
+
import { openAiApplyPatchProviderTool, codexApplyPatchProviderTool } from './openai-apply-patch.js';
|
|
32
33
|
import { TOOL_SEARCH_NAME, TOOL_SEARCH_PROVIDER_NAME } from './tool-availability.js';
|
|
33
34
|
export class ModelAdapter {
|
|
34
35
|
input;
|
|
@@ -97,12 +98,40 @@ export class ModelAdapter {
|
|
|
97
98
|
maxOutputTokens() {
|
|
98
99
|
return selectedModelMaxOutputTokens(this.input.connection, this.input.modelId, this.input.providerOptions, this.runtime);
|
|
99
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Keep a provider-derived output limit from making a resumed request
|
|
103
|
+
* impossible. The token count is the last request the provider accepted, so
|
|
104
|
+
* it is a conservative lower bound for the next request. Leave a small
|
|
105
|
+
* amount of room for newly appended user/tool content because Runtime does
|
|
106
|
+
* not estimate the final prompt locally.
|
|
107
|
+
*/
|
|
108
|
+
maxOutputTokensForInput(knownInputTokens) {
|
|
109
|
+
const outputLimit = this.maxOutputTokens();
|
|
110
|
+
if (outputLimit === undefined ||
|
|
111
|
+
knownInputTokens === undefined ||
|
|
112
|
+
!Number.isFinite(knownInputTokens) ||
|
|
113
|
+
knownInputTokens < 0) {
|
|
114
|
+
return outputLimit;
|
|
115
|
+
}
|
|
116
|
+
const contextWindow = resolveSelectedModelContextWindow(this.input.connection, this.input.modelId);
|
|
117
|
+
if (contextWindow === undefined)
|
|
118
|
+
return outputLimit;
|
|
119
|
+
const thinkingBudget = this.runtime.wire === 'anthropic-messages'
|
|
120
|
+
? fixedAnthropicThinkingBudget(this.input.providerOptions)
|
|
121
|
+
: 0;
|
|
122
|
+
const available = contextWindow - Math.ceil(knownInputTokens) - CONTEXT_INPUT_GROWTH_HEADROOM - thinkingBudget;
|
|
123
|
+
// Do not turn a near-window request into a one-token success. A useful
|
|
124
|
+
// floor deliberately lets the provider reject the request, which keeps
|
|
125
|
+
// the existing reactive compaction path in control of recovery.
|
|
126
|
+
return Math.max(Math.min(outputLimit, CONTEXT_RECOVERY_OUTPUT_FLOOR), Math.min(outputLimit, available));
|
|
127
|
+
}
|
|
100
128
|
async startStream(input) {
|
|
101
129
|
const ai = await import('ai').catch((err) => {
|
|
102
130
|
throw new Error(`Failed to load 'ai' package. Run \`npm install ai\`. Inner: ${err.message}`);
|
|
103
131
|
});
|
|
104
132
|
const { streamText, wrapLanguageModel } = ai;
|
|
105
|
-
const maxOutputTokens =
|
|
133
|
+
const maxOutputTokens = input.maxOutputTokens ??
|
|
134
|
+
selectedModelMaxOutputTokens(this.input.connection, this.input.modelId, this.input.providerOptions, this.runtime);
|
|
106
135
|
let settleAccounting;
|
|
107
136
|
const terminalModel = withProviderFinishBoundary(input.model, wrapLanguageModel);
|
|
108
137
|
const trackedModel = input.providerRequestTracker
|
|
@@ -127,7 +156,7 @@ export class ModelAdapter {
|
|
|
127
156
|
sdkTools[TOOL_SEARCH_PROVIDER_NAME] = sdkTools[TOOL_SEARCH_NAME];
|
|
128
157
|
delete sdkTools[TOOL_SEARCH_NAME];
|
|
129
158
|
}
|
|
130
|
-
const fullMessages = input.messages;
|
|
159
|
+
const fullMessages = this.runtime.wire === 'openai-chat' ? lowerChatToolImages(input.messages) : input.messages;
|
|
131
160
|
const responsesLane = input.continuationKey && usesNativeOpenAiResponses(this.input.connection, this.runtime)
|
|
132
161
|
? input.continuationKey
|
|
133
162
|
: undefined;
|
|
@@ -441,6 +470,12 @@ function failedStepOutcome(failure, usage) {
|
|
|
441
470
|
continuation: 'none',
|
|
442
471
|
};
|
|
443
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* A persisted provider count describes the preceding request, not the new
|
|
475
|
+
* user message or tool result that may be appended after a restart.
|
|
476
|
+
*/
|
|
477
|
+
const CONTEXT_INPUT_GROWTH_HEADROOM = 8_000;
|
|
478
|
+
const CONTEXT_RECOVERY_OUTPUT_FLOOR = 8_000;
|
|
444
479
|
function selectedModelMaxOutputTokens(connection, modelId, providerOptions, runtime) {
|
|
445
480
|
const anthropicMessages = runtime.wire === 'anthropic-messages';
|
|
446
481
|
const kimiOpenAiChat = connection.providerType === 'kimi-coding-plan' && runtime.wire === 'openai-chat';
|
|
@@ -835,6 +870,46 @@ function translateChunk(chunk, openAiChatReasoningTransportState, runtime, runti
|
|
|
835
870
|
return [];
|
|
836
871
|
}
|
|
837
872
|
}
|
|
873
|
+
function lowerChatToolImages(messages) {
|
|
874
|
+
const result = [];
|
|
875
|
+
let images = [];
|
|
876
|
+
const flush = () => {
|
|
877
|
+
if (images.length === 0)
|
|
878
|
+
return;
|
|
879
|
+
result.push({ role: 'user', content: images });
|
|
880
|
+
images = [];
|
|
881
|
+
};
|
|
882
|
+
for (const message of messages) {
|
|
883
|
+
if (message.role !== 'tool') {
|
|
884
|
+
flush();
|
|
885
|
+
result.push(message);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
result.push({
|
|
889
|
+
...message,
|
|
890
|
+
content: message.content.map((part) => {
|
|
891
|
+
if (part.type !== 'tool-result' || part.output.type !== 'content')
|
|
892
|
+
return part;
|
|
893
|
+
return {
|
|
894
|
+
...part,
|
|
895
|
+
output: {
|
|
896
|
+
...part.output,
|
|
897
|
+
value: part.output.value.map((content) => {
|
|
898
|
+
if (content.type !== 'file' || !content.mediaType.startsWith('image/'))
|
|
899
|
+
return content;
|
|
900
|
+
images.push({ type: 'text', text: `Image from tool ${part.toolName} (${part.toolCallId}):` }, content);
|
|
901
|
+
return { type: 'text', text: 'Image supplied below.' };
|
|
902
|
+
}),
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
}),
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
// Chat tool messages are text-only; attach images after the whole result group
|
|
909
|
+
// so an assistant's parallel tool calls stay paired before the next user message.
|
|
910
|
+
flush();
|
|
911
|
+
return result;
|
|
912
|
+
}
|
|
838
913
|
/**
|
|
839
914
|
* OpenAI Responses reserves the provider name `tool_search`. Keep Maka's
|
|
840
915
|
* persisted/history name intact and translate only the provider-bound copy.
|
|
@@ -898,6 +973,8 @@ function compileProviderTool(tool) {
|
|
|
898
973
|
switch (tool.kind) {
|
|
899
974
|
case 'openai-apply-patch':
|
|
900
975
|
return openAiApplyPatchProviderTool;
|
|
976
|
+
case 'codex-apply-patch':
|
|
977
|
+
return codexApplyPatchProviderTool;
|
|
901
978
|
case 'openai-web-search':
|
|
902
979
|
return openai.tools.webSearch({
|
|
903
980
|
...(tool.searchContextSize ? { searchContextSize: tool.searchContextSize } : {}),
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { PROVIDER_REGISTRY, effectiveBaseUrl, } from '@maka/core/llm-connections';
|
|
20
20
|
import { lookupModelMetadata, lookupModelRuntimeOverride, openAiAdapterApiProtocol, } from '@maka/core/model-metadata';
|
|
21
21
|
import { isRetiredProvider } from '@maka/core/provider-registry';
|
|
22
|
+
import { modelOverride } from '@maka/core/model-thinking';
|
|
22
23
|
import { anthropicV1BaseUrl, googleV1BetaBaseUrl, openAiResponsesBaseUrl, } from './provider-urls.js';
|
|
23
24
|
import { resolveApplyPatchProfile } from './apply-patch-profile.js';
|
|
24
25
|
export function resolveModelRuntime(connection, modelId) {
|
|
@@ -76,6 +77,11 @@ export function resolveModelRuntime(connection, modelId) {
|
|
|
76
77
|
applyPatchProfile: resolveApplyPatchProfile({
|
|
77
78
|
wire,
|
|
78
79
|
applyPatchProtocol: adapter.applyPatchProtocol,
|
|
80
|
+
enabled: modelOverride(connection, modelId)?.applyPatch,
|
|
81
|
+
customTools: wire === 'openai-responses' &&
|
|
82
|
+
(connection.providerType === 'openai' || connection.providerType === 'openai-codex') &&
|
|
83
|
+
replay.kind === 'responses' &&
|
|
84
|
+
replay.contract.adapter === 'openai',
|
|
79
85
|
}, modelId),
|
|
80
86
|
};
|
|
81
87
|
}
|