218 lines
7.9 KiB
TypeScript
218 lines
7.9 KiB
TypeScript
import path from 'node:path';
|
|
import type { AgentBrowserModule } from '../agent-browser';
|
|
import { CodingAttachmentStore } from '../coding-projects/attachment-store';
|
|
import { createCodingConversationStore } from '../coding-projects/conversation-store';
|
|
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';
|
|
import { PiManagedExtensionHost } from '../coding-runtime/pi/extension-host';
|
|
import { PiManagedInputRevisionCoordinator } from '../coding-runtime/pi/managed-input-revision';
|
|
import { PiProductTools } from '../coding-runtime/pi/product-tools';
|
|
import {
|
|
buildPiProviderCatalog,
|
|
resolvePiProviderCredentialFromSecretStore,
|
|
selectPiProviderModel,
|
|
} from '../coding-runtime/pi/provider-config';
|
|
import {
|
|
createPiManagedWorkerOpener,
|
|
PiConversationRuntime,
|
|
} from '../coding-runtime/pi/runtime';
|
|
import { PiSessionRegistry } from '../coding-runtime/pi/session-registry';
|
|
import { createPiManagedSubagentChildOpener } from '../coding-runtime/pi/subagent-child';
|
|
import { PiSubagentScheduler } from '../coding-runtime/pi/subagent';
|
|
import { PiProcessBudget, PiWorkerPool } from '../coding-runtime/pi/worker-pool';
|
|
import { getProviderService } from '../services/providers/provider-service';
|
|
import {
|
|
isCodingProviderAuthenticationError,
|
|
refreshCodingProviderCredential,
|
|
} 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;
|
|
cliPath: string;
|
|
userDataDir: string;
|
|
bundledSkillsDir: string;
|
|
}
|
|
|
|
export interface CreateCodingCompositionOptions {
|
|
storage: CodingProjectStorage;
|
|
projectStore?: CodingProjectStore;
|
|
browser: AgentBrowserModule;
|
|
paths: CodingCompositionPaths;
|
|
getLocalProxyCredential?(): string | undefined;
|
|
}
|
|
|
|
export function resolveCodingPiRuntimePaths(input: {
|
|
isPackaged: boolean;
|
|
resourcesPath: string;
|
|
appPath: string;
|
|
executablePath: string;
|
|
}): Pick<CodingCompositionPaths, 'executablePath' | 'cliPath'> {
|
|
return {
|
|
executablePath: input.executablePath,
|
|
cliPath: input.isPackaged
|
|
? path.join(input.resourcesPath, 'pi-runtime', 'dist', 'cli.js')
|
|
: path.join(
|
|
input.appPath,
|
|
'node_modules',
|
|
'@earendil-works',
|
|
'pi-coding-agent',
|
|
'dist',
|
|
'cli.js',
|
|
),
|
|
};
|
|
}
|
|
|
|
export function createCodingComposition(
|
|
options: CreateCodingCompositionOptions,
|
|
): CodingProductComposition {
|
|
const getLocalProxyCredential = options.getLocalProxyCredential;
|
|
const projectStore = options.projectStore ?? createCodingProjectStore(options.storage);
|
|
const attachments = new CodingAttachmentStore(
|
|
path.join(options.paths.userDataDir, 'coding-runtime', 'attachments'),
|
|
);
|
|
const productTools = new PiProductTools({
|
|
browser: options.browser,
|
|
attachments,
|
|
bundledSkillsDir: options.paths.bundledSkillsDir,
|
|
});
|
|
const extensionHost = new PiManagedExtensionHost();
|
|
extensionHost.configureProductTools(productTools);
|
|
const conversationStores = new Map<string, ReturnType<typeof createCodingConversationStore>>();
|
|
const conversationStoreForProject = (projectPath: string) => {
|
|
const existing = conversationStores.get(projectPath);
|
|
if (existing) return existing;
|
|
const created = createCodingConversationStore(projectPath);
|
|
conversationStores.set(projectPath, created);
|
|
return created;
|
|
};
|
|
const registry = new PiSessionRegistry({
|
|
projectStore,
|
|
createConversationStore: conversationStoreForProject,
|
|
});
|
|
const revisions = new PiManagedInputRevisionCoordinator();
|
|
const processBudget = new PiProcessBudget();
|
|
const loadProviderInput = async () => ({
|
|
accounts: await getProviderService().listAccounts(),
|
|
modelSummaries: [],
|
|
});
|
|
const workerPool = new PiWorkerPool({
|
|
processBudget,
|
|
revisionCoordinator: revisions,
|
|
openWorker: createPiManagedWorkerOpener({
|
|
registry,
|
|
executablePath: options.paths.executablePath,
|
|
cliPath: options.paths.cliPath,
|
|
userDataDir: options.paths.userDataDir,
|
|
bundledSkillsDir: options.paths.bundledSkillsDir,
|
|
loadProviderInput,
|
|
resolveCredential: resolvePiProviderCredentialFromSecretStore,
|
|
...(getLocalProxyCredential
|
|
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
|
|
: {}),
|
|
extensionHost,
|
|
}),
|
|
});
|
|
const childOpener = createPiManagedSubagentChildOpener({
|
|
projectStore,
|
|
executablePath: options.paths.executablePath,
|
|
cliPath: options.paths.cliPath,
|
|
userDataDir: options.paths.userDataDir,
|
|
bundledSkillsDir: options.paths.bundledSkillsDir,
|
|
extensionHost,
|
|
loadProviderInput,
|
|
resolveCredential: resolvePiProviderCredentialFromSecretStore,
|
|
getRevision: () => revisions.current,
|
|
...(getLocalProxyCredential
|
|
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
|
|
: {}),
|
|
});
|
|
const subagents = new PiSubagentScheduler({
|
|
openChild: childOpener,
|
|
processBudget,
|
|
reclaimProcessCapacity: (signal) => workerPool.reclaimIdleWorker(signal),
|
|
});
|
|
const runtime = new PiConversationRuntime({
|
|
pool: workerPool,
|
|
registry,
|
|
extensionHost,
|
|
subagentScheduler: subagents,
|
|
resolveModel: async (model) => selectPiProviderModel(
|
|
buildPiProviderCatalog(await loadProviderInput()),
|
|
model,
|
|
),
|
|
isAuthenticationError: isCodingProviderAuthenticationError,
|
|
refreshCredential: refreshCodingProviderCredential,
|
|
resolveImages: async (refs) => await Promise.all(refs.map(async ({ attachmentId }) => {
|
|
const record = await attachments.read(attachmentId);
|
|
return {
|
|
type: 'image',
|
|
data: record.data.toString('base64'),
|
|
mimeType: record.mime,
|
|
};
|
|
})),
|
|
});
|
|
const projects = new CodingProjectService(projectStore, {
|
|
migration: {
|
|
resolveLegacyModel: async ({ legacyModel }) => resolveLegacyProjectModel(
|
|
legacyModel,
|
|
await getProviderService().listAccounts(),
|
|
),
|
|
},
|
|
createConversationStore: conversationStoreForProject,
|
|
onResourcesChanged: async (project) => {
|
|
runtime.markResourcesStale();
|
|
const conversations = await conversationStoreForProject(project.path).read()
|
|
.then((file) => file.conversations)
|
|
.catch(() => []);
|
|
for (const conversation of conversations) registry.forget(conversation.id);
|
|
},
|
|
onProjectDeactivated: async (project) => {
|
|
const conversations = await conversationStoreForProject(project.path).read()
|
|
.then((file) => file.conversations)
|
|
.catch(() => []);
|
|
await Promise.allSettled([
|
|
options.browser.close(project.path),
|
|
...conversations.map(({ id }) => runtime.dispose(id)),
|
|
]);
|
|
},
|
|
});
|
|
const conversations = new CodingConversationService(projects, runtime, {
|
|
archiveSession: async ({ projectId, sessionKey }) => {
|
|
await archivePiConversationSession({
|
|
userDataDir: options.paths.userDataDir,
|
|
projectId,
|
|
sessionKey,
|
|
});
|
|
},
|
|
});
|
|
const host = createCodingProductHost({
|
|
projects,
|
|
productTools,
|
|
listPiCommands: (conversationId) => conversations.listLiveCommands(conversationId),
|
|
});
|
|
return {
|
|
attachments,
|
|
productTools,
|
|
projects,
|
|
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();
|
|
},
|
|
};
|
|
}
|