Skip to content
Open
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
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@effect/platform-node": "catalog:",
"@effect/platform-node-shared": "catalog:",
"@effect/sql-sqlite-bun": "catalog:",
"@factory/droid-sdk": "^0.2.0",
"@opencode-ai/sdk": "^1.3.15",
"@pierre/diffs": "catalog:",
"effect": "catalog:",
Expand Down
157 changes: 157 additions & 0 deletions apps/server/src/provider/Drivers/DroidDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
DroidSettings,
ProviderDriverKind,
TextGenerationError,
type ServerProvider,
} from "@t3tools/contracts";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";
import { HttpClient } from "effect/unstable/http";
import { ChildProcessSpawner } from "effect/unstable/process";

import { ServerConfig } from "../../config.ts";
import type { TextGenerationShape } from "../../textGeneration/TextGeneration.ts";
import { ProviderDriverError } from "../Errors.ts";
import { makeDroidAdapter } from "../Layers/DroidAdapter.ts";
import { checkDroidProviderStatus, makePendingDroidProvider } from "../Layers/DroidProvider.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 {
enrichProviderSnapshotWithVersionAdvisory,
makePackageManagedProviderMaintenanceResolver,
resolveProviderMaintenanceCapabilitiesEffect,
} from "../providerMaintenance.ts";

const decodeDroidSettings = Schema.decodeSync(DroidSettings);
const DRIVER_KIND = ProviderDriverKind.make("droid");
const SNAPSHOT_REFRESH_INTERVAL = Duration.minutes(5);
const UPDATE = makePackageManagedProviderMaintenanceResolver({
provider: DRIVER_KIND,
npmPackageName: "droid",
homebrewFormula: null,
nativeUpdate: null,
});

export type DroidDriverEnv =
| ChildProcessSpawner.ChildProcessSpawner
| FileSystem.FileSystem
| HttpClient.HttpClient
| 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 },
});

function makeUnsupportedTextGeneration(): TextGenerationShape {
const fail = (operation: TextGenerationError["operation"]) =>
Effect.fail(
new TextGenerationError({
operation,
detail: "Droid SDK text generation is not enabled in this WIP.",
}),
);
return {
generateCommitMessage: () => fail("generateCommitMessage"),
generatePrContent: () => fail("generatePrContent"),
generateBranchName: () => fail("generateBranchName"),
generateThreadTitle: () => fail("generateThreadTitle"),
};
}

export const DroidDriver: ProviderDriver<DroidSettings, DroidDriverEnv> = {
driverKind: DRIVER_KIND,
metadata: {
displayName: "Droid",
supportsMultipleInstances: true,
},
configSchema: DroidSettings,
defaultConfig: (): DroidSettings => decodeDroidSettings({}),
create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>
Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const httpClient = yield* HttpClient.HttpClient;
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 DroidSettings;
const maintenanceCapabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(UPDATE, {
binaryPath: effectiveConfig.binaryPath,
env: processEnv,
});

const adapter = yield* makeDroidAdapter(effectiveConfig, {
instanceId,
environment: processEnv,
});
const checkProvider = checkDroidProviderStatus(effectiveConfig, processEnv).pipe(
Effect.map(stampIdentity),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
const snapshot = yield* makeManagedServerProvider<DroidSettings>({
maintenanceCapabilities,
getSettings: Effect.succeed(effectiveConfig),
streamSettings: Stream.never,
haveSettingsChanged: () => false,
initialSnapshot: (settings) =>
makePendingDroidProvider(settings).pipe(Effect.map(stampIdentity)),
checkProvider,
enrichSnapshot: ({ snapshot, publishSnapshot }) =>
enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe(
Effect.provideService(HttpClient.HttpClient, httpClient),
Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)),
),
refreshInterval: SNAPSHOT_REFRESH_INTERVAL,
}).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: `Failed to build Droid snapshot: ${cause.message ?? String(cause)}`,
cause,
}),
),
);

return {
instanceId,
driverKind: DRIVER_KIND,
continuationIdentity,
displayName,
accentColor,
enabled,
snapshot,
adapter,
textGeneration: makeUnsupportedTextGeneration(),
} satisfies ProviderInstance;
}),
};
2 changes: 2 additions & 0 deletions apps/server/src/provider/Layers/CodexSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ function runtimeModeToThreadConfig(input: RuntimeMode): {
sandbox: "read-only",
};
case "auto-accept-edits":
case "medium-access":
return {
approvalPolicy: "on-request",
sandbox: "workspace-write",
Expand Down Expand Up @@ -307,6 +308,7 @@ function runtimeModeToTurnSandboxPolicy(
type: "readOnly",
};
case "auto-accept-edits":
case "medium-access":
return {
type: "workspaceWrite",
};
Expand Down
Loading
Loading