Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/src/app/DesktopLifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Cause from "effect/Cause";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Deferred from "effect/Deferred";
import { Deferred } from "effect";
import * as Layer from "effect/Layer";
import * as Ref from "effect/Ref";
import * as Scope from "effect/Scope";
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/backend/DesktopBackendManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type DesktopBackendBootstrap as DesktopBackendBootstrapValue,
} from "@t3tools/contracts";
import { assert, describe, it } from "@effect/vitest";
import * as Deferred from "effect/Deferred";
import { Deferred } from "effect";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as Data from "effect/Data";
import * as DateTime from "effect/DateTime";
import * as Deferred from "effect/Deferred";
import { Deferred } from "effect";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/updates/DesktopUpdates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { assert, describe, it } from "@effect/vitest";
import * as NodeServices from "@effect/platform-node/NodeServices";
import type { DesktopUpdateState } from "@t3tools/contracts";
import * as Cause from "effect/Cause";
import * as Deferred from "effect/Deferred";
import { Deferred } from "effect";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Fiber from "effect/Fiber";
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/window/DesktopApplicationMenu.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as Deferred from "effect/Deferred";
import { Deferred } from "effect";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
Expand Down
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.154",
"@earendil-works/pi-coding-agent": "^0.77.0",
"@effect/platform-bun": "catalog:",
"@effect/platform-node": "catalog:",
"@effect/platform-node-shared": "catalog:",
Expand Down
116 changes: 116 additions & 0 deletions apps/server/src/provider/Drivers/PiDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* PiDriver — `ProviderDriver` for the Pi coding-agent SDK runtime.
*
* The driver embeds Pi through `@earendil-works/pi-coding-agent` rather than
* spawning the TUI. This keeps Pi behind T3 Code's normal provider adapter
* boundary while preserving Pi's own session files, model registry, skills,
* extensions, and context-file discovery.
*
* @module provider/Drivers/PiDriver
*/
import { PiSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts";
import * as Crypto from "effect/Crypto";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";

import { makeUnsupportedTextGeneration } from "../../textGeneration/UnsupportedTextGeneration.ts";
import { ServerConfig } from "../../config.ts";
import { ProviderDriverError } from "../Errors.ts";
import { makePiAdapter } from "../Layers/PiAdapter.ts";
import { checkPiProviderStatus, makePendingPiProvider } from "../Layers/PiProvider.ts";
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts";
import {
defaultProviderContinuationIdentity,
type ProviderDriver,
type ProviderInstance,
} from "../ProviderDriver.ts";
import type { ServerProviderDraft } from "../providerSnapshot.ts";

const decodePiSettings = Schema.decodeSync(PiSettings);
const DRIVER_KIND = ProviderDriverKind.make("pi");
const SNAPSHOT_REFRESH_INTERVAL = Duration.minutes(5);
const MAINTENANCE = makeManualOnlyProviderMaintenanceCapabilities({
provider: DRIVER_KIND,
packageName: "@earendil-works/pi-coding-agent",
});

export type PiDriverEnv = Crypto.Crypto | ServerConfig;

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, enabled, config }) =>
Effect.gen(function* () {
const effectiveConfig = { ...config, enabled } satisfies PiSettings;
const continuationIdentity = defaultProviderContinuationIdentity({
driverKind: DRIVER_KIND,
instanceId,
});
const stampIdentity = withInstanceIdentity({
instanceId,
displayName,
accentColor,
continuationGroupKey: continuationIdentity.continuationKey,
});

const adapter = yield* makePiAdapter(effectiveConfig, { instanceId });
const textGeneration = makeUnsupportedTextGeneration("Pi");

const snapshot = yield* makeManagedServerProvider<PiSettings>({
maintenanceCapabilities: MAINTENANCE,
getSettings: Effect.succeed(effectiveConfig),
streamSettings: Stream.never,
haveSettingsChanged: () => false,
initialSnapshot: (settings) =>
makePendingPiProvider(settings).pipe(Effect.map(stampIdentity)),
checkProvider: checkPiProviderStatus(effectiveConfig).pipe(Effect.map(stampIdentity)),
refreshInterval: SNAPSHOT_REFRESH_INTERVAL,
}).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: `Failed to build Pi snapshot: ${cause.message ?? String(cause)}`,
cause,
}),
),
);

return {
instanceId,
driverKind: DRIVER_KIND,
continuationIdentity,
displayName,
accentColor,
enabled,
snapshot,
adapter,
textGeneration,
} satisfies ProviderInstance;
}),
};
Loading
Loading