This commit is contained in:
lienminhquang 2026-01-30 17:52:55 +08:00 committed by GitHub
commit 990c0cf3f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 535 additions and 0 deletions

View File

@ -46,6 +46,7 @@ See [Venice AI](/providers/venice).
- [GLM models](/providers/glm)
- [MiniMax](/providers/minimax)
- [Venius (Venice AI, privacy-focused)](/providers/venice)
- [PixelML (multi-model API)](/providers/pixelml)
- [Ollama (local models)](/providers/ollama)
## Transcription providers

141
docs/providers/pixelml.md Normal file
View File

@ -0,0 +1,141 @@
---
summary: "Use PixelML multi-model API in Clawdbot"
read_when:
- You want to use PixelML models in Clawdbot
- You need PixelML setup guidance
---
# PixelML
PixelML provides a unified OpenAI-compatible API for accessing multiple AI models including GPT-4o, Claude, and more.
## Features
- **Multi-model access**: Use GPT, Claude, and other models through a single API
- **OpenAI-compatible**: Standard `/v1` endpoints for easy integration
- **Streaming**: Supported on all models
- **Vision**: Supported on models with vision capability
## Setup
### 1. Get API Key
1. Sign up at [platform.pixelml.com](https://platform.pixelml.com)
2. Go to your account settings and create an API key
3. Copy your API key
### 2. Configure Clawdbot
**Option A: Environment Variable**
```bash
export PIXELML_API_KEY="your-api-key"
```
**Option B: Interactive Setup (Recommended)**
```bash
clawdbot onboard --auth-choice pixelml-api-key
```
This will:
1. Prompt for your API key (or use existing `PIXELML_API_KEY`)
2. Configure the provider automatically
3. Set the default model
**Option C: Non-interactive**
```bash
clawdbot onboard --non-interactive \
--auth-choice pixelml-api-key \
--pixelml-api-key "your-api-key"
```
### 3. Verify Setup
```bash
clawdbot chat --model pixelml/gpt-4o-mini "Hello, are you working?"
```
## Available Models
| Model ID | Name | Context | Features |
|----------|------|---------|----------|
| `gpt-4o-mini` | GPT-4o Mini | 128k | Vision |
| `gpt-4o` | GPT-4o | 128k | Vision |
| `claude-4.5-haiku` | Claude 4.5 Haiku | 200k | Vision |
| `claude-4.5-sonnet` | Claude 4.5 Sonnet | 200k | Vision, Reasoning |
## Model Selection
Change your default model anytime:
```bash
clawdbot models set pixelml/gpt-4o-mini
clawdbot models set pixelml/claude-4.5-sonnet
```
List all available models:
```bash
clawdbot models list | grep pixelml
```
## Usage Examples
```bash
# Use GPT-4o Mini (default)
clawdbot chat --model pixelml/gpt-4o-mini "Hello"
# Use Claude via PixelML
clawdbot chat --model pixelml/claude-4.5-sonnet "Write a poem"
# Use GPT-4o for vision tasks
clawdbot chat --model pixelml/gpt-4o
```
## Config File Example
```json5
{
env: { PIXELML_API_KEY: "your-api-key" },
agents: { defaults: { model: { primary: "pixelml/gpt-4o-mini" } } },
models: {
mode: "merge",
providers: {
pixelml: {
baseUrl: "https://ishi.pixelml.com/v1",
apiKey: "${PIXELML_API_KEY}",
api: "openai-completions",
models: [
{
id: "gpt-4o-mini",
name: "GPT-4o Mini",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 16384
}
]
}
}
}
}
```
## Troubleshooting
### API key not recognized
```bash
echo $PIXELML_API_KEY
clawdbot models list | grep pixelml
```
### Connection issues
PixelML API is at `https://ishi.pixelml.com/v1`. Ensure your network allows HTTPS connections.
## Links
- [PixelML](https://platform.pixelml.com)

View File

@ -284,6 +284,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
xiaomi: "XIAOMI_API_KEY",
synthetic: "SYNTHETIC_API_KEY",
venice: "VENICE_API_KEY",
pixelml: "PIXELML_API_KEY",
mistral: "MISTRAL_API_KEY",
opencode: "OPENCODE_API_KEY",
};

View File

@ -12,6 +12,7 @@ import {
SYNTHETIC_BASE_URL,
SYNTHETIC_MODEL_CATALOG,
} from "./synthetic-models.js";
import { discoverPixelmlModels, PIXELML_BASE_URL } from "./pixelml-models.js";
import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
type ModelsConfig = NonNullable<OpenClawConfig["models"]>;
@ -379,6 +380,15 @@ async function buildVeniceProvider(): Promise<ProviderConfig> {
};
}
async function buildPixelmlProvider(apiKey?: string): Promise<ProviderConfig> {
const models = await discoverPixelmlModels(apiKey);
return {
baseUrl: PIXELML_BASE_URL,
api: "openai-completions",
models,
};
}
async function buildOllamaProvider(): Promise<ProviderConfig> {
const models = await discoverOllamaModels();
return {
@ -431,6 +441,13 @@ export async function resolveImplicitProviders(params: {
providers.venice = { ...(await buildVeniceProvider()), apiKey: veniceKey };
}
const pixelmlKey =
resolveEnvApiKeyVarName("pixelml") ??
resolveApiKeyFromProfiles({ provider: "pixelml", store: authStore });
if (pixelmlKey) {
providers.pixelml = { ...(await buildPixelmlProvider(pixelmlKey)), apiKey: pixelmlKey };
}
const qwenProfiles = listProfilesForProvider(authStore, "qwen-portal");
if (qwenProfiles.length > 0) {
providers["qwen-portal"] = {

View File

@ -0,0 +1,188 @@
import type { ModelDefinitionConfig } from "../config/types.js";
export const PIXELML_BASE_URL = "https://ishi.pixelml.com/v1";
export const PIXELML_DEFAULT_MODEL_ID = "gpt-4o-mini";
export const PIXELML_DEFAULT_MODEL_REF = `pixelml/${PIXELML_DEFAULT_MODEL_ID}`;
// PixelML uses a unified pricing model; set to 0 as costs vary by model.
export const PIXELML_DEFAULT_COST = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
};
/**
* Static catalog of PixelML models.
* PixelML provides a unified OpenAI-compatible API for multiple AI models.
*
* This catalog serves as a fallback when the PixelML API is unreachable.
*/
export const PIXELML_MODEL_CATALOG = [
// GPT models
{
id: "gpt-4o-mini",
name: "GPT-4o Mini",
reasoning: false,
input: ["text", "image"],
contextWindow: 128000,
maxTokens: 16384,
},
{
id: "gpt-4o",
name: "GPT-4o",
reasoning: false,
input: ["text", "image"],
contextWindow: 128000,
maxTokens: 16384,
},
// Claude models
{
id: "claude-4.5-haiku",
name: "Claude 4.5 Haiku",
reasoning: false,
input: ["text", "image"],
contextWindow: 200000,
maxTokens: 8192,
},
{
id: "claude-4.5-sonnet",
name: "Claude 4.5 Sonnet",
reasoning: true,
input: ["text", "image"],
contextWindow: 200000,
maxTokens: 8192,
},
] as const;
export type PixelmlCatalogEntry = (typeof PIXELML_MODEL_CATALOG)[number];
/**
* Build a ModelDefinitionConfig from a PixelML catalog entry.
*/
export function buildPixelmlModelDefinition(entry: PixelmlCatalogEntry): ModelDefinitionConfig {
return {
id: entry.id,
name: entry.name,
reasoning: entry.reasoning,
input: [...entry.input],
cost: PIXELML_DEFAULT_COST,
contextWindow: entry.contextWindow,
maxTokens: entry.maxTokens,
};
}
// PixelML API response types
interface PixelmlModelCost {
input: number;
output: number;
}
interface PixelmlModelLimit {
context: number;
output: number;
}
interface PixelmlModelModalities {
input: Array<"text" | "image">;
output: Array<"text" | "image">;
}
interface PixelmlModel {
id: string;
name: string;
family?: string;
release_date?: string;
attachment?: boolean;
reasoning?: boolean;
temperature?: boolean;
tool_call?: boolean;
cost?: PixelmlModelCost;
limit?: PixelmlModelLimit;
modalities?: PixelmlModelModalities;
options?: Record<string, unknown>;
}
// Response can be either { data: [...] } or a flat array
type PixelmlModelsResponse = { data: PixelmlModel[] } | PixelmlModel[];
/**
* Discover models from PixelML API with fallback to static catalog.
* The /models endpoint requires authentication.
*/
export async function discoverPixelmlModels(apiKey?: string): Promise<ModelDefinitionConfig[]> {
// Skip API discovery in test environment
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
return PIXELML_MODEL_CATALOG.map(buildPixelmlModelDefinition);
}
// Without an API key, return the static catalog
if (!apiKey?.trim()) {
return PIXELML_MODEL_CATALOG.map(buildPixelmlModelDefinition);
}
try {
const response = await fetch(`${PIXELML_BASE_URL}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
signal: AbortSignal.timeout(5000),
});
if (!response.ok) {
console.warn(
`[pixelml-models] Failed to discover models: HTTP ${response.status}, using static catalog`,
);
return PIXELML_MODEL_CATALOG.map(buildPixelmlModelDefinition);
}
const rawData = (await response.json()) as PixelmlModelsResponse;
// Handle both { data: [...] } and flat array responses
const apiModels = Array.isArray(rawData) ? rawData : rawData.data;
if (!Array.isArray(apiModels) || apiModels.length === 0) {
console.warn("[pixelml-models] No models found from API, using static catalog");
return PIXELML_MODEL_CATALOG.map(buildPixelmlModelDefinition);
}
// Build models from API response
const models: ModelDefinitionConfig[] = [];
for (const apiModel of apiModels) {
// Extract model ID without provider prefix (e.g., "pixelml/gpt-5.1" -> "gpt-5.1")
const modelId = apiModel.id.includes("/")
? apiModel.id.split("/").slice(1).join("/")
: apiModel.id;
// Use API-provided modalities, filtering to only supported types (text, image)
const rawModalities = apiModel.modalities?.input ?? ["text"];
const inputModalities: Array<"text" | "image"> = rawModalities.filter(
(m): m is "text" | "image" => m === "text" || m === "image",
);
// Ensure at least "text" is present
if (inputModalities.length === 0) {
inputModalities.push("text");
}
// Use API-provided values with sensible defaults
models.push({
id: modelId,
name: apiModel.name || modelId,
reasoning: apiModel.reasoning ?? false,
input: inputModalities,
cost: {
input: apiModel.cost?.input ?? 0,
output: apiModel.cost?.output ?? 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: apiModel.limit?.context ?? 128000,
maxTokens: apiModel.limit?.output ?? 8192,
});
}
return models.length > 0 ? models : PIXELML_MODEL_CATALOG.map(buildPixelmlModelDefinition);
} catch (error) {
console.warn(`[pixelml-models] Discovery failed: ${String(error)}, using static catalog`);
return PIXELML_MODEL_CATALOG.map(buildPixelmlModelDefinition);
}
}

View File

@ -21,6 +21,7 @@ export type AuthChoiceGroupId =
| "minimax"
| "synthetic"
| "venice"
| "pixelml"
| "qwen";
export type AuthChoiceGroup = {
@ -72,6 +73,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
hint: "Privacy-focused (uncensored models)",
choices: ["venice-api-key"],
},
{
value: "pixelml",
label: "PixelML",
hint: "Multi-model API (GPT, Claude)",
choices: ["pixelml-api-key"],
},
{
value: "google",
label: "Google",
@ -154,6 +161,11 @@ export function buildAuthChoiceOptions(params: {
label: "Venice AI API key",
hint: "Privacy-focused inference (uncensored models)",
});
options.push({
value: "pixelml-api-key",
label: "PixelML API key",
hint: "Multi-model API (GPT, Claude via PixelML)",
});
options.push({
value: "github-copilot",
label: "GitHub Copilot (GitHub device login)",

View File

@ -21,6 +21,8 @@ import {
applyOpencodeZenProviderConfig,
applyOpenrouterConfig,
applyOpenrouterProviderConfig,
applyPixelmlConfig,
applyPixelmlProviderConfig,
applySyntheticConfig,
applySyntheticProviderConfig,
applyVeniceConfig,
@ -33,6 +35,7 @@ import {
KIMI_CODE_MODEL_REF,
MOONSHOT_DEFAULT_MODEL_REF,
OPENROUTER_DEFAULT_MODEL_REF,
PIXELML_DEFAULT_MODEL_REF,
SYNTHETIC_DEFAULT_MODEL_REF,
VENICE_DEFAULT_MODEL_REF,
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
@ -42,6 +45,7 @@ import {
setMoonshotApiKey,
setOpencodeZenApiKey,
setOpenrouterApiKey,
setPixelmlApiKey,
setSyntheticApiKey,
setVeniceApiKey,
setVercelAiGatewayApiKey,
@ -89,6 +93,8 @@ export async function applyAuthChoiceApiProviders(
authChoice = "synthetic-api-key";
} else if (params.opts.tokenProvider === "venice") {
authChoice = "venice-api-key";
} else if (params.opts.tokenProvider === "pixelml") {
authChoice = "pixelml-api-key";
} else if (params.opts.tokenProvider === "opencode") {
authChoice = "opencode-zen";
}
@ -576,6 +582,64 @@ export async function applyAuthChoiceApiProviders(
return { config: nextConfig, agentModelOverride };
}
if (authChoice === "pixelml-api-key") {
let hasCredential = false;
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "pixelml") {
await setPixelmlApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
hasCredential = true;
}
if (!hasCredential) {
await params.prompter.note(
[
"PixelML provides access to multiple AI models (GPT, Claude, etc.).",
"Get your API key at: https://platform.pixelml.com",
].join("\n"),
"PixelML",
);
}
const envKey = resolveEnvApiKey("pixelml");
if (envKey) {
const useExisting = await params.prompter.confirm({
message: `Use existing PIXELML_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true,
});
if (useExisting) {
await setPixelmlApiKey(envKey.apiKey, params.agentDir);
hasCredential = true;
}
}
if (!hasCredential) {
const key = await params.prompter.text({
message: "Enter PixelML API key",
validate: validateApiKeyInput,
});
await setPixelmlApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "pixelml:default",
provider: "pixelml",
mode: "api_key",
});
{
const applied = await applyDefaultModelChoice({
config: nextConfig,
setDefaultModel: params.setDefaultModel,
defaultModel: PIXELML_DEFAULT_MODEL_REF,
applyDefaultConfig: applyPixelmlConfig,
applyProviderConfig: applyPixelmlProviderConfig,
noteDefault: PIXELML_DEFAULT_MODEL_REF,
noteAgentModel,
prompter: params.prompter,
});
nextConfig = applied.config;
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
}
return { config: nextConfig, agentModelOverride };
}
if (authChoice === "opencode-zen") {
let hasCredential = false;
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") {

View File

@ -21,6 +21,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
"xiaomi-api-key": "xiaomi",
"synthetic-api-key": "synthetic",
"venice-api-key": "venice",
"pixelml-api-key": "pixelml",
"github-copilot": "github-copilot",
"copilot-proxy": "copilot-proxy",
"minimax-cloud": "minimax",

View File

@ -1,4 +1,10 @@
import { buildXiaomiProvider, XIAOMI_DEFAULT_MODEL_ID } from "../agents/models-config.providers.js";
import {
buildPixelmlModelDefinition,
PIXELML_BASE_URL,
PIXELML_DEFAULT_MODEL_REF,
PIXELML_MODEL_CATALOG,
} from "../agents/pixelml-models.js";
import {
buildSyntheticModelDefinition,
SYNTHETIC_BASE_URL,
@ -484,6 +490,91 @@ export function applyVeniceConfig(cfg: OpenClawConfig): OpenClawConfig {
};
}
/**
* Apply PixelML provider configuration without changing the default model.
* Registers PixelML models and sets up the provider, but preserves existing model selection.
*/
export function applyPixelmlProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig {
const models = { ...cfg.agents?.defaults?.models };
// Add all catalog models to the allowlist
for (const entry of PIXELML_MODEL_CATALOG) {
const modelRef = `pixelml/${entry.id}`;
if (!models[modelRef]) {
models[modelRef] = {};
}
}
// Set alias for the default model
models[PIXELML_DEFAULT_MODEL_REF] = {
...models[PIXELML_DEFAULT_MODEL_REF],
alias: models[PIXELML_DEFAULT_MODEL_REF]?.alias ?? "GPT-4o Mini",
};
const providers = { ...cfg.models?.providers };
const existingProvider = providers.pixelml;
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
const pixelmlModels = PIXELML_MODEL_CATALOG.map(buildPixelmlModelDefinition);
const mergedModels = [
...existingModels,
...pixelmlModels.filter(
(model) => !existingModels.some((existing) => existing.id === model.id),
),
];
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
string,
unknown
> as { apiKey?: string };
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
const normalizedApiKey = resolvedApiKey?.trim();
providers.pixelml = {
...existingProviderRest,
baseUrl: PIXELML_BASE_URL,
api: "openai-completions",
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
models: mergedModels.length > 0 ? mergedModels : pixelmlModels,
};
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
models,
},
},
models: {
mode: cfg.models?.mode ?? "merge",
providers,
},
};
}
/**
* Apply PixelML provider configuration AND set PixelML as the default model.
* Use this when PixelML is the primary provider choice during onboarding.
*/
export function applyPixelmlConfig(cfg: ClawdbotConfig): ClawdbotConfig {
const next = applyPixelmlProviderConfig(cfg);
const existingModel = next.agents?.defaults?.model;
return {
...next,
agents: {
...next.agents,
defaults: {
...next.agents?.defaults,
model: {
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
? {
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
}
: undefined),
primary: PIXELML_DEFAULT_MODEL_REF,
},
},
},
};
}
export function applyAuthProfileConfig(
cfg: OpenClawConfig,
params: {

View File

@ -112,6 +112,19 @@ export async function setVeniceApiKey(key: string, agentDir?: string) {
});
}
export async function setPixelmlApiKey(key: string, agentDir?: string) {
// Write to resolved agent dir so gateway finds credentials on startup.
upsertAuthProfile({
profileId: "pixelml:default",
credential: {
type: "api_key",
provider: "pixelml",
key,
},
agentDir: resolveAuthAgentDir(agentDir),
});
}
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash";
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";

