import type { AgentMessage } from "@mariozechner/pi-agent-core"; import { completeSimple, type Api, type Context, type Model } from "@mariozechner/pi-ai"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import { Type } from "typebox"; import { describe, expect, it } from "vitest"; import { loadConfig } from "../config/config.js"; import { resolveOpenClawAgentDir } from "./agent-paths.js"; import { isLiveProfileKeyModeEnabled, isLiveTestEnabled } from "./live-test-helpers.js"; import { getApiKeyForModel, requireApiKey } from "./model-auth.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; import { sanitizeSessionHistory } from "./pi-embedded-runner/replay-history.js"; import { discoverAuthStorage, discoverModels } from "./pi-model-discovery.js"; import { transformTransportMessages } from "./transport-message-transform.js";
function isOpenAIResponsesFamily(api: string): boolean { return (
api === "openai-responses" ||
api === "openai-codex-responses" ||
api === "azure-openai-responses"
);
}
function buildReplayMessages(model: Model<Api>): AgentMessage[] { const now = Date.now(); // Gemini source metadata deliberately simulates a model switch from a // provider-owned transcript. That forces the same id sanitization and replay // repair path that failed in real session replays, not just the happy path for // a same-provider synthetic fixture. const source =
model.provider === "google"
? {
api: "google-gemini-cli",
provider: "google-antigravity",
model: "claude-sonnet-4-20250514",
}
: {
api: model.api,
provider: model.provider,
model: model.id,
};
function isKnownLiveBlocker(errorMessage: string): boolean { return (
/not supported when using codex with a chatgpt account/i.test(errorMessage) ||
/hit your chatgpt usage limit/i.test(errorMessage)
);
}
describeLive("tool replay repair live", () => { for (const target of TARGET_MODEL_REFS) {
it(
`accepts repaired displaced and missing tool results with ${target.ref}`,
async () => { const cfg = loadConfig();
await ensureOpenClawModelsJson(cfg);
// These assertions are the model-visible contract: OpenAI Responses // gets Codex-compatible "aborted" outputs, while Gemini proves the // generic repair does not leak OpenAI wording into other providers. const insertedTexts = sanitized.slice(3, 5).map(syntheticToolResultText); if (isOpenAIResponsesFamily(model.api)) {
expect(insertedTexts).toEqual(["aborted", "aborted"]);
} else {
expect(insertedTexts).not.toContain("aborted");
}
// Sending the repaired transcript to the real model is the live proof: // providers reject malformed tool-call adjacency before generation, so // any non-error response here validates the repair shape end to end. const response = await completeSimpleWithTimeout(
model,
{
systemPrompt: "You are a concise assistant. Follow the user's instruction exactly.",
messages: sanitized as never,
tools: [
{
name: "noop",
description: "Return ok.",
parameters: Type.Object({}, { additionalProperties: false }),
},
],
},
{
apiKey: requireApiKey(apiKeyInfo, model.provider),
reasoning: "low",
maxTokens: 96,
}, 120_000,
);
// This is the transport replay regression proof: providers reject // assistant(tool_call)->user replays without a matching result, so the // dropped transcript must still be accepted by real model APIs. const response = await completeSimpleWithTimeout(
model,
{
systemPrompt: "You are a concise assistant. Follow the user's instruction exactly.",
messages: transformed as never,
tools: [
{
name: "noop",
description: "Return ok.",
parameters: Type.Object({}, { additionalProperties: false }),
},
],
},
{
apiKey: requireApiKey(apiKeyInfo, model.provider),
reasoning: "low",
maxTokens: 96,
}, 120_000,
);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.