Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
91249c8
Add Droid SDK provider WIP
0xSero May 14, 2026
33e12e4
Implement Droid SDK runtime mapping
0xSero May 14, 2026
d1d3e20
Address Droid model discovery and review comments
0xSero May 15, 2026
c72f19b
Fix Droid streaming and access modes
0xSero May 15, 2026
b1dabf8
Harden Droid session stop cleanup
0xSero May 15, 2026
6c25239
Merge branch 'main' into ai/droid-sdk-provider
0xSero May 15, 2026
af7ffb0
Split Droid adapter responsibilities
0xSero May 15, 2026
8939e57
Address Droid PR review comments
0xSero May 16, 2026
742ab64
Normalize Droid turn usage payload
0xSero May 16, 2026
0944b58
Harden Droid thinking and interrupt handling
0xSero May 16, 2026
a39cdf1
Preserve Droid cumulative token usage
0xSero May 16, 2026
282971c
Align Droid final block dedup keys
0xSero May 16, 2026
450fa08
Harden Droid session lifecycle
0xSero May 16, 2026
969aceb
Guard Droid turn state
0xSero May 16, 2026
a1c1457
Harden Droid streaming races
0xSero May 17, 2026
30725c3
Guard runtime mode normalization
0xSero May 17, 2026
23191e9
Harden Droid usage and shutdown handling
0xSero May 17, 2026
b17efc0
Fix Droid session races and access display
0xSero May 17, 2026
da7cc1d
Fix provider fallback runtime mode display
0xSero May 17, 2026
c1ad5f0
Fix Droid runtime mode presentation and final usage
0xSero May 17, 2026
0e5d6af
Fix Droid usage merge and session replacement
0xSero May 17, 2026
7bc4e12
Fix Droid discovery and turn usage accounting
0xSero May 17, 2026
d7b895e
Handle unknown Droid model providers
0xSero May 18, 2026
e6e7338
Fix Droid routing and discovery timeout
0xSero May 18, 2026
d5a6bf3
Handle pre-aborted Droid discovery signals
0xSero May 18, 2026
87453f2
Share Droid environment compaction
0xSero May 18, 2026
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