mirror of
https://github.com/ValueCell-ai/ClawX.git
synced 2026-08-14 08:53:09 +00:00
Compare commits
6
Commits
v0.5.4
...
v0.4.2-beta.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bfbcfe099 | ||
|
|
4c7acaf52e | ||
|
|
faf8c9499e | ||
|
|
507913ebd4 | ||
|
|
a797a78df5 | ||
|
|
048e408c4a |
@@ -1,6 +1,10 @@
|
||||
import { getProviderConfig } from '../utils/provider-registry';
|
||||
import { getOpenClawProviderKeyForType, isOAuthProviderType } from '../utils/provider-keys';
|
||||
import type { ProviderConfig } from '../utils/secure-storage';
|
||||
import {
|
||||
piAiModelsJsonModelEntry,
|
||||
type PiAiModelCostRates,
|
||||
} from '../shared/pi-ai-model-cost';
|
||||
|
||||
export interface AgentProviderUpdatePayload {
|
||||
providerKey: string;
|
||||
@@ -8,7 +12,7 @@ export interface AgentProviderUpdatePayload {
|
||||
baseUrl: string;
|
||||
api: string;
|
||||
apiKey: string | undefined;
|
||||
models: Array<{ id: string; name: string }>;
|
||||
models: Array<{ id: string; name: string; cost: PiAiModelCostRates }>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,7 +46,7 @@ export function buildNonOAuthAgentProviderUpdate(
|
||||
baseUrl,
|
||||
api,
|
||||
apiKey: meta?.apiKeyEnv,
|
||||
models: modelId ? [{ id: modelId, name: modelId }] : [],
|
||||
models: modelId ? [piAiModelsJsonModelEntry(modelId)] : [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ import {
|
||||
updateAgentModelProvider,
|
||||
updateSingleAgentModelProvider,
|
||||
} from '../../utils/openclaw-auth';
|
||||
import {
|
||||
piAiModelsJsonModelEntry,
|
||||
type PiAiModelCostRates,
|
||||
} from '../../shared/pi-ai-model-cost';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { listAgentsSnapshot } from '../../utils/agent-config';
|
||||
|
||||
@@ -342,7 +346,7 @@ async function syncCustomProviderAgentModel(
|
||||
await updateAgentModelProvider(runtimeProviderKey, {
|
||||
baseUrl: normalizeProviderBaseUrl(config, config.baseUrl, config.apiProtocol || 'openai-completions'),
|
||||
api: config.apiProtocol || 'openai-completions',
|
||||
models: modelId ? [{ id: modelId, name: modelId }] : [],
|
||||
models: modelId ? [piAiModelsJsonModelEntry(modelId)] : [],
|
||||
apiKey: resolvedKey,
|
||||
});
|
||||
}
|
||||
@@ -411,7 +415,7 @@ async function buildAgentModelProviderEntry(
|
||||
): Promise<{
|
||||
baseUrl?: string;
|
||||
api?: string;
|
||||
models?: Array<{ id: string; name: string }>;
|
||||
models?: Array<{ id: string; name: string; cost: PiAiModelCostRates }>;
|
||||
apiKey?: string;
|
||||
authHeader?: boolean;
|
||||
} | null> {
|
||||
@@ -440,7 +444,7 @@ async function buildAgentModelProviderEntry(
|
||||
return {
|
||||
baseUrl,
|
||||
api,
|
||||
models: [{ id: modelId, name: modelId }],
|
||||
models: [piAiModelsJsonModelEntry(modelId)],
|
||||
apiKey,
|
||||
authHeader,
|
||||
};
|
||||
@@ -695,7 +699,7 @@ export async function syncDefaultProviderToRuntime(
|
||||
api,
|
||||
authHeader: targetProviderKey === 'minimax-portal' ? true : undefined,
|
||||
apiKey: targetProviderKey === 'minimax-portal' ? 'minimax-oauth' : 'qwen-oauth',
|
||||
models: defaultModelId ? [{ id: defaultModelId, name: defaultModelId }] : [],
|
||||
models: defaultModelId ? [piAiModelsJsonModelEntry(defaultModelId)] : [],
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to update models.json for OAuth provider "${targetProviderKey}":`, err);
|
||||
@@ -711,7 +715,7 @@ export async function syncDefaultProviderToRuntime(
|
||||
await updateAgentModelProvider(ock, {
|
||||
baseUrl: normalizeProviderBaseUrl(provider, provider.baseUrl, provider.apiProtocol || 'openai-completions'),
|
||||
api: provider.apiProtocol || 'openai-completions',
|
||||
models: modelId ? [{ id: modelId, name: modelId }] : [],
|
||||
models: modelId ? [piAiModelsJsonModelEntry(modelId)] : [],
|
||||
apiKey: providerKey,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Per-million-token rates expected by `@mariozechner/pi-ai` `calculateCost`.
|
||||
* Custom / synced catalog rows often omit pricing; zeros keep accounting stable
|
||||
* and avoid `Cannot read properties of undefined (reading 'input')` when usage
|
||||
* chunks arrive during openai-completions streaming.
|
||||
*/
|
||||
export const PI_AI_MODEL_ZERO_COST = {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
} as const;
|
||||
|
||||
export type PiAiModelCostRates = {
|
||||
input: number;
|
||||
output: number;
|
||||
cacheRead: number;
|
||||
cacheWrite: number;
|
||||
};
|
||||
|
||||
export function normalizePiAiModelCost(existing: unknown): PiAiModelCostRates {
|
||||
if (!existing || typeof existing !== 'object') {
|
||||
return { ...PI_AI_MODEL_ZERO_COST };
|
||||
}
|
||||
const record = existing as Record<string, unknown>;
|
||||
const num = (value: unknown) =>
|
||||
typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
||||
|
||||
return {
|
||||
input: num(record.input),
|
||||
output: num(record.output),
|
||||
cacheRead: num(record.cacheRead),
|
||||
cacheWrite: num(record.cacheWrite),
|
||||
};
|
||||
}
|
||||
|
||||
/** Entry shape suitable for OpenClaw agent `models.json` provider.models[]. */
|
||||
export function piAiModelsJsonModelEntry(
|
||||
id: string,
|
||||
name: string = id,
|
||||
): { id: string; name: string; cost: PiAiModelCostRates } {
|
||||
return { id, name, cost: normalizePiAiModelCost(undefined) };
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
isOAuthProviderType,
|
||||
isOpenClawOAuthPluginProviderKey,
|
||||
} from './provider-keys';
|
||||
import { normalizePiAiModelCost, type PiAiModelCostRates } from '../shared/pi-ai-model-cost';
|
||||
import { withConfigLock } from './config-mutex';
|
||||
|
||||
const AUTH_STORE_VERSION = 1;
|
||||
@@ -1753,7 +1754,7 @@ export async function batchSyncConfigFields(token: string): Promise<void> {
|
||||
type AgentModelProviderEntry = {
|
||||
baseUrl?: string;
|
||||
api?: string;
|
||||
models?: Array<{ id: string; name: string }>;
|
||||
models?: Array<{ id: string; name: string; cost?: PiAiModelCostRates }>;
|
||||
apiKey?: string;
|
||||
/** When true, pi-ai sends Authorization: Bearer instead of x-api-key */
|
||||
authHeader?: boolean;
|
||||
@@ -1788,7 +1789,11 @@ async function updateModelsJsonProviderEntriesForAgents(
|
||||
|
||||
const mergedModels = (entry.models ?? []).map((m) => {
|
||||
const prev = existingModels.find((e) => e.id === m.id);
|
||||
return prev ? { ...prev, id: m.id, name: m.name } : { ...m };
|
||||
const base = prev ? { ...prev, id: m.id, name: m.name } : { ...m };
|
||||
return {
|
||||
...base,
|
||||
cost: normalizePiAiModelCost((base as { cost?: unknown }).cost),
|
||||
};
|
||||
});
|
||||
|
||||
if (entry.baseUrl !== undefined) existing.baseUrl = entry.baseUrl;
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawx",
|
||||
"version": "0.4.2-alpha.0",
|
||||
"version": "0.4.2-beta.2",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@discordjs/opus",
|
||||
@@ -65,6 +65,7 @@
|
||||
"package:win": "pnpm run prep:win-binaries && pnpm run package && node scripts/run-electron-builder.mjs --win --publish never",
|
||||
"package:linux": "pnpm run package && node scripts/run-electron-builder.mjs --linux --publish never",
|
||||
"release": "pnpm run uv:download && pnpm run package && node scripts/run-electron-builder.mjs --publish always",
|
||||
"preversion": "node scripts/pre-version-fetch-tags.mjs",
|
||||
"version": "node scripts/assert-release-version.mjs",
|
||||
"version:patch": "pnpm version patch",
|
||||
"version:minor": "pnpm version minor",
|
||||
@@ -73,7 +74,7 @@
|
||||
"version:prerelease-alpha": "pnpm version prerelease --preid=alpha",
|
||||
"version:prerelease-beta": "pnpm version prerelease --preid=beta",
|
||||
"release:validate": "node scripts/assert-tag-matches-package.mjs",
|
||||
"postversion": "git push && git push --tags"
|
||||
"postversion": "node scripts/post-version-push.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* npm/pnpm `version` lifecycle hook: runs after package.json is bumped, before
|
||||
* `git tag`. Aborts if the target tag already exists so we never fail late on
|
||||
* `fatal: tag 'vX.Y.Z' already exists`.
|
||||
* `git tag`. Aborts if the target tag already exists locally or on origin so we
|
||||
* never fail late on `fatal: tag 'vX.Y.Z' already exists` or a rejected push.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { execFileSync, execSync } from 'node:child_process';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
@@ -18,6 +18,7 @@ function readPackageVersion() {
|
||||
|
||||
const version = process.env.npm_package_version || readPackageVersion();
|
||||
const tag = `v${version}`;
|
||||
const skipRemote = process.env.SKIP_RELEASE_REMOTE_CHECK === '1';
|
||||
|
||||
function localTagExists(t) {
|
||||
try {
|
||||
@@ -28,6 +29,17 @@ function localTagExists(t) {
|
||||
}
|
||||
}
|
||||
|
||||
function remoteTagExists(t) {
|
||||
try {
|
||||
const out = execFileSync('git', ['ls-remote', '--tags', 'origin', `refs/tags/${t}`], {
|
||||
encoding: 'utf8',
|
||||
}).trim();
|
||||
return out.length > 0;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (localTagExists(tag)) {
|
||||
console.error(`
|
||||
Release version check failed: git tag ${tag} already exists locally.
|
||||
@@ -42,4 +54,32 @@ Typical fixes:
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Release version OK: tag ${tag} is not present locally yet.`);
|
||||
if (!skipRemote) {
|
||||
const onRemote = remoteTagExists(tag);
|
||||
if (onRemote === null) {
|
||||
console.error(`
|
||||
Release version check failed: could not query origin for refs/tags/${tag}.
|
||||
|
||||
Ensure \`origin\` exists and you can reach the network, run
|
||||
\`pnpm run preversion\` / \`git fetch origin --tags\`, then retry.
|
||||
|
||||
To skip this check (offline only): SKIP_RELEASE_REMOTE_CHECK=1
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (onRemote) {
|
||||
console.error(`
|
||||
Release version check failed: tag ${tag} already exists on origin.
|
||||
|
||||
Bump to a version that is not on the remote yet (see \`git ls-remote --tags origin\`).
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (skipRemote) {
|
||||
console.log('Release version OK (remote check skipped): tag is not present locally.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`Release version OK: tag ${tag} is not present locally and not on origin.`);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* npm/pnpm `postversion`: push the current branch (set upstream if missing) and
|
||||
* only the new version tag — avoids \`git push --tags\` publishing unrelated tags.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
function readPackageVersion() {
|
||||
const raw = readFileSync(join(root, 'package.json'), 'utf8');
|
||||
return JSON.parse(raw).version;
|
||||
}
|
||||
|
||||
const version = process.env.npm_package_version || readPackageVersion();
|
||||
const tag = `v${version}`;
|
||||
|
||||
execFileSync('git', ['push', '-u', 'origin', 'HEAD'], { stdio: 'inherit' });
|
||||
execFileSync('git', ['push', 'origin', `refs/tags/${tag}`], { stdio: 'inherit' });
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* npm/pnpm `preversion`: fetch tags from origin so local state matches remote
|
||||
* before SemVer bump + assert-release-version remote checks.
|
||||
*/
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
const skip = process.env.SKIP_RELEASE_FETCH === '1';
|
||||
if (skip) {
|
||||
console.log('[pre-version-fetch-tags] Skip: SKIP_RELEASE_FETCH=1');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
execFileSync('git', ['fetch', 'origin', '--tags', '--prune'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
console.error(`
|
||||
[pre-version-fetch-tags] git fetch origin --tags failed.
|
||||
|
||||
Fix your network/remotes, or retry. To bypass (not recommended), run with
|
||||
SKIP_RELEASE_FETCH=1 — assert-release-version may still block on remote tags
|
||||
unless SKIP_RELEASE_REMOTE_CHECK=1.
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -34,7 +34,7 @@ describe('provider-model-sync', () => {
|
||||
baseUrl: 'https://api.moonshot.cn/v1',
|
||||
api: 'openai-completions',
|
||||
apiKey: 'MOONSHOT_API_KEY',
|
||||
models: [{ id: 'kimi-k2.6', name: 'kimi-k2.6' }],
|
||||
models: [{ id: 'kimi-k2.6', name: 'kimi-k2.6', cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } }],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -74,7 +74,7 @@ describe('provider-model-sync', () => {
|
||||
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
|
||||
api: 'openai-completions',
|
||||
apiKey: 'MODELSTUDIO_API_KEY',
|
||||
models: [{ id: 'qwen3.5-plus', name: 'qwen3.5-plus' }],
|
||||
models: [{ id: 'qwen3.5-plus', name: 'qwen3.5-plus', cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } }],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ describe('provider-runtime-sync refresh strategy', () => {
|
||||
expect.objectContaining({
|
||||
baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
|
||||
api: 'openai-completions',
|
||||
models: [{ id: 'ark-code-latest', name: 'ark-code-latest' }],
|
||||
models: [{ id: 'ark-code-latest', name: 'ark-code-latest', cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } }],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user