feat: implement menu service for context menu management
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
This commit is contained in:
231
electron/service/provider-runtime-sync.ts
Normal file
231
electron/service/provider-runtime-sync.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user