apps/server/src/provider/Drivers/PiDriver.ts
/**
* PiDriver — v1 `ProviderDriver` for the Pi coding agent, composing the
* orchestrator-v2 adapter (`PiAdapterV2`), the snapshot/probe layer
* (`PiProvider`), and Pi-backed text generation.
*
* Pi state (sessions, settings, extensions, auth) lives in the user's own
* `~/.pi/agent`, so continuation identity uses the default instance grouping.
*/
import { PiSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { HttpClient } from "effect/unstable/http";
import { ChildProcessSpawner } from "effect/unstable/process";
import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
import { ServerConfig } from "../../config.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { makePiTextGeneration } from "../../textGeneration/PiTextGeneration.ts";
import {
PiAdapterV2Driver,
type PiAdapterV2DriverEnv,
} from "../../orchestration-v2/Adapters/PiAdapterV2.ts";
import { ProviderDriverError } from "../Errors.ts";
import {
buildInitialPiProviderSnapshot,
checkPiProviderStatus,
enrichPiSnapshot,
} from "../Layers/PiProvider.ts";
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
import {
defaultProviderContinuationIdentity,
type ProviderDriver,
type ProviderInstance,
} from "../ProviderDriver.ts";
import type { ServerProviderDraft } from "../providerSnapshot.ts";
import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts";
import {
makePackageManagedProviderMaintenanceResolver,
resolveProviderMaintenanceCapabilitiesEffect,
} from "../providerMaintenance.ts";
import {
haveProviderSnapshotSettingsChanged,
makeProviderSnapshotSettingsSource,
type ProviderSnapshotSettings,
} from "../providerUpdateSettings.ts";
const decodePiSettings = Schema.decodeSync(PiSettings);
const DRIVER_KIND = ProviderDriverKind.make("pi");
const UPDATE = makePackageManagedProviderMaintenanceResolver({
provider: DRIVER_KIND,
npmPackageName: "@earendil-works/pi-coding-agent",
homebrewFormula: null,
nativeUpdate: null,
});
export type PiDriverEnv =
| PiAdapterV2DriverEnv
| BackgroundPolicy.BackgroundPolicy
| ChildProcessSpawner.ChildProcessSpawner
| FileSystem.FileSystem
| HttpClient.HttpClient
| Path.Path
| ServerConfig
| ServerSettingsService;
const withInstanceIdentity =
(input: {
readonly instanceId: ProviderInstance["instanceId"];
readonly displayName: string | undefined;
readonly accentColor: string | undefined;
readonly continuationGroupKey: string;
}) =>
(snapshot: ServerProviderDraft): ServerProvider => ({
...snapshot,
instanceId: input.instanceId,
driver: DRIVER_KIND,
...(input.displayName ? { displayName: input.displayName } : {}),
...(input.accentColor ? { accentColor: input.accentColor } : {}),
continuation: { groupKey: input.continuationGroupKey },
});
export const PiDriver: ProviderDriver<PiSettings, PiDriverEnv> = {
driverKind: DRIVER_KIND,
metadata: {
displayName: "Pi",
supportsMultipleInstances: true,
},
configSchema: PiSettings,
defaultConfig: (): PiSettings => decodePiSettings({}),
create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>
Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const httpClient = yield* HttpClient.HttpClient;
const { cwd } = yield* ServerConfig;
const serverSettings = yield* ServerSettingsService;
const processEnv = mergeProviderInstanceEnvironment(environment);
const continuationIdentity = defaultProviderContinuationIdentity({
driverKind: DRIVER_KIND,
instanceId,
});
const stampIdentity = withInstanceIdentity({
instanceId,
displayName,
accentColor,
continuationGroupKey: continuationIdentity.continuationKey,
});
const effectiveConfig = { ...config, enabled } satisfies PiSettings;
const maintenanceCapabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(UPDATE, {
binaryPath: effectiveConfig.binaryPath,
env: processEnv,
});
const orchestrationAdapter = yield* PiAdapterV2Driver.create({
instanceId,
displayName,
accentColor,
environment,
enabled,
config,
}).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: "Failed to build Pi orchestration adapter.",
cause,
}),
),
);
const textGeneration = yield* makePiTextGeneration(effectiveConfig, processEnv);
const checkProvider = checkPiProviderStatus(effectiveConfig, processEnv, cwd).pipe(
Effect.map(stampIdentity),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<PiSettings>>({
maintenanceCapabilities,
getSettings: snapshotSettings.getSettings,
streamSettings: snapshotSettings.streamSettings,
haveSettingsChanged: haveProviderSnapshotSettingsChanged,
initialSnapshot: (settings) =>
buildInitialPiProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)),
checkProvider,
enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) =>
enrichPiSnapshot({
snapshot: currentSnapshot,
maintenanceCapabilities,
enableProviderUpdateChecks: settings.enableProviderUpdateChecks,
publishSnapshot,
httpClient,
}),
}).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: "Failed to build Pi snapshot.",
cause,
}),
),
);
return {
instanceId,
driverKind: DRIVER_KIND,
continuationIdentity,
displayName,
accentColor,
enabled,
snapshot,
orchestrationAdapter,
textGeneration,
} satisfies ProviderInstance;
}),
};