Skip to content

Commit 4b61f5e

Browse files
authored
feat(openclaw): expand model catalog and refactor provider auth flow (#2)
- Add model metadata for Opus 4.5, Sonnet 4.5, Grok 4.1 Fast, MiniMax M2.7 - Refactor DEFAULT_SURF_MODELS to record-based MODEL_METADATA lookup - Extract buildSurfAuthResult helper for provider config patch - Return config patch on existing wallet (skip redundant setup prompt) - Remove autoEnableWhenConfiguredProviders from plugin manifest
1 parent 02cb0d4 commit 4b61f5e

3 files changed

Lines changed: 172 additions & 32 deletions

File tree

packages/x402-proxy/openclaw.plugin.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"name": "mpp/x402 Payments Proxy",
44
"description": "x402 and MPP payments, wallet tools, and paid inference proxying",
55
"providers": ["surf"],
6-
"autoEnableWhenConfiguredProviders": ["surf"],
76
"providerAuthChoices": [
87
{
98
"provider": "surf",

packages/x402-proxy/src/openclaw/defaults.ts

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,62 +25,168 @@ export const DEFAULT_SURF_UPSTREAM_URL = "https://surf.cascade.fyi/api/v1/infere
2525
export const DEFAULT_PROVIDER_PROTOCOL: PaymentProtocol = "mpp";
2626
export const DEFAULT_MPP_SESSION_BUDGET = "0.5";
2727

28-
export const DEFAULT_SURF_MODELS: Array<Omit<ModelEntry, "provider">> = [
29-
{
30-
id: "anthropic/claude-opus-4.6",
28+
/** Known model metadata for cost/capability enrichment. */
29+
const MODEL_METADATA: Record<string, Omit<ModelEntry, "provider" | "id">> = {
30+
"anthropic/claude-opus-4.6": {
3131
name: "Claude Opus 4.6",
3232
maxTokens: 200000,
3333
reasoning: true,
3434
input: ["text", "image"],
3535
cost: { input: 0.015, output: 0.075, cacheRead: 0.0015, cacheWrite: 0.01875 },
3636
contextWindow: 200000,
3737
},
38-
{
39-
id: "anthropic/claude-sonnet-4.6",
38+
"anthropic/claude-opus-4.5": {
39+
name: "Claude Opus 4.5",
40+
maxTokens: 200000,
41+
reasoning: true,
42+
input: ["text", "image"],
43+
cost: { input: 0.015, output: 0.075, cacheRead: 0.0015, cacheWrite: 0.01875 },
44+
contextWindow: 200000,
45+
},
46+
"anthropic/claude-sonnet-4.6": {
4047
name: "Claude Sonnet 4.6",
4148
maxTokens: 200000,
4249
reasoning: true,
4350
input: ["text", "image"],
4451
cost: { input: 0.003, output: 0.015, cacheRead: 0.0003, cacheWrite: 0.00375 },
4552
contextWindow: 200000,
4653
},
47-
{
48-
id: "x-ai/grok-4.20-beta",
54+
"anthropic/claude-sonnet-4.5": {
55+
name: "Claude Sonnet 4.5",
56+
maxTokens: 200000,
57+
reasoning: true,
58+
input: ["text", "image"],
59+
cost: { input: 0.003, output: 0.015, cacheRead: 0.0003, cacheWrite: 0.00375 },
60+
contextWindow: 200000,
61+
},
62+
"x-ai/grok-4.20-beta": {
4963
name: "Grok 4.20 Beta",
5064
maxTokens: 131072,
5165
reasoning: true,
5266
input: ["text"],
5367
cost: { input: 0.003, output: 0.015, cacheRead: 0, cacheWrite: 0 },
5468
contextWindow: 131072,
5569
},
56-
{
57-
id: "minimax/minimax-m2.5",
70+
"x-ai/grok-4.1-fast": {
71+
name: "Grok 4.1 Fast",
72+
maxTokens: 131072,
73+
reasoning: false,
74+
input: ["text"],
75+
cost: { input: 0.001, output: 0.005, cacheRead: 0, cacheWrite: 0 },
76+
contextWindow: 131072,
77+
},
78+
"minimax/minimax-m2.7": {
79+
name: "MiniMax M2.7",
80+
maxTokens: 1000000,
81+
reasoning: false,
82+
input: ["text"],
83+
cost: { input: 0.001, output: 0.005, cacheRead: 0, cacheWrite: 0 },
84+
contextWindow: 1000000,
85+
},
86+
"minimax/minimax-m2.5": {
5887
name: "MiniMax M2.5",
5988
maxTokens: 1000000,
6089
reasoning: false,
6190
input: ["text"],
6291
cost: { input: 0.001, output: 0.005, cacheRead: 0, cacheWrite: 0 },
6392
contextWindow: 1000000,
6493
},
65-
{
66-
id: "moonshotai/kimi-k2.5",
94+
"moonshotai/kimi-k2.5": {
6795
name: "Kimi K2.5",
6896
maxTokens: 131072,
6997
reasoning: true,
7098
input: ["text"],
7199
cost: { input: 0.002, output: 0.008, cacheRead: 0, cacheWrite: 0 },
72100
contextWindow: 131072,
73101
},
74-
{
75-
id: "z-ai/glm-5",
102+
"z-ai/glm-5": {
76103
name: "GLM-5",
77104
maxTokens: 128000,
78105
reasoning: false,
79106
input: ["text"],
80107
cost: { input: 0.001, output: 0.005, cacheRead: 0, cacheWrite: 0 },
81108
contextWindow: 128000,
82109
},
83-
];
110+
"z-ai/glm-5-turbo": {
111+
name: "GLM-5 Turbo",
112+
maxTokens: 128000,
113+
reasoning: false,
114+
input: ["text"],
115+
cost: { input: 0.001, output: 0.005, cacheRead: 0, cacheWrite: 0 },
116+
contextWindow: 128000,
117+
},
118+
"qwen/qwen-2.5-7b-instruct": {
119+
name: "Qwen 2.5 7B Instruct",
120+
maxTokens: 32768,
121+
reasoning: false,
122+
input: ["text"],
123+
cost: { input: 0.0005, output: 0.002, cacheRead: 0, cacheWrite: 0 },
124+
contextWindow: 131072,
125+
},
126+
"stepfun/step-3.5-flash": {
127+
name: "Step 3.5 Flash",
128+
maxTokens: 131072,
129+
reasoning: false,
130+
input: ["text"],
131+
cost: { input: 0.001, output: 0.005, cacheRead: 0, cacheWrite: 0 },
132+
contextWindow: 131072,
133+
},
134+
"xiaomi/mimo-v2-pro": {
135+
name: "MiMo V2 Pro",
136+
maxTokens: 131072,
137+
reasoning: true,
138+
input: ["text"],
139+
cost: { input: 0.001, output: 0.005, cacheRead: 0, cacheWrite: 0 },
140+
contextWindow: 131072,
141+
},
142+
};
143+
144+
const DEFAULT_CONTEXT_WINDOW = 131072;
145+
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
146+
147+
export function modelFromId(id: string): Omit<ModelEntry, "provider"> {
148+
const known = MODEL_METADATA[id];
149+
if (known) return { id, ...known };
150+
const raw = id.split("/").pop() ?? id;
151+
const name = raw.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
152+
return {
153+
id,
154+
name,
155+
maxTokens: DEFAULT_CONTEXT_WINDOW,
156+
reasoning: false,
157+
input: ["text"],
158+
cost: ZERO_COST,
159+
contextWindow: DEFAULT_CONTEXT_WINDOW,
160+
};
161+
}
162+
163+
/** Static fallback models used when upstream fetch fails. */
164+
export const DEFAULT_SURF_MODELS: Array<Omit<ModelEntry, "provider">> =
165+
Object.keys(MODEL_METADATA).map(modelFromId);
166+
167+
const MODELS_CACHE_TTL_MS = 5 * 60 * 1000;
168+
let modelsCache: { models: Array<Omit<ModelEntry, "provider">>; fetchedAt: number } | null = null;
169+
170+
export async function fetchUpstreamModels(
171+
upstreamUrl: string,
172+
): Promise<Array<Omit<ModelEntry, "provider">>> {
173+
if (modelsCache && Date.now() - modelsCache.fetchedAt < MODELS_CACHE_TTL_MS) {
174+
return modelsCache.models;
175+
}
176+
try {
177+
const res = await globalThis.fetch(`${upstreamUrl}/v1/models`, {
178+
signal: AbortSignal.timeout(5000),
179+
});
180+
if (!res.ok) return modelsCache?.models ?? DEFAULT_SURF_MODELS;
181+
const data = (await res.json()) as { data?: Array<{ id: string }> };
182+
if (!data.data?.length) return modelsCache?.models ?? DEFAULT_SURF_MODELS;
183+
const models = data.data.map((m) => modelFromId(m.id));
184+
modelsCache = { models, fetchedAt: Date.now() };
185+
return models;
186+
} catch {
187+
return modelsCache?.models ?? DEFAULT_SURF_MODELS;
188+
}
189+
}
84190

85191
export function resolveProviders(config: Record<string, unknown>): {
86192
providers: ResolvedProviderConfig[];

packages/x402-proxy/src/openclaw/plugin.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,49 @@ export function register(api: OpenClawPluginApi): void {
3333
const { providers, models: allModels } = resolveProviders(config);
3434
const defaultProvider = providers[0];
3535

36+
function buildSurfAuthResult(gatewayPort: number) {
37+
return {
38+
profiles: [
39+
{
40+
profileId: "surf:default",
41+
credential: {
42+
type: "api_key" as const,
43+
provider: "surf",
44+
key: "x402-proxy-managed",
45+
},
46+
},
47+
],
48+
configPatch: {
49+
models: {
50+
providers: {
51+
surf: {
52+
baseUrl: `http://localhost:${gatewayPort}${defaultProvider.baseUrl}`,
53+
api: "openai-completions" as const,
54+
authHeader: false,
55+
models: defaultProvider.models as Array<{
56+
id: string;
57+
name: string;
58+
reasoning: boolean;
59+
input: Array<"text" | "image">;
60+
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
61+
contextWindow: number;
62+
maxTokens: number;
63+
}>,
64+
},
65+
},
66+
},
67+
},
68+
defaultModel: defaultProvider.models[0]?.id,
69+
};
70+
}
71+
3672
const walletAuthMethod: ProviderAuthMethod = {
3773
id: "wallet-setup",
3874
label: "x402-proxy wallet setup",
3975
hint: "Generate or import a crypto wallet for paid inference",
4076
kind: "custom",
4177
run: async (ctx) => {
78+
const gatewayPort = ctx.config.gateway?.port ?? 18789;
4279
const existing = resolveWallet();
4380
if (existing.source !== "none") {
4481
const addresses = [
@@ -51,7 +88,7 @@ export function register(api: OpenClawPluginApi): void {
5188
`Wallet already configured (source: ${existing.source}).\n\n${addresses}`,
5289
"x402-proxy wallet",
5390
);
54-
return { profiles: [] };
91+
return buildSurfAuthResult(gatewayPort);
5592
}
5693

5794
const action = await ctx.prompter.select({
@@ -99,14 +136,7 @@ export function register(api: OpenClawPluginApi): void {
99136
.join("\n");
100137
await ctx.prompter.note(msg, "Wallet created");
101138

102-
return {
103-
profiles: [
104-
{
105-
profileId: "surf:x402-proxy",
106-
credential: { type: "api_key", provider: "surf", key: "x402-proxy-managed" },
107-
},
108-
],
109-
};
139+
return buildSurfAuthResult(gatewayPort);
110140
},
111141
};
112142

@@ -119,14 +149,19 @@ export function register(api: OpenClawPluginApi): void {
119149
resolveConfigApiKey: () => "x402-proxy-managed",
120150
catalog: {
121151
order: "simple",
122-
run: async () => ({
123-
provider: {
124-
baseUrl: provider.baseUrl,
125-
api: "openai-completions",
126-
authHeader: false,
127-
models: provider.models,
128-
},
129-
}),
152+
run: async (ctx) => {
153+
const { apiKey } = ctx.resolveProviderApiKey(provider.id);
154+
if (!apiKey) return null;
155+
return {
156+
provider: {
157+
baseUrl: provider.baseUrl,
158+
api: "openai-completions",
159+
authHeader: false,
160+
apiKey,
161+
models: provider.models,
162+
},
163+
};
164+
},
130165
},
131166
});
132167
}

0 commit comments

Comments
 (0)