feat: add provider API service for managing provider accounts and keys feat: create provider runtime sync service for agent runtime management feat: introduce script execution service for running automation scripts feat: develop script store service for managing script metadata and storage feat: implement theme service for managing application theme settings feat: add updater service for handling application updates feat: create window service for managing application windows and their states
232 lines
7.1 KiB
TypeScript
232 lines
7.1 KiB
TypeScript
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import logManager from '@electron/service/logger';
|
|
import { providerApiService } from '@electron/service/provider-api-service';
|
|
import { listAgentsSnapshot } from '@electron/utils/agent-config';
|
|
import { ensureDir, ensureOpenClawRuntimeLayout, getOpenClawRuntimePaths } from '@electron/utils/paths';
|
|
import type { AgentSummary, AgentsSnapshot } from '@runtime/lib/agents';
|
|
import type { ProviderAccount } from '@runtime/lib/providers';
|
|
|
|
interface AgentRuntimeSyncMeta {
|
|
agentId: string;
|
|
providerAccountId: string | null;
|
|
modelRef: string | null;
|
|
inheritedModel: boolean;
|
|
runtimeDir: string;
|
|
}
|
|
|
|
export interface ProviderRuntimeSyncResult {
|
|
syncedAt: string;
|
|
agentCount: number;
|
|
defaultAccountId: string | null;
|
|
globalRegistryPath: string;
|
|
agents: AgentRuntimeSyncMeta[];
|
|
warnings: string[];
|
|
}
|
|
|
|
interface RuntimeProviderProfile {
|
|
id: string;
|
|
vendorId: string;
|
|
label: string;
|
|
authMode: string;
|
|
apiKey: string | null;
|
|
hasKey: boolean;
|
|
baseUrl?: string;
|
|
apiProtocol?: string;
|
|
headers?: Record<string, string>;
|
|
metadata?: ProviderAccount['metadata'];
|
|
}
|
|
|
|
const AGENT_RUNTIME_DIR_NAME = 'runtime';
|
|
const AUTH_PROFILES_FILE_NAME = 'auth-profiles.json';
|
|
const MODELS_FILE_NAME = 'models.json';
|
|
const OPENCLAW_FILE_NAME = 'openclaw.json';
|
|
const GLOBAL_REGISTRY_FILE_NAME = 'agents-runtime.json';
|
|
|
|
function writeJson(filePath: string, data: unknown): void {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
}
|
|
|
|
function getEnabledAccounts(accounts?: ProviderAccount[]): ProviderAccount[] {
|
|
const source = Array.isArray(accounts) ? accounts : providerApiService.getAccounts();
|
|
return source.filter((account) => account.enabled !== false);
|
|
}
|
|
|
|
function resolveEffectiveAccount(
|
|
agent: AgentSummary,
|
|
accounts: ProviderAccount[],
|
|
defaultAccountId: string | null,
|
|
): ProviderAccount | null {
|
|
if (agent.providerAccountId) {
|
|
return accounts.find((account) => account.id === agent.providerAccountId) ?? null;
|
|
}
|
|
|
|
if (defaultAccountId) {
|
|
return accounts.find((account) => account.id === defaultAccountId) ?? null;
|
|
}
|
|
|
|
return accounts[0] ?? null;
|
|
}
|
|
|
|
function buildRuntimeProfile(account: ProviderAccount | null): RuntimeProviderProfile[] {
|
|
if (!account) return [];
|
|
|
|
const apiKey = providerApiService.getApiKey(account.id).apiKey;
|
|
return [{
|
|
id: account.id,
|
|
vendorId: account.vendorId,
|
|
label: account.label,
|
|
authMode: account.authMode,
|
|
apiKey: apiKey || null,
|
|
hasKey: Boolean(apiKey),
|
|
baseUrl: account.baseUrl,
|
|
apiProtocol: account.apiProtocol,
|
|
headers: account.headers,
|
|
metadata: account.metadata,
|
|
}];
|
|
}
|
|
|
|
function buildRuntimeModelEntry(
|
|
agent: AgentSummary,
|
|
effectiveAccount: ProviderAccount | null,
|
|
defaultModelRef: string | null,
|
|
) {
|
|
const effectiveModelRef = agent.modelRef ?? effectiveAccount?.model ?? defaultModelRef ?? null;
|
|
if (!effectiveModelRef) {
|
|
return [];
|
|
}
|
|
|
|
return [{
|
|
id: effectiveModelRef,
|
|
modelRef: effectiveModelRef,
|
|
providerAccountId: effectiveAccount?.id ?? null,
|
|
vendorId: effectiveAccount?.vendorId ?? agent.vendorId ?? null,
|
|
baseUrl: effectiveAccount?.baseUrl ?? null,
|
|
apiProtocol: effectiveAccount?.apiProtocol ?? null,
|
|
headers: effectiveAccount?.headers ?? {},
|
|
fallbackModels: effectiveAccount?.fallbackModels ?? [],
|
|
fallbackAccountIds: effectiveAccount?.fallbackAccountIds ?? [],
|
|
inheritedModel: Boolean(agent.inheritedModel),
|
|
source: agent.overrideModelRef ? 'agent-override' : 'provider-default',
|
|
}];
|
|
}
|
|
|
|
function ensureAgentRuntimeDir(agent: AgentSummary): string {
|
|
const agentDir = agent.agentDir?.trim();
|
|
if (!agentDir) {
|
|
throw new Error(`Agent "${agent.id}" is missing agentDir`);
|
|
}
|
|
|
|
return ensureDir(path.join(agentDir, AGENT_RUNTIME_DIR_NAME));
|
|
}
|
|
|
|
function writeAgentRuntimeFiles(
|
|
agent: AgentSummary,
|
|
snapshot: AgentsSnapshot,
|
|
accounts: ProviderAccount[],
|
|
defaultAccountId: string | null,
|
|
syncedAt: string,
|
|
warnings: string[],
|
|
): AgentRuntimeSyncMeta {
|
|
const runtimeDir = ensureAgentRuntimeDir(agent);
|
|
const effectiveAccount = resolveEffectiveAccount(agent, accounts, defaultAccountId);
|
|
const profiles = buildRuntimeProfile(effectiveAccount);
|
|
const models = buildRuntimeModelEntry(agent, effectiveAccount, snapshot.defaultModelRef);
|
|
|
|
if (agent.providerAccountId && !effectiveAccount) {
|
|
warnings.push(`Agent "${agent.id}" references missing provider account "${agent.providerAccountId}"`);
|
|
}
|
|
|
|
if (!effectiveAccount) {
|
|
warnings.push(`Agent "${agent.id}" has no enabled provider account available`);
|
|
}
|
|
|
|
if (models.length === 0) {
|
|
warnings.push(`Agent "${agent.id}" has no model configured after runtime sync`);
|
|
}
|
|
|
|
writeJson(path.join(runtimeDir, AUTH_PROFILES_FILE_NAME), {
|
|
version: 1,
|
|
syncedAt,
|
|
agentId: agent.id,
|
|
defaultProfileId: effectiveAccount?.id ?? null,
|
|
profiles,
|
|
});
|
|
|
|
writeJson(path.join(runtimeDir, MODELS_FILE_NAME), {
|
|
version: 1,
|
|
syncedAt,
|
|
agentId: agent.id,
|
|
defaultModelRef: models[0]?.modelRef ?? null,
|
|
models,
|
|
});
|
|
|
|
writeJson(path.join(runtimeDir, OPENCLAW_FILE_NAME), {
|
|
version: 1,
|
|
syncedAt,
|
|
agentId: agent.id,
|
|
agentName: agent.name,
|
|
mainSessionKey: agent.mainSessionKey,
|
|
workspace: agent.workspace ?? null,
|
|
agentDir: agent.agentDir ?? null,
|
|
providerAccountId: effectiveAccount?.id ?? null,
|
|
defaultProviderAccountId: snapshot.defaultProviderAccountId,
|
|
modelRef: models[0]?.modelRef ?? null,
|
|
defaultModelRef: snapshot.defaultModelRef,
|
|
inheritedModel: Boolean(agent.inheritedModel),
|
|
});
|
|
|
|
return {
|
|
agentId: agent.id,
|
|
providerAccountId: effectiveAccount?.id ?? null,
|
|
modelRef: models[0]?.modelRef ?? null,
|
|
inheritedModel: Boolean(agent.inheritedModel),
|
|
runtimeDir,
|
|
};
|
|
}
|
|
|
|
export function syncProviderRuntimeSnapshot(options?: {
|
|
accounts?: ProviderAccount[];
|
|
defaultAccountId?: string | null;
|
|
snapshot?: AgentsSnapshot;
|
|
}): ProviderRuntimeSyncResult {
|
|
const syncedAt = new Date().toISOString();
|
|
const accounts = getEnabledAccounts(options?.accounts);
|
|
const defaultAccountId = options?.defaultAccountId ?? providerApiService.getDefault().accountId;
|
|
const snapshot = options?.snapshot ?? listAgentsSnapshot(accounts, defaultAccountId);
|
|
const warnings: string[] = [];
|
|
const runtimePaths = ensureOpenClawRuntimeLayout(getOpenClawRuntimePaths());
|
|
|
|
const agents = snapshot.agents.map((agent) =>
|
|
writeAgentRuntimeFiles(agent, snapshot, accounts, defaultAccountId, syncedAt, warnings),
|
|
);
|
|
|
|
const globalRegistryPath = path.join(runtimePaths.runtimeDir, GLOBAL_REGISTRY_FILE_NAME);
|
|
writeJson(globalRegistryPath, {
|
|
version: 1,
|
|
syncedAt,
|
|
defaultAccountId,
|
|
defaultModelRef: snapshot.defaultModelRef,
|
|
agents,
|
|
});
|
|
|
|
if (warnings.length > 0) {
|
|
logManager.warn('Provider runtime sync completed with warnings', warnings);
|
|
} else {
|
|
logManager.info('Provider runtime sync completed', {
|
|
agentCount: agents.length,
|
|
globalRegistryPath,
|
|
});
|
|
}
|
|
|
|
return {
|
|
syncedAt,
|
|
agentCount: agents.length,
|
|
defaultAccountId,
|
|
globalRegistryPath,
|
|
agents,
|
|
warnings,
|
|
};
|
|
}
|