Add Droid SDK provider#2689
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Latest Droid follow-up fixes pushed in c72f19b:
Validation: |
|
Addressed the unresolved Droid cleanup review thread in b1dabf8. Added a regression test covering two resumed Droid sessions where one Validation:
Note: the first full-suite run hit a timeout in |
ApprovabilityVerdict: Needs human review This PR introduces a new Droid SDK provider integration with significant new functionality including session management, command execution, permission handling, and UI changes. New provider integrations of this scope warrant human review. You can customize Macroscope's approvability policy. Learn more. |
|
Split the Droid adapter by responsibility in af7ffb0:
The Droid implementation is now 989 lines total across focused modules instead of one nearly 900-line adapter file. Validation:
|
|
Addressed the two remaining Cursor Bugbot review threads in 8939e57:
Validation:
|
|
Addressed the remaining token-usage review thread in 742ab64:
Validation after this fix:
Earlier in this pass, full |
|
Follow-up fixes for the latest automated review threads:
Validation:
|
|
Follow-up for the latest Cursor Bugbot token usage thread:
Validation after this change:
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a39cdf1. Configure here.
|
Fixed the latest Cursor Bugbot thread:
Validation:
|
| const droid = yield* Effect.tryPromise({ | ||
| try: () => | ||
| typeof input.resumeCursor === "string" | ||
| ? sdk.resumeSession(input.resumeCursor, sdkOptions) |
There was a problem hiding this comment.
[warning] Resumed Droid sessions never apply the requested access level.
When runtime mode changes, the provider command reactor restarts the session with a resume cursor; this branch calls resumeSession with only sdkOptions, so the SDK can keep the previous autonomyLevel while T3 records and shows the new runtime mode. Please update the resumed session settings, or avoid resuming on access-mode changes, so permission behavior matches the selected mode.
| cumulativeTokenUsage: undefined, | ||
| }; | ||
| contextRef = context; | ||
| sessions.set(input.threadId, context); |
There was a problem hiding this comment.
[warning] Starting Droid again for the same thread overwrites the existing context without closing it.
If recovery/restart calls startSession while sessions already has this threadId, sessions.set(...) drops the old SDK session, and that process is no longer reachable by stopSession, stopAll, or the adapter finalizer. Please abort/close any previous context for the thread before installing the replacement.
| ...(context.activeTokenUsage ? { usage: context.activeTokenUsage } : {}), | ||
| }, | ||
| }); | ||
| } catch (cause) { |
There was a problem hiding this comment.
[warning] User interrupts are reported as provider failures.
interruptTurn aborts context.activeAbort, and the Droid SDK stream throws when that abort signal is observed; this catch then emits runtime.error plus turn.completed with state: "failed". Pressing interrupt/stop during a Droid turn leaves the thread in an error state, so please detect abort/interrupt errors and complete the turn as interrupted/cancelled instead.
| }); | ||
| } | ||
| const nextLength = Math.max(0, context.turns.length - numTurns); | ||
| context.turns.splice(nextLength); |
There was a problem hiding this comment.
[warning] Rollback only truncates T3's local snapshot, not the Droid session.
ProviderService.rollbackConversation routes user rollback requests here, but this implementation only removes entries from context.turns; the underlying context.droid session and resumeCursor still contain the supposedly removed conversation. The next Droid turn will continue with stale history, so please use the SDK rewind/fork APIs or fail rollback as unsupported until provider state can actually be rewound.
|
|
||
| const firstTextIndex = message.content.findIndex((block) => block.type === "text"); | ||
| const firstTextBlock = message.content[firstTextIndex]; | ||
| const completedItemId = |
There was a problem hiding this comment.
[warning] The completion dedup key differs from the streamed/final text key.
Final create_message text is stored in activeAssistantItems under ${messageId}-${index}, but this completion path switches to firstTextBlock.id when the SDK supplies a block id. In that case sendTurn later sees ${messageId}-${index} as incomplete and emits a second item.completed for the same assistant message; please use the same item key for completion tracking and emission.
| ...(message.outcome === "success" ? {} : { error: message.message }), | ||
| }, | ||
| }); | ||
| case DroidMessageType.Error: |
There was a problem hiding this comment.
[warning] Droid stream errors do not make the active turn fail.
The SDK yields Error as a normal stream message; this handler emits runtime.error, but nothing records that the active turn failed. When TurnComplete arrives, sendTurn still sets the session back to ready and emits turn.completed with state: "completed", so please track or throw stream errors so the final completion is failed and the session error state is preserved.
| try: async () => { | ||
| let session: DroidSession | undefined; | ||
| try { | ||
| session = await (options?.sdk ?? defaultSdk).createSession({ |
There was a problem hiding this comment.
[warning] Model discovery timeout does not cancel the SDK session creation.
Effect.timeoutOption can return after DROID_MODEL_DISCOVERY_TIMEOUT_MS, but the tryPromise body never passes an abort signal to createSession; if initialization is hung, no session is assigned and the spawned Droid process can keep running after the probe reports a timeout. Please wire cancellation into createSession({ abortSignal }) and close any partial session on interruption.
| selectedProviderByThreadId ?? threadProvider ?? ProviderDriverKind.make("codex"), | ||
| ); | ||
| const selectedProvider: ProviderDriverKind = lockedProvider ?? unlockedSelectedProvider; | ||
| const runtimeMode = normalizeRuntimeModeForProvider(selectedProvider, rawRuntimeMode); |
There was a problem hiding this comment.
[warning] Runtime mode normalization can persist a downgraded Droid mode before providers load.
serverConfig starts as null, so providerStatuses is temporarily empty and resolveSelectableProvider falls back to Codex. A persisted Droid draft/thread with rawRuntimeMode === "medium-access" is then normalized to auto-accept-edits here and the effect below persists that downgrade before the Droid provider snapshot arrives; please defer normalization until provider data is loaded, or resolve the selected instance's driver without defaulting to Codex.

Intention
Add Droid as a first-class T3 Code provider using Factory's TypeScript SDK: https://github.com/Factory-AI/droid-sdk-typescript
This is still WIP while we validate more real Droid permission, file, MCP, auth, and long-running streaming flows.
What this adds
droidto the shared provider, model, settings, runtime source, and driver contracts.createSession/resumeSessionfrom@factory/droid-sdkin a T3 provider adapter.gif,jpeg,png,webp) as base64 SDK image sources resolved through the existing attachment store.initResult.availableModels, including user/custom models, while preserving custom model ids so duplicate underlying models remain selectable.Implementation shape
The Droid adapter has been split by responsibility instead of keeping one large file:
DroidAdapter.ts: session lifecycle and adapter orchestration.provider/droid/DroidRuntimeEvents.ts: SDK message to T3 runtime event projection.provider/droid/DroidSdkMappings.ts: SDK model, access, reasoning, usage, approval, and user-input mappings.provider/droid/DroidAttachmentResolver.ts: attachment MIME validation and image loading.provider/droid/DroidAdapterTypes.ts: shared Droid adapter types.Security / safety notes
droid; prompt content is sent through the SDK rather than shell interpolation.Validation
droid exec --model glm-5.1 --cwd /tmp ...returneddroid-pong.HomeLab - GLM-5.1,HomeLab - Trinity-Large-Thinking,Direct - GPT-5.5-Fast-xHigh, andDirect - GPT-5.5-Low.numTurns; streaming, thinking, access-mode mapping, and custom model discovery were updated.bun fmtpassed.bun lintpassed with existing unrelated web warnings.bun typecheckpassed.cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts.bun run testpreviously passed; after rebasing, one full parallel run hit three unrelated web timeout failures, and rerunning those exact files in isolation passed.Note
Add Droid SDK provider with session management, event streaming, and runtime mode support
droidprovider driver (DroidDriver.ts) that probes the Droid CLI for version/auth status, discovers available models via@factory/droid-sdk, and returns a structuredServerProviderDraftwith install/auth state and model catalog.ProviderRuntimeEventemissions in real time, including content deltas, tool lifecycle, MCP auth/status, token usage snapshots, and errors (DroidRuntimeEvents.ts).medium-accessruntime mode mapped toAutonomyLevel.Medium; the web UI renders provider-specific access mode options and labels via newgetRuntimeModeConfig/getRuntimeModeOptionshelpers (runtimeModePresentation.ts).DroidSettingsto server settings contracts (disabled by default, withbinaryPathandcustomModels), registers the driver in the built-in driver list, and surfaces a WIP-badged Droid card in the settings UI.Macroscope summarized 282971c.
Note
Medium Risk
Adds a new provider driver/adapter plus new
RuntimeModevalue (medium-access) that flows through shared contracts and UI, so regressions could affect provider selection/runtime-mode handling across the app. Droid is disabled by default, but the adapter introduces new streaming/approval/user-input pathways and CLI probing/model discovery logic.Overview
Adds Droid as a first-class provider end-to-end: introduces
DroidSettingsand registers a new built-inDroidDriverbacked by@factory/droid-sdk, including managed snapshot/probing (CLI version/health + model discovery) and explicit “text generation not supported” errors.Implements a Droid provider adapter that can start/resume/stop/interrupt sessions, send turns with optional image attachments, translate Droid SDK stream messages into canonical runtime events (content/tool lifecycle/token usage/metadata/errors), and route SDK permission + ask-user callbacks through existing approval and structured user-input flows; includes thread snapshot + rollback support with tests.
Extends shared/runtime contracts with a new
RuntimeMode(medium-access) and updates Codex runtime mapping + the web composer/runtime-mode UI to be provider-aware (Droid exposes 4 access levels; other providers keep 3, with normalization when switching providers). Also adds Droid presence in provider pickers/settings and a newDroidIcon.Reviewed by Cursor Bugbot for commit 282971c. Bugbot is set up for automated code reviews on this repo. Configure here.