blob: d4d524b4ee58244e512360568ba4518845c974f0 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { ApplyPatchProtocol } from '@maka/core/llm-connections';
import { parseCodexV4aPatch } from './codex-v4a-patch.js';
import type { ApplyPatchOperation } from './filesystem-executor.js';
import type { ModelRuntimeWire } from './model-runtime.js';
import { openAiModelSupportsApplyPatch } from './openai-apply-patch.js';
import type { MakaTool } from './tool-runtime.js';
export type ApplyPatchProfile = { readonly kind: 'openai-structured' };
export interface ApplyPatchProfileRuntime {
readonly wire: ModelRuntimeWire;
readonly applyPatchProtocol?: ApplyPatchProtocol;
}
/** Resolve the exact provider/model/wire contract; unknown combinations fail closed. */
export function resolveApplyPatchProfile(
runtime: ApplyPatchProfileRuntime,
modelId: string,
): ApplyPatchProfile | null {
if (runtime.wire !== 'openai-responses' || !runtime.applyPatchProtocol) return null;
const id = modelId.trim().toLowerCase();
if (runtime.applyPatchProtocol === 'openai-structured' && openAiModelSupportsApplyPatch(id)) {
return { kind: 'openai-structured' };
}
return null;
}
/** Project one verified profile into an exclusive model-facing editing surface. */
export function routeApplyPatchTools(
tools: readonly MakaTool[],
profile: ApplyPatchProfile | null,
): MakaTool[] {
const applyPatchTool = tools.find((tool) => tool.providerTool?.kind === 'openai-apply-patch');
if (!applyPatchTool) return [...tools];
if (!profile) return tools.filter((tool) => tool !== applyPatchTool);
return tools.filter((tool) => tool.name !== 'Write' && tool.name !== 'Edit');
}
/** Convert historical freeform calls for a structured target, or reject an undeclared target. */
export function normalizeApplyPatchReplayInput(
profile: ApplyPatchProfile | null,
toolCallId: string,
input: unknown,
): unknown | null {
// A missing profile means the target request does not advertise ApplyPatch.
// Returning the historical input would serialize a call to an undeclared
// tool; route it through the durable-fact downgrade instead.
if (!profile) return null;
if (typeof input !== 'string') return input;
try {
const operations = parseCodexV4aPatch(input);
return operations.length === 1 ? { callId: toolCallId, operation: operations[0] } : null;
} catch {
return null;
}
}
/** Preserve an executed multi-file patch when the target wire cannot replay one call for it. */
export function applyPatchReplayFactText(
input: unknown,
output: unknown,
isError: boolean,
): string | null {
let operations: ApplyPatchOperation[];
if (typeof input === 'string') {
try {
operations = parseCodexV4aPatch(input);
} catch {
return null;
}
} else {
const operation = structuredApplyPatchOperation(input);
if (!operation) return null;
operations = [operation];
}
const recorded = recordedAppliedOperations(output);
const applied = recorded ?? (isError ? [] : operations.map(operationFact));
const facts = applied.map((item) => `${item.type} ${item.path}`).join(', ');
if (!isError) {
return `ApplyPatch completed ${applied.length} file operation${applied.length === 1 ? '' : 's'}: ${facts}.`;
}
const attempted = operations.map(operationFact);
const attemptedFacts = attempted.map((item) => `${item.type} ${item.path}`).join(', ');
return applied.length > 0
? `ApplyPatch failed after applying ${applied.length} file operation${applied.length === 1 ? '' : 's'}: ${facts}.`
: `ApplyPatch failed while attempting ${attempted.length} file operation${attempted.length === 1 ? '' : 's'}: ${attemptedFacts}. No applied operation was recorded.`;
}
function operationFact(operation: ApplyPatchOperation): {
type: ApplyPatchOperation['type'];
path: string;
} {
return { type: operation.type, path: operation.path };
}
function recordedAppliedOperations(
output: unknown,
): Array<{ type: ApplyPatchOperation['type']; path: string }> | null {
const candidate = unwrapToolResultValue(output);
if (!candidate || typeof candidate !== 'object') return null;
const applied = (candidate as { applied?: unknown }).applied;
if (!Array.isArray(applied)) return null;
const facts: Array<{ type: ApplyPatchOperation['type']; path: string }> = [];
for (const item of applied) {
if (!item || typeof item !== 'object') return null;
const { type, path } = item as { type?: unknown; path?: unknown };
if (
(type !== 'create_file' && type !== 'update_file' && type !== 'delete_file') ||
typeof path !== 'string'
) {
return null;
}
facts.push({ type, path });
}
return facts;
}
function unwrapToolResultValue(output: unknown): unknown {
if (!output || typeof output !== 'object') return output;
const record = output as { kind?: unknown; value?: unknown };
return record.kind === 'json' ? record.value : output;
}
function structuredApplyPatchOperation(input: unknown): ApplyPatchOperation | null {
if (!input || typeof input !== 'object') return null;
const operation = (input as { operation?: unknown }).operation;
if (!operation || typeof operation !== 'object') return null;
const candidate = operation as { type?: unknown; path?: unknown; diff?: unknown };
if (typeof candidate.path !== 'string' || /[\r\n]/.test(candidate.path)) return null;
if (candidate.type === 'delete_file') return { type: candidate.type, path: candidate.path };
if (
(candidate.type === 'create_file' || candidate.type === 'update_file') &&
typeof candidate.diff === 'string'
) {
return { type: candidate.type, path: candidate.path, diff: candidate.diff };
}
return null;
}