feat: remove legacy OpenCode runtime
Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
This commit is contained in:
@@ -5,6 +5,7 @@ import { createCodingConversationStore } from '../coding-projects/conversation-s
|
||||
import { CodingProjectService } from '../coding-projects/project-service';
|
||||
import {
|
||||
createCodingProjectStore,
|
||||
type CodingProjectStore,
|
||||
type CodingProjectStorage,
|
||||
} from '../coding-projects/project-store';
|
||||
import { CodingConversationService } from '../coding-runtime/conversation-service';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
} from './coding-provider-auth';
|
||||
import { createCodingProductHost, type CodingProductComposition } from './coding-product-services';
|
||||
import { archivePiConversationSession } from '../coding-runtime/pi/resource-loader';
|
||||
import { resolveLegacyProjectModel } from '../coding-projects/legacy-v1';
|
||||
|
||||
export interface CodingCompositionPaths {
|
||||
executablePath: string;
|
||||
@@ -41,6 +43,7 @@ export interface CodingCompositionPaths {
|
||||
|
||||
export interface CreateCodingCompositionOptions {
|
||||
storage: CodingProjectStorage;
|
||||
projectStore?: CodingProjectStore;
|
||||
browser: AgentBrowserModule;
|
||||
paths: CodingCompositionPaths;
|
||||
localProxyCredential?: string;
|
||||
@@ -70,7 +73,7 @@ export function resolveCodingPiRuntimePaths(input: {
|
||||
export function createCodingComposition(
|
||||
options: CreateCodingCompositionOptions,
|
||||
): CodingProductComposition {
|
||||
const projectStore = createCodingProjectStore(options.storage);
|
||||
const projectStore = options.projectStore ?? createCodingProjectStore(options.storage);
|
||||
const attachments = new CodingAttachmentStore(
|
||||
path.join(options.paths.userDataDir, 'coding-runtime', 'attachments'),
|
||||
);
|
||||
@@ -156,6 +159,12 @@ export function createCodingComposition(
|
||||
})),
|
||||
});
|
||||
const projects = new CodingProjectService(projectStore, {
|
||||
migration: {
|
||||
resolveLegacyModel: async ({ legacyModel }) => resolveLegacyProjectModel(
|
||||
legacyModel,
|
||||
await getProviderService().listAccounts(),
|
||||
),
|
||||
},
|
||||
createConversationStore: conversationStoreForProject,
|
||||
onResourcesChanged: async (project) => {
|
||||
runtime.markResourcesStale();
|
||||
@@ -195,6 +204,10 @@ export function createCodingComposition(
|
||||
conversations,
|
||||
runtime,
|
||||
host,
|
||||
async sleep() {
|
||||
const conversationIds = runtime.getDiagnostics().workers.map((worker) => worker.conversationId);
|
||||
await Promise.allSettled(conversationIds.map((conversationId) => runtime.dispose(conversationId)));
|
||||
},
|
||||
async shutdown() {
|
||||
await subagents.close();
|
||||
await runtime.shutdown();
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface CodingProductComposition {
|
||||
conversations: CodingConversationService;
|
||||
runtime: CodingConversationRuntime;
|
||||
host: CodingProductHost;
|
||||
sleep(): Promise<void>;
|
||||
shutdown(): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import type { OpencodeManager } from '../opencode/manager';
|
||||
import type { OpencodeProjectStore } from '../opencode/project-store';
|
||||
import type { CodingProjectStore } from '../coding-projects/project-store';
|
||||
import type { HostEventBus } from './event-bus';
|
||||
import type { createWorksSubmissionBindingStore } from '../services/works-submission-binding';
|
||||
import type { DesignWorkspaceModule } from '../image-workspace/module';
|
||||
@@ -69,8 +68,7 @@ export interface AgentBrowserService {
|
||||
}
|
||||
|
||||
export interface HostApiContext {
|
||||
opencodeManager: OpencodeManager;
|
||||
opencodeProjectStore: OpencodeProjectStore;
|
||||
codingProjectStore: CodingProjectStore;
|
||||
eventBus: HostEventBus;
|
||||
mainWindow: BrowserWindow | null;
|
||||
agentBrowser?: AgentBrowserService;
|
||||
|
||||
@@ -7,7 +7,6 @@ export function shouldUseLoopbackHostApi(path: string, method = 'GET'): boolean
|
||||
const normalizedMethod = method.toUpperCase();
|
||||
const pathname = path.split('?', 1)[0] || path;
|
||||
if (pathname === '/api/events'
|
||||
|| pathname === '/api/opencode/events'
|
||||
|| pathname === '/api/coding/events') return true;
|
||||
if (pathname === '/api/coding/attachments'
|
||||
|| pathname.startsWith('/api/coding/attachments/')) return true;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { handleAiProxyRoutes } from './routes/ai-proxy';
|
||||
import { handleAiHardwareRoutes } from './routes/ai-hardware';
|
||||
import { handleAppRoutes } from './routes/app';
|
||||
import { handleAuthRoutes } from './routes/auth';
|
||||
import { handleOpencodeRoutes } from './routes/opencode';
|
||||
import { handleImageWorkspaceRoutes } from './routes/image-workspace';
|
||||
import { handleImagePromptMuseumRoutes } from './routes/image-prompt-museum';
|
||||
import { handleLearningRoutes } from './routes/learning';
|
||||
@@ -50,7 +49,6 @@ export const hostApiRouteHandlers: readonly HostApiRouteHandler[] = [
|
||||
handleCodingProjectRoutes,
|
||||
handleCodingConversationRoutes,
|
||||
handleCodingFileRoutes,
|
||||
handleOpencodeRoutes,
|
||||
handleSettingsRoutes,
|
||||
handleProviderRoutes,
|
||||
handleFileRoutes,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PORTS } from '../utils/config';
|
||||
|
||||
/**
|
||||
* Allowed CORS origins — only the Electron renderer (Vite dev or production)
|
||||
* and the local opencode runtime are permitted to make cross-origin requests.
|
||||
* and explicitly trusted local product surfaces may make cross-origin requests.
|
||||
*/
|
||||
const ALLOWED_ORIGINS = new Set([
|
||||
`http://127.0.0.1:${PORTS.NIANCODE_DEV}`,
|
||||
@@ -44,20 +44,11 @@ export function requireJsonContentType(req: IncomingMessage): boolean {
|
||||
export function setCorsHeaders(
|
||||
res: ServerResponse,
|
||||
origin?: string,
|
||||
runtimeUrl?: string,
|
||||
): void {
|
||||
// Only reflect the Origin header back if it is in the allow-list.
|
||||
// Omitting the header for unknown origins causes the browser to block
|
||||
// the response — this is the intended behavior for untrusted callers.
|
||||
let runtimeOrigin: string | null = null;
|
||||
if (runtimeUrl) {
|
||||
try {
|
||||
runtimeOrigin = new URL(runtimeUrl).origin;
|
||||
} catch {
|
||||
runtimeOrigin = null;
|
||||
}
|
||||
}
|
||||
if (origin && (ALLOWED_ORIGINS.has(origin) || origin === runtimeOrigin)) {
|
||||
if (origin && ALLOWED_ORIGINS.has(origin)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Vary', 'Origin');
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { realpath } from 'node:fs/promises';
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import type { AgentBrowserBounds, AgentBrowserFaultShape } from '../../../shared/agent-browser';
|
||||
import { normalizeProjectPath } from '../../opencode/project-store';
|
||||
import { normalizeCodingProjectPath } from '../../coding-projects/project-store';
|
||||
import type { HostApiContext } from '../context';
|
||||
import { hasRendererCapability } from '../renderer-capability';
|
||||
import { parseJsonBody, sendJson } from '../route-utils';
|
||||
@@ -71,7 +71,7 @@ function parseStringArray(value: unknown): string[] | undefined {
|
||||
}
|
||||
|
||||
async function resolveActiveProject(ctx: HostApiContext, requestedPath?: unknown) {
|
||||
const activeProject = await ctx.opencodeProjectStore.getActiveProject();
|
||||
const activeProject = await ctx.codingProjectStore.getActiveProject();
|
||||
if (!activeProject) {
|
||||
throw new AgentBrowserRouteError('PROJECT_NOT_ACTIVE', '请先打开一个项目。', 409);
|
||||
}
|
||||
@@ -93,7 +93,7 @@ async function resolveActiveProject(ctx: HostApiContext, requestedPath?: unknown
|
||||
} catch {
|
||||
throw new AgentBrowserRouteError('PROJECT_MISMATCH', '请求的项目目录不可用。', 403);
|
||||
}
|
||||
if (normalizeProjectPath(requestedRealPath) !== normalizeProjectPath(activeRealPath)) {
|
||||
if (normalizeCodingProjectPath(requestedRealPath) !== normalizeCodingProjectPath(activeRealPath)) {
|
||||
throw new AgentBrowserRouteError('PROJECT_MISMATCH', '智能体只能调试当前项目。', 403);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ async function ensureProjectStillActive(
|
||||
ctx: HostApiContext,
|
||||
project: { id: string; path: string },
|
||||
): Promise<void> {
|
||||
const activeProject = await ctx.opencodeProjectStore.getActiveProject();
|
||||
const activeProject = await ctx.codingProjectStore.getActiveProject();
|
||||
let activeRealPath: string | null = null;
|
||||
if (activeProject?.id === project.id) {
|
||||
try {
|
||||
@@ -119,7 +119,7 @@ async function ensureProjectStillActive(
|
||||
if (
|
||||
activeProject?.id === project.id
|
||||
&& activeRealPath
|
||||
&& normalizeProjectPath(activeRealPath) === normalizeProjectPath(project.path)
|
||||
&& normalizeCodingProjectPath(activeRealPath) === normalizeCodingProjectPath(project.path)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from '../../services/works-square-ai-gateway';
|
||||
import { proxyAwareFetch } from '../../utils/proxy-fetch';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { getOpencodeErrorKind } from '../../../shared/opencode-error-kind';
|
||||
import { isOpencodeUpstreamSaturated } from '../../../shared/opencode-error-details';
|
||||
import { getAIGatewayErrorKind } from '../../../shared/ai-gateway-error-kind';
|
||||
import { isAIGatewayUpstreamSaturated } from '../../../shared/ai-gateway-error-details';
|
||||
|
||||
const AI_PROXY_PREFIX = '/api/ai-proxy/v1';
|
||||
const MAX_ERROR_LOG_MESSAGE_LENGTH = 600;
|
||||
@@ -79,10 +79,10 @@ function isExpiredGatewayTokenResponse(status: number, bodyText: string): boolea
|
||||
}
|
||||
|
||||
function getForwardedOneApiStatus(status: number, bodyText: string): number {
|
||||
if (status === 429 && getOpencodeErrorKind(bodyText) === 'quota_exhausted') {
|
||||
if (status === 429 && getAIGatewayErrorKind(bodyText) === 'quota_exhausted') {
|
||||
return 402;
|
||||
}
|
||||
if (status === 429 && isOpencodeUpstreamSaturated(bodyText)) {
|
||||
if (status === 429 && isAIGatewayUpstreamSaturated(bodyText)) {
|
||||
return 400;
|
||||
}
|
||||
return status;
|
||||
|
||||
@@ -17,14 +17,13 @@ export async function handleAppRoutes(
|
||||
});
|
||||
res.write(': connected\n\n');
|
||||
ctx.eventBus.addSseClient(res);
|
||||
res.write(`event: opencode:status\ndata: ${JSON.stringify(ctx.opencodeManager.getStatus())}\n\n`);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/app/runtime-info' && req.method === 'GET') {
|
||||
sendJson(res, 200, {
|
||||
runtime: 'opencode',
|
||||
status: ctx.opencodeManager.getStatus(),
|
||||
runtime: 'pi',
|
||||
diagnostics: ctx.codingProducts?.runtime.getDiagnostics() ?? null,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,16 @@ export async function handleCodingProjectRoutes(
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (url.pathname === '/api/coding/projects/legacy-conversation-notice/acknowledge'
|
||||
&& req.method === 'POST') {
|
||||
const body = await parseJsonBody<{ projectId?: string }>(req);
|
||||
sendJson(res, 200, {
|
||||
snapshot: publicProjectSnapshot(
|
||||
await projects.acknowledgeLegacyConversationNotice(body.projectId ?? ''),
|
||||
),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (url.pathname === '/api/coding/projects/conversations' && req.method === 'GET') {
|
||||
sendJson(res, 200, {
|
||||
conversations: await conversations.listConversations(
|
||||
|
||||
@@ -5,7 +5,6 @@ import { extname, join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import type { HostApiContext } from '../context';
|
||||
import { parseJsonBody, sendJson } from '../route-utils';
|
||||
import { readProjectConfig } from '../../opencode/project-config';
|
||||
import { loadGameAssetCandidates } from '../../coding-projects/game-asset-browser';
|
||||
import {
|
||||
GameAssetReviewConflictError,
|
||||
@@ -128,13 +127,13 @@ export async function handleFileRoutes(
|
||||
): Promise<boolean> {
|
||||
if (url.pathname === '/api/files/game-asset-candidates' && req.method === 'GET') {
|
||||
try {
|
||||
const activeProject = await ctx.opencodeProjectStore.getActiveProject();
|
||||
const activeProject = await ctx.codingProjectStore.getActiveProject();
|
||||
if (!activeProject) {
|
||||
sendJson(res, 409, { success: false, error: 'No active project selected' });
|
||||
return true;
|
||||
}
|
||||
const config = await readProjectConfig(activeProject.path);
|
||||
if (config.status !== 'valid' || !config.config.initialized) {
|
||||
const config = await ctx.codingProducts?.projects.getConfig(activeProject.id);
|
||||
if (!config?.config.initialized) {
|
||||
sendJson(res, 403, { success: false, error: 'Game asset browsing is unavailable for an uninitialized project' });
|
||||
return true;
|
||||
}
|
||||
@@ -147,13 +146,13 @@ export async function handleFileRoutes(
|
||||
|
||||
if (url.pathname === '/api/files/game-asset-review' && (req.method === 'GET' || req.method === 'POST')) {
|
||||
try {
|
||||
const activeProject = await ctx.opencodeProjectStore.getActiveProject();
|
||||
const activeProject = await ctx.codingProjectStore.getActiveProject();
|
||||
if (!activeProject) {
|
||||
sendJson(res, 409, { success: false, error: 'No active project selected' });
|
||||
return true;
|
||||
}
|
||||
const config = await readProjectConfig(activeProject.path);
|
||||
if (config.status !== 'valid' || !config.config.initialized) {
|
||||
const config = await ctx.codingProducts?.projects.getConfig(activeProject.id);
|
||||
if (!config?.config.initialized) {
|
||||
sendJson(res, 403, { success: false, error: 'Game asset review is unavailable for an uninitialized project' });
|
||||
return true;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,10 +22,6 @@ import {
|
||||
NIANCODE_USER_MODEL_ACCOUNT_LABEL,
|
||||
normalizeImportedUserModelId,
|
||||
} from '../../../shared/user-model-config';
|
||||
import {
|
||||
withRuntimeAcceptanceTimeout,
|
||||
withRuntimeConfigCoordinator,
|
||||
} from '../../opencode/runtime-config-readiness';
|
||||
|
||||
const legacyProviderRoutesWarned = new Set<string>();
|
||||
|
||||
@@ -150,7 +146,7 @@ type ImportedUserModelConfig = {
|
||||
const WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE = 'works_square_ai_gateway';
|
||||
const WORKS_SQUARE_AI_GATEWAY_PROXY_CREDENTIAL_MODE = 'works_square_ai_gateway_proxy';
|
||||
const WORKS_SQUARE_AI_TOKEN_HEADER = 'X-Works-Square-AI-Token';
|
||||
const NIANCODE_USER_MODEL_API_KEY_ENV = 'NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY';
|
||||
const NIANCODE_USER_MODEL_API_KEY_ENV = 'NIANCODE_USER_MODELS_API_KEY';
|
||||
|
||||
class WorksSquareModelConfigError extends Error {
|
||||
constructor(
|
||||
@@ -242,7 +238,7 @@ function localAiProxyBaseUrl(): string {
|
||||
}
|
||||
|
||||
async function fetchCurrentUserModelConfig(accessToken: string): Promise<ImportedUserModelConfig> {
|
||||
const { response, payload } = await withRuntimeAcceptanceTimeout(async (signal) => {
|
||||
const { response, payload } = await withProviderRequestTimeout(async (signal) => {
|
||||
const response = await proxyAwareFetch(
|
||||
createWorksUrl('/api/auth/me/model-config').toString(),
|
||||
{
|
||||
@@ -267,18 +263,21 @@ async function fetchCurrentUserModelConfig(accessToken: string): Promise<Importe
|
||||
return normalizeImportedUserModelConfig(payload);
|
||||
}
|
||||
|
||||
async function refreshRunningRuntimeAfterProviderChange(ctx: HostApiContext): Promise<void> {
|
||||
async function refreshCodingRuntimeAfterProviderChange(ctx: HostApiContext): Promise<void> {
|
||||
ctx.codingProducts?.conversations.markProviderStale();
|
||||
if (ctx.opencodeManager.getStatus().state === 'stopped') return;
|
||||
}
|
||||
|
||||
async function withProviderRequestTimeout<T>(
|
||||
operation: (signal: AbortSignal) => Promise<T>,
|
||||
timeoutMs = 10_000,
|
||||
): Promise<T> {
|
||||
const controller = new AbortController();
|
||||
const timeoutError = new Error('Provider operation timed out');
|
||||
const timeout = setTimeout(() => controller.abort(timeoutError), timeoutMs);
|
||||
try {
|
||||
const status = await ctx.opencodeManager.restart();
|
||||
if (status.state !== 'running' || typeof status.pid !== 'number') {
|
||||
throw new Error('opencode runtime did not restart with the new provider configuration');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('[providers] Failed to restart opencode runtime after provider configuration changed', error);
|
||||
throw error;
|
||||
return await operation(controller.signal);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,10 +331,7 @@ export async function importCurrentUserModelConfig(
|
||||
}> {
|
||||
const providerService = getProviderService();
|
||||
const modelConfig = await fetchCurrentUserModelConfig(accessToken);
|
||||
return await withRuntimeConfigCoordinator(ctx.opencodeManager, async (lease) => {
|
||||
let operationMarkedPending = false;
|
||||
try {
|
||||
return await withRuntimeAcceptanceTimeout(async (signal) => {
|
||||
return await withProviderRequestTimeout(async (signal) => {
|
||||
signal.throwIfAborted();
|
||||
const useLocalAiProxy = modelConfig.credentialMode === WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE;
|
||||
const nowMs = Date.now();
|
||||
@@ -362,23 +358,9 @@ export async function importCurrentUserModelConfig(
|
||||
createdAt: existing?.createdAt ?? now,
|
||||
updatedAt: now,
|
||||
};
|
||||
const runtimeIsActive = ctx.opencodeManager.getStatus().state !== 'stopped';
|
||||
const runtimeAlreadyUsesLocalProxyApiKey = useLocalAiProxy
|
||||
&& ctx.opencodeManager.getRuntimeGenerationProvenance?.() === 'fresh';
|
||||
const shouldRestartRuntime = importedProviderRuntimeShapeChanged(existing, account)
|
||||
|| (!runtimeAlreadyUsesLocalProxyApiKey
|
||||
&& await importedProviderApiKeyChanged(providerService, existing, accountApiKey));
|
||||
const shouldRefreshRuntime = importedProviderRuntimeShapeChanged(existing, account)
|
||||
|| await importedProviderApiKeyChanged(providerService, existing, accountApiKey);
|
||||
signal.throwIfAborted();
|
||||
const refreshAlreadyPending = lease.isRefreshPending();
|
||||
const runtimeRefreshRequired = runtimeIsActive
|
||||
&& (shouldRestartRuntime || refreshAlreadyPending);
|
||||
const armStoppedApplyForNextFresh = !runtimeIsActive
|
||||
&& refreshAlreadyPending
|
||||
&& options.runtimeRefresh !== 'defer';
|
||||
if (runtimeRefreshRequired) {
|
||||
lease.markRefreshPending();
|
||||
operationMarkedPending = true;
|
||||
}
|
||||
|
||||
signal.throwIfAborted();
|
||||
if (useLocalAiProxy) {
|
||||
@@ -399,28 +381,16 @@ export async function importCurrentUserModelConfig(
|
||||
signal.throwIfAborted();
|
||||
|
||||
await providerService.setDefaultAccount(NIANCODE_USER_MODEL_ACCOUNT_ID);
|
||||
if (!runtimeRefreshRequired || options.runtimeRefresh === 'defer') {
|
||||
ctx.codingProducts?.conversations.markProviderStale();
|
||||
}
|
||||
signal.throwIfAborted();
|
||||
if (armStoppedApplyForNextFresh) {
|
||||
lease.markRefreshPending();
|
||||
}
|
||||
if (runtimeRefreshRequired && options.runtimeRefresh !== 'defer') {
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
if (shouldRefreshRuntime) {
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
signal.throwIfAborted();
|
||||
}
|
||||
|
||||
return {
|
||||
account: savedAccount,
|
||||
importedModels: modelConfig.models,
|
||||
runtimeRefreshRequired,
|
||||
runtimeRefreshRequired: shouldRefreshRuntime && options.runtimeRefresh === 'defer',
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
if (operationMarkedPending) lease.retainRefreshPending();
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -453,7 +423,7 @@ export async function handleProviderRoutes(
|
||||
try {
|
||||
const body = await parseJsonBody<{ account: ProviderAccount; apiKey?: string }>(req);
|
||||
const account = await providerService.createAccount(body.account, body.apiKey);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true, account });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
@@ -475,7 +445,7 @@ export async function handleProviderRoutes(
|
||||
return true;
|
||||
}
|
||||
await providerService.setDefaultAccount(body.accountId);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
@@ -629,7 +599,7 @@ export async function handleProviderRoutes(
|
||||
return true;
|
||||
}
|
||||
const nextAccount = await providerService.updateAccount(accountId, body.updates, body.apiKey);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true, account: nextAccount });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
@@ -642,12 +612,12 @@ export async function handleProviderRoutes(
|
||||
try {
|
||||
if (url.searchParams.get('apiKeyOnly') === '1') {
|
||||
await providerService._deleteProviderApiKeyInternal(accountId);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
return true;
|
||||
}
|
||||
await providerService.deleteAccount(accountId);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
@@ -677,7 +647,7 @@ export async function handleProviderRoutes(
|
||||
return true;
|
||||
}
|
||||
await providerService._setDefaultProviderInternal(body.providerId);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
@@ -765,7 +735,7 @@ export async function handleProviderRoutes(
|
||||
await providerService._setProviderApiKeyInternal(config.id, trimmedKey);
|
||||
}
|
||||
}
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
@@ -815,7 +785,7 @@ export async function handleProviderRoutes(
|
||||
await providerService._deleteProviderApiKeyInternal(providerId);
|
||||
}
|
||||
}
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
@@ -830,12 +800,12 @@ export async function handleProviderRoutes(
|
||||
await providerService._getProviderInternal(providerId);
|
||||
if (url.searchParams.get('apiKeyOnly') === '1') {
|
||||
await providerService._deleteProviderApiKeyInternal(providerId);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
return true;
|
||||
}
|
||||
await providerService._deleteProviderInternal(providerId);
|
||||
await refreshRunningRuntimeAfterProviderChange(ctx);
|
||||
await refreshCodingRuntimeAfterProviderChange(ctx);
|
||||
sendJson(res, 200, { success: true });
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { success: false, error: String(error) });
|
||||
|
||||
@@ -525,7 +525,7 @@ async function handleDownloadAssetToProject(
|
||||
const body = await parseJsonBody<DownloadAssetInput>(req);
|
||||
const projectId = readRequiredString(body.projectId, 'projectId');
|
||||
const fileName = assetZipFileName(slug);
|
||||
const projects = await ctx.opencodeProjectStore.listProjects();
|
||||
const projects = await ctx.codingProjectStore.listProjects();
|
||||
const project = projects.find((candidate) => candidate.id === projectId);
|
||||
if (!project) {
|
||||
throw new Error('Project not found');
|
||||
@@ -1081,7 +1081,7 @@ async function handlePublishProjectSource(
|
||||
return;
|
||||
}
|
||||
const appId = projectMetadata.app_id as string;
|
||||
const localProject = (await ctx.opencodeProjectStore.listProjects())
|
||||
const localProject = (await ctx.codingProjectStore.listProjects())
|
||||
.find((candidate) => candidate.id === projectId);
|
||||
if (!localProject) {
|
||||
sendPublishSourceFailure(res, 404, 'PROJECT_NOT_FOUND', '本地项目不存在,请重新选择项目。');
|
||||
@@ -1266,7 +1266,7 @@ async function handleStartReleaseJob(
|
||||
sendJson(res, 400, { success: false, code: 'PROJECT_ID_REQUIRED', error: '缺少项目标识。' });
|
||||
return;
|
||||
}
|
||||
const project = (await ctx.opencodeProjectStore.listProjects()).find((candidate) => candidate.id === projectId);
|
||||
const project = (await ctx.codingProjectStore.listProjects()).find((candidate) => candidate.id === projectId);
|
||||
if (!project) {
|
||||
sendJson(res, 404, { success: false, code: 'PROJECT_NOT_FOUND', error: '本地项目不存在。' });
|
||||
return;
|
||||
|
||||
@@ -33,7 +33,7 @@ export function startHostApiServer(ctx: HostApiContext, port = getPort('NIANCODE
|
||||
// Set origin-aware CORS headers early so every response
|
||||
// (including error responses) carries them consistently.
|
||||
const origin = req.headers.origin;
|
||||
setCorsHeaders(res, origin, ctx.opencodeManager.getStatus().url);
|
||||
setCorsHeaders(res, origin);
|
||||
|
||||
// CORS preflight — respond before auth so browsers can negotiate.
|
||||
if (req.method === 'OPTIONS') {
|
||||
|
||||
Reference in New Issue
Block a user