View File

@ -1,3 +1,4 @@
export { PIXELML_DEFAULT_MODEL_ID, PIXELML_DEFAULT_MODEL_REF } from "../agents/pixelml-models.js";
export {
SYNTHETIC_DEFAULT_MODEL_ID,
SYNTHETIC_DEFAULT_MODEL_REF,
@ -11,6 +12,8 @@ export {
applyMoonshotProviderConfig,
applyOpenrouterConfig,
applyOpenrouterProviderConfig,
applyPixelmlConfig,
applyPixelmlProviderConfig,
applySyntheticConfig,
applySyntheticProviderConfig,
applyVeniceConfig,
@ -43,6 +46,7 @@ export {
setMoonshotApiKey,
setOpencodeZenApiKey,
setOpenrouterApiKey,
setPixelmlApiKey,
setSyntheticApiKey,
setVeniceApiKey,
setVercelAiGatewayApiKey,

View File

@ -17,6 +17,7 @@ export type AuthChoice =
| "kimi-code-api-key"
| "synthetic-api-key"
| "venice-api-key"
| "pixelml-api-key"
| "codex-cli"
| "apiKey"
| "gemini-api-key"
@ -72,6 +73,7 @@ export type OnboardOptions = {
minimaxApiKey?: string;
syntheticApiKey?: string;
veniceApiKey?: string;
pixelmlApiKey?: string;
opencodeZenApiKey?: string;
gatewayPort?: number;
gatewayBind?: GatewayBind;