fix: restore CI restart and provider compat

This commit is contained in:
Peter Steinberger
2026-04-18 19:22:18 +01:00
parent 162bf51adb
commit 84aed919a9
6 changed files with 72 additions and 14 deletions

View File

@@ -709,6 +709,32 @@ describe("provider attribution", () => {
endpointClass: "modelstudio-native",
supportsNativeStreamingUsageCompat: true,
});
expect(
resolveProviderRequestCapabilities({
provider: "ollama",
api: "openai-completions",
baseUrl: "http://127.0.0.1:11434/v1",
capability: "llm",
transport: "stream",
}),
).toMatchObject({
endpointClass: "local",
supportsNativeStreamingUsageCompat: true,
});
expect(
resolveProviderRequestCapabilities({
provider: "ollama",
api: "openai-completions",
baseUrl: "http://127.0.0.1:11434/v1",
capability: "llm",
transport: "stream",
modelId: "kimi-k2.5:cloud",
}),
).toMatchObject({
compatibilityFamily: "moonshot",
});
});
it("treats native GitHub Copilot base URLs as known native endpoints", () => {
@@ -902,6 +928,28 @@ describe("provider attribution", () => {
supportsNativeStreamingUsageCompat: true,
},
},
{
name: "Ollama OpenAI-compatible completions",
input: {
provider: "ollama",
api: "openai-completions",
baseUrl: "http://127.0.0.1:11434/v1",
capability: "llm" as const,
transport: "stream" as const,
},
expected: {
knownProviderFamily: "ollama",
endpointClass: "local",
isKnownNativeEndpoint: false,
allowsOpenAIServiceTier: false,
supportsOpenAIReasoningCompatPayload: false,
allowsResponsesStore: false,
supportsResponsesStoreField: false,
shouldStripResponsesPromptCache: false,
allowsAnthropicServiceTier: false,
supportsNativeStreamingUsageCompat: true,
},
},
{
name: "native Google Gemini api",
input: {

View File

@@ -132,6 +132,10 @@ const OPENAI_RESPONSES_APIS = new Set([
const OPENAI_RESPONSES_PROVIDERS = new Set(["openai", "azure-openai", "azure-openai-responses"]);
const MOONSHOT_COMPAT_PROVIDERS = new Set(["moonshot", "kimi"]);
function isOllamaMoonshotCompatModel(modelId: string | null | undefined): boolean {
return /^kimi-k2\.5(?::|$)/i.test(modelId?.trim() ?? "");
}
function formatOpenClawUserAgent(version: string): string {
return `${OPENCLAW_ATTRIBUTION_ORIGINATOR}/${version}`;
}
@@ -571,7 +575,12 @@ export function resolveProviderRequestCapabilities(
endpointClass === "google-vertex";
let compatibilityFamily: ProviderRequestCompatibilityFamily | undefined;
if (provider && MOONSHOT_COMPAT_PROVIDERS.has(provider)) {
const isOllamaOpenAICompletions = provider === "ollama" && api === "openai-completions";
if (
provider &&
(MOONSHOT_COMPAT_PROVIDERS.has(provider) ||
(provider === "ollama" && isOllamaMoonshotCompatModel(input.modelId)))
) {
compatibilityFamily = "moonshot";
}
@@ -629,7 +638,9 @@ export function resolveProviderRequestCapabilities(
// Native endpoint class is the real signal here. Users can point a generic
// provider key at Moonshot or DashScope and still need streaming usage.
supportsNativeStreamingUsageCompat:
endpointClass === "moonshot-native" || endpointClass === "modelstudio-native",
endpointClass === "moonshot-native" ||
endpointClass === "modelstudio-native" ||
isOllamaOpenAICompletions,
compatibilityFamily,
};
}

View File

@@ -165,9 +165,7 @@ exit 0
OPENCLAW_PROFILE: "default",
HOME: "/Users/testuser",
});
expect(content).toContain(
"exec >>'/Users/testuser/.openclaw/logs/gateway-restart.log' 2>&1 || true",
);
expect(content).toContain("exec >>'/Users/testuser/.openclaw/logs/gateway-restart.log' 2>&1");
// Every launchctl call should allow output through now (no `2>/dev/null`)
// and the final kickstart must not swallow its exit code.
expect(content).not.toMatch(/launchctl[^\n]*2>\/dev\/null/);
@@ -185,10 +183,10 @@ exit 0
OPENCLAW_STATE_DIR: "/tmp/openclaw-state",
});
expect(content).toContain("mkdir -p '/tmp/openclaw-state/logs' 2>/dev/null || true");
expect(content).toContain(
"exec >>'/tmp/openclaw-state/logs/gateway-restart.log' 2>&1 || true",
"if mkdir -p '/tmp/openclaw-state/logs' 2>/dev/null && : >>'/tmp/openclaw-state/logs/gateway-restart.log' 2>/dev/null; then",
);
expect(content).toContain("exec >>'/tmp/openclaw-state/logs/gateway-restart.log' 2>&1");
await cleanupScript(scriptPath);
});

View File

@@ -41,9 +41,7 @@ describe("scheduleDetachedLaunchdRestartHandoff", () => {
expect(args[6]).toBe("9876");
expect(args[7]).toBe("ai.openclaw.gateway");
expect(args[1]).toContain('while kill -0 "$wait_pid" >/dev/null 2>&1; do');
expect(args[1]).toContain(
"exec >>'/Users/test/.openclaw/logs/gateway-restart.log' 2>&1 || true",
);
expect(args[1]).toContain("exec >>'/Users/test/.openclaw/logs/gateway-restart.log' 2>&1");
expect(args[1]).toContain("openclaw restart attempt source=launchd-handoff mode=kickstart");
expect(args[1]).toContain('launchctl enable "$service_target"');
expect(args[1]).toContain('if launchctl kickstart -k "$service_target"; then');

View File

@@ -40,10 +40,10 @@ describe("restart log conventions", () => {
HOME: "/Users/test's",
});
expect(setup).toContain("mkdir -p '/Users/test'\\''s/.openclaw/logs' 2>/dev/null || true");
expect(setup).toContain(
"exec >>'/Users/test'\\''s/.openclaw/logs/gateway-restart.log' 2>&1 || true",
"if mkdir -p '/Users/test'\\''s/.openclaw/logs' 2>/dev/null && : >>'/Users/test'\\''s/.openclaw/logs/gateway-restart.log' 2>/dev/null; then",
);
expect(setup).toContain("exec >>'/Users/test'\\''s/.openclaw/logs/gateway-restart.log' 2>&1");
});
it("renders CMD log setup with quoted paths", () => {

View File

@@ -31,8 +31,11 @@ export function shellEscapeRestartLogValue(value: string): string {
export function renderPosixRestartLogSetup(env: GatewayServiceEnv): string {
const logDir = path.dirname(resolveGatewayRestartLogPath(env));
const logPath = resolveGatewayRestartLogPath(env);
return `mkdir -p '${shellEscapeRestartLogValue(logDir)}' 2>/dev/null || true
exec >>'${shellEscapeRestartLogValue(logPath)}' 2>&1 || true`;
const escapedLogDir = shellEscapeRestartLogValue(logDir);
const escapedLogPath = shellEscapeRestartLogValue(logPath);
return `if mkdir -p '${escapedLogDir}' 2>/dev/null && : >>'${escapedLogPath}' 2>/dev/null; then
exec >>'${escapedLogPath}' 2>&1
fi`;
}
export function renderCmdRestartLogSetup(env: GatewayServiceEnv): {