mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix: add vercel ai gateway thinking profile
Adds a Vercel AI Gateway provider thinking-profile resolver for trusted OpenAI and Anthropic upstream refs, preserving catalog compat fallback for unsupported/base-only refs. Includes provider tests, docs, and changelog coverage. Supersedes #41561. Co-authored-by: Zcg2021 <80769518+Zcg2021@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
6d7a77dcf9
commit
9d1c5a77c2
@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Cron/Gateway: defer missed isolated agent-turn catch-up out of the channel startup window, so overdue cron work cannot starve Discord or Telegram while providers connect after a restart. Thanks @vincentkoc.
|
||||
- Heartbeat/cron: defer heartbeat turns while cron work is active or queued, add opt-in `heartbeat.skipWhenBusy` for subagent/nested lane pressure, and retry busy skips without advancing the schedule so local Ollama hosts do not run heartbeat and cron prompts concurrently. Fixes #50773. Thanks @scottgl9.
|
||||
- Agents/thinking: honor configured model `compat.supportedReasoningEfforts` entries that include `xhigh`, so custom OpenAI-compatible provider refs expose and validate `/think xhigh` consistently across command menus, Gateway sessions, agent CLI, and `llm-task`. Carries forward #48904. Thanks @Milchstrassse and @wufunc.
|
||||
- Vercel AI Gateway: expose provider-owned `/think xhigh` for trusted OpenAI/Codex upstream refs and Claude adaptive thinking for Anthropic upstream refs, while leaving untrusted namespaced refs on base levels. Carries forward #41561. Thanks @Zcg2021.
|
||||
- Plugins/runtime-deps: prune stale `openclaw-unknown-*` bundled runtime dependency roots during Gateway startup while keeping recent or locked roots, so old staging debris cannot keep growing across restarts. Thanks @vincentkoc.
|
||||
- Ollama: compose caller abort signals with guarded-fetch timeouts for native `/api/chat` streams, so `/stop` and early cancellation still interrupt local Ollama requests that also carry provider timeout budgets. Refs #74133. Thanks @obviyus.
|
||||
- Doctor/TTS: migrate legacy `messages.tts.enabled`, agent TTS, channel TTS, and voice-call plugin TTS toggles to `auto` mode during `openclaw doctor --fix`, matching the documented TTS config contract. Thanks @vincentkoc.
|
||||
|
||||
@@ -105,6 +105,15 @@ configuration. OpenClaw resolves the canonical form automatically.
|
||||
MoonshotAI. Your single `AI_GATEWAY_API_KEY` handles authentication for all
|
||||
upstream providers.
|
||||
</Accordion>
|
||||
<Accordion title="Thinking levels">
|
||||
`/think` options follow trusted upstream model prefixes when OpenClaw knows
|
||||
the upstream provider contract. `vercel-ai-gateway/anthropic/...` uses the
|
||||
Claude thinking profile, including adaptive defaults for Claude 4.6 models.
|
||||
`vercel-ai-gateway/openai/gpt-5.4`, `gpt-5.5`, and Codex-style refs expose
|
||||
`/think xhigh` just like the direct OpenAI/OpenAI Codex providers. Other
|
||||
namespaced refs keep the normal reasoning levels unless their catalog
|
||||
metadata declares more.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Related
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
buildStaticVercelAiGatewayProvider,
|
||||
buildVercelAiGatewayProvider,
|
||||
} from "./provider-catalog.js";
|
||||
import { resolveVercelAiGatewayThinkingProfile } from "./thinking.js";
|
||||
|
||||
const PROVIDER_ID = "vercel-ai-gateway";
|
||||
|
||||
@@ -35,5 +36,6 @@ export default defineSingleProviderPluginEntry({
|
||||
buildProvider: buildVercelAiGatewayProvider,
|
||||
buildStaticProvider: buildStaticVercelAiGatewayProvider,
|
||||
},
|
||||
resolveThinkingProfile: ({ modelId }) => resolveVercelAiGatewayThinkingProfile(modelId),
|
||||
},
|
||||
});
|
||||
|
||||
72
extensions/vercel-ai-gateway/thinking.test.ts
Normal file
72
extensions/vercel-ai-gateway/thinking.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
registerProviderPlugin,
|
||||
requireRegisteredProvider,
|
||||
} from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("vercel ai gateway thinking profile", () => {
|
||||
async function getProvider() {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin,
|
||||
id: "vercel-ai-gateway",
|
||||
name: "Vercel AI Gateway Provider",
|
||||
});
|
||||
return requireRegisteredProvider(providers, "vercel-ai-gateway");
|
||||
}
|
||||
|
||||
it("exposes xhigh for trusted OpenAI upstream refs", async () => {
|
||||
const provider = await getProvider();
|
||||
|
||||
const profile = provider.resolveThinkingProfile?.({
|
||||
provider: "vercel-ai-gateway",
|
||||
modelId: "openai/gpt-5.4",
|
||||
});
|
||||
|
||||
expect(profile?.levels).toEqual(expect.arrayContaining([{ id: "xhigh" }]));
|
||||
});
|
||||
|
||||
it("exposes Codex xhigh through the OpenAI upstream prefix", async () => {
|
||||
const provider = await getProvider();
|
||||
|
||||
const profile = provider.resolveThinkingProfile?.({
|
||||
provider: "vercel-ai-gateway",
|
||||
modelId: "openai/gpt-5.3-codex-spark",
|
||||
});
|
||||
|
||||
expect(profile?.levels).toEqual(expect.arrayContaining([{ id: "xhigh" }]));
|
||||
});
|
||||
|
||||
it("reuses Claude thinking defaults for trusted Anthropic upstream refs", async () => {
|
||||
const provider = await getProvider();
|
||||
|
||||
const profile = provider.resolveThinkingProfile?.({
|
||||
provider: "vercel-ai-gateway",
|
||||
modelId: "anthropic/claude-opus-4.6",
|
||||
});
|
||||
|
||||
expect(profile).toMatchObject({
|
||||
levels: expect.arrayContaining([{ id: "adaptive" }]),
|
||||
defaultLevel: "adaptive",
|
||||
});
|
||||
expect(profile?.levels.some((level) => level.id === "xhigh" || level.id === "max")).toBe(false);
|
||||
});
|
||||
|
||||
it("falls through for unsupported OpenAI or untrusted namespaced refs", async () => {
|
||||
const provider = await getProvider();
|
||||
const resolveThinkingProfile = provider.resolveThinkingProfile!;
|
||||
|
||||
expect(
|
||||
resolveThinkingProfile({
|
||||
provider: "vercel-ai-gateway",
|
||||
modelId: "openai/gpt-4.1",
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
resolveThinkingProfile({
|
||||
provider: "vercel-ai-gateway",
|
||||
modelId: "acme/gpt-5.4",
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
77
extensions/vercel-ai-gateway/thinking.ts
Normal file
77
extensions/vercel-ai-gateway/thinking.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { ProviderThinkingProfile } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
matchesExactOrPrefix,
|
||||
resolveClaudeThinkingProfile,
|
||||
} from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const UPSTREAM_OPENAI_PREFIX = "openai/";
|
||||
const UPSTREAM_ANTHROPIC_PREFIX = "anthropic/";
|
||||
|
||||
const BASE_OPENAI_THINKING_LEVELS = [
|
||||
{ id: "off" },
|
||||
{ id: "minimal" },
|
||||
{ id: "low" },
|
||||
{ id: "medium" },
|
||||
{ id: "high" },
|
||||
] as const satisfies ProviderThinkingProfile["levels"];
|
||||
|
||||
const VERCEL_OPENAI_XHIGH_MODEL_IDS = [
|
||||
"gpt-5.5",
|
||||
"gpt-5.5-pro",
|
||||
"gpt-5.4",
|
||||
"gpt-5.4-pro",
|
||||
"gpt-5.4-mini",
|
||||
"gpt-5.4-nano",
|
||||
"gpt-5.3-codex",
|
||||
"gpt-5.2",
|
||||
"gpt-5.2-codex",
|
||||
"gpt-5.1-codex",
|
||||
] as const;
|
||||
|
||||
function stripTrustedUpstreamPrefix(modelId: string, prefix: string): string | null {
|
||||
const normalized = normalizeLowercaseStringOrEmpty(modelId);
|
||||
if (!normalized.startsWith(prefix)) {
|
||||
return null;
|
||||
}
|
||||
const upstreamModelId = normalized.slice(prefix.length).trim();
|
||||
return upstreamModelId || null;
|
||||
}
|
||||
|
||||
function resolveOpenAiThinkingProfile(modelId: string): ProviderThinkingProfile | undefined {
|
||||
if (!matchesExactOrPrefix(modelId, VERCEL_OPENAI_XHIGH_MODEL_IDS)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
levels: [...BASE_OPENAI_THINKING_LEVELS, { id: "xhigh" }],
|
||||
};
|
||||
}
|
||||
|
||||
function hasVercelSpecificClaudeProfile(profile: ProviderThinkingProfile): boolean {
|
||||
return Boolean(
|
||||
profile.defaultLevel ||
|
||||
profile.levels.some(
|
||||
(level) => level.id === "adaptive" || level.id === "xhigh" || level.id === "max",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveVercelAiGatewayThinkingProfile(
|
||||
modelId: string,
|
||||
): ProviderThinkingProfile | undefined {
|
||||
const openAiModelId = stripTrustedUpstreamPrefix(modelId, UPSTREAM_OPENAI_PREFIX);
|
||||
if (openAiModelId) {
|
||||
return resolveOpenAiThinkingProfile(openAiModelId);
|
||||
}
|
||||
|
||||
const anthropicModelId = stripTrustedUpstreamPrefix(modelId, UPSTREAM_ANTHROPIC_PREFIX);
|
||||
if (anthropicModelId) {
|
||||
const profile = resolveClaudeThinkingProfile(anthropicModelId);
|
||||
// Returning a base-only provider profile would hide catalog compat metadata
|
||||
// from generic thinking resolution. Only take over when Claude has an
|
||||
// upstream-specific default or elevated level set.
|
||||
return hasVercelSpecificClaudeProfile(profile) ? profile : undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user