Skip to content

Add Devin provider integration - #47

Merged
ranvier2d2 merged 6 commits into
mainfrom
feat/devin-provider-integration
Mar 31, 2026
Merged

Add Devin provider integration#47
ranvier2d2 merged 6 commits into
mainfrom
feat/devin-provider-integration

Conversation

@ranvier2d2

@ranvier2d2 ranvier2d2 commented Mar 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add the Devin direct provider, API client, and adapter wiring on the server
  • add contracts and web support for the synthetic devin-default model and settings surfaces
  • add focused tests for Devin snapshot checks, adapter behavior, and provider model wiring

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun x vitest run apps/server/src/provider/devinApi.test.ts apps/server/src/provider/Layers/DevinProvider.test.ts apps/server/src/provider/Layers/DevinAdapter.test.ts packages/contracts/src/provider.test.ts packages/contracts/src/settings.test.ts packages/shared/src/model.test.ts
  • bun x vitest run src/components/chat/composerProviderRegistry.test.tsx src/providerModels.test.ts src/session-logic.test.ts (from apps/web)

Summary by CodeRabbit

  • New Features

    • Devin is now available as a supported AI provider alongside existing options like Claude and Codex.
    • Added Devin provider configuration, model selection support, and settings integration.
    • Enable and configure Devin in settings with your API credentials and organization ID.
  • Documentation

    • Updated provider documentation with Devin integration details, polling policies, and setup recommendations.

@coderabbitai

coderabbitai Bot commented Mar 30, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@ranvier2d2 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 44 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 44 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f36bf17d-fa4a-45cd-8423-264fc5f2706e

📥 Commits

Reviewing files that changed from the base of the PR and between 993a0d6f81055c8545dc01c4122b9a433af9c8ad and 335e69b.

📒 Files selected for processing (41)
  • README.md
  • ai_docs/tasks/007_devin_provider_integration.md
  • apps/server/integration/TestProviderAdapter.integration.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
  • apps/server/src/provider/Layers/DevinAdapter.test.ts
  • apps/server/src/provider/Layers/DevinAdapter.ts
  • apps/server/src/provider/Layers/DevinProvider.test.ts
  • apps/server/src/provider/Layers/DevinProvider.ts
  • apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderAdapterRegistry.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
  • apps/server/src/provider/Layers/ProviderService.ts
  • apps/server/src/provider/Services/DevinAdapter.ts
  • apps/server/src/provider/Services/DevinProvider.ts
  • apps/server/src/provider/devinApi.test.ts
  • apps/server/src/provider/devinApi.ts
  • apps/server/src/provider/providerCapabilities.ts
  • apps/server/src/provider/providerSnapshot.ts
  • apps/server/src/serverLayers.ts
  • apps/server/src/serverSettings.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/Icons.tsx
  • apps/web/src/components/KeybindingsToast.browser.tsx
  • apps/web/src/components/chat/ProviderModelPicker.tsx
  • apps/web/src/components/chat/composerProviderRegistry.test.tsx
  • apps/web/src/components/chat/composerProviderRegistry.tsx
  • apps/web/src/composerDraftStore.ts
  • apps/web/src/hooks/useSettings.ts
  • apps/web/src/modelSelection.ts
  • apps/web/src/providerModels.test.ts
  • apps/web/src/providerModels.ts
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/session-logic.test.ts
  • apps/web/src/session-logic.ts
  • apps/web/src/store.ts
  • packages/contracts/src/model.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/provider.test.ts
  • packages/contracts/src/settings.test.ts
  • packages/contracts/src/settings.ts
  • packages/shared/src/model.test.ts
📝 Walkthrough

Walkthrough

This pull request adds comprehensive Devin AI provider integration to the platform, including service definitions, an Effect-based adapter layer with session/message management and polling, an HTTP API client, provider configuration resolution, test coverage, and frontend UI components for provider selection and model discovery.

Changes

Cohort / File(s) Summary
Documentation & Formatting
ai_docs/tasks/007_devin_provider_integration.md
Whitespace and alignment adjustments in tables and sections without substantive content changes.
Devin API Client
apps/server/src/provider/devinApi.ts, apps/server/src/provider/devinApi.test.ts
New Effect-based HTTP client for Devin API with typed request/response contracts, error handling, and methods for session/message management. Tests verify endpoint construction, authentication, response normalization, and error payloads.
Service Definitions
apps/server/src/provider/Services/DevinProvider.ts, apps/server/src/provider/Services/DevinAdapter.ts
New service map classes defining provider and adapter shapes, enabling registration/composition within the effect service container.
Provider Layer & Configuration
apps/server/src/provider/Layers/DevinProvider.ts, apps/server/src/provider/Layers/DevinProvider.test.ts
Provider layer resolving Devin settings from environment and server config, checking API authentication/org validation, and wiring live status updates. Tests validate configuration resolution, API error handling, and provider registration.
Adapter Layer & Session Management
apps/server/src/provider/Layers/DevinAdapter.ts, apps/server/src/provider/Layers/DevinAdapter.test.ts
Effect-based adapter managing session lifecycle, message polling with deduplication, remote attachment uploads, and streaming events. Comprehensive tests verify lazy session creation, message reuse, duplicate suppression, and provider error/status mapping.
Provider Registry & Capabilities
apps/server/src/provider/Layers/ProviderRegistry.ts, apps/server/src/provider/Layers/ProviderAdapterRegistry.ts, apps/server/src/provider/providerCapabilities.ts
Integration of Devin into provider/adapter registries and definition of Devin capability constraints (no user input, rollback, approval; no session model switching; basic resume/attachment support).
Server Routing & Settings
apps/server/src/provider/Layers/ProviderService.ts, apps/server/src/serverLayers.ts, apps/server/src/serverSettings.ts, apps/server/src/provider/providerSnapshot.ts, apps/server/integration/TestProviderAdapter.integration.ts
Routing updates to map Devin to direct adapter path, layer composition changes, fallback provider ordering, custom model handling for unknown providers, and harness test fixture selection logic.
Frontend UI Components
apps/web/src/components/Icons.tsx, apps/web/src/components/ChatView.tsx, apps/web/src/components/KeybindingsToast.browser.tsx, apps/web/src/components/chat/ProviderModelPicker.tsx, apps/web/src/components/chat/composerProviderRegistry.tsx, apps/web/src/components/chat/composerProviderRegistry.test.tsx
New Devin icon component and updates to provider model discovery, picker icon mapping, and composer registry to handle Devin provider rendering and state management.
Frontend Model Selection & Settings
apps/web/src/modelSelection.ts, apps/web/src/providerModels.ts, apps/web/src/providerModels.test.ts, apps/web/src/routes/_chat.settings.tsx, apps/web/src/session-logic.ts, apps/web/src/session-logic.test.ts, apps/web/src/hooks/useSettings.ts, apps/web/src/store.ts
Devin model handling, synthetic fallback model introduction, provider options list, custom model config gating, legacy settings migration, and provider-specific UI treatment in settings page.
Contract & Schema Updates
packages/contracts/src/orchestration.ts, packages/contracts/src/model.ts, packages/contracts/src/settings.ts, packages/contracts/src/provider.test.ts, packages/contracts/src/settings.test.ts, packages/shared/src/model.test.ts
Addition of Devin to provider kind union, model selection schemas, settings schemas, default models/capabilities, and comprehensive test coverage for decoding/validation.

Sequence Diagram

sequenceDiagram
    participant Client as Client/Frontend
    participant Adapter as DevinAdapter<br/>(Session Manager)
    participant LocalCtx as Local Session<br/>Context (Map)
    participant DevinAPI as Devin API<br/>Client
    participant RemoteAPI as Devin Remote<br/>API Endpoint

    Client->>Adapter: sendTurn(text, attachments)
    activate Adapter
    
    alt First Turn (No Session)
        Adapter->>LocalCtx: Initialize session context
        Adapter->>DevinAPI: createAttachment(file)
        DevinAPI->>RemoteAPI: POST /v3/.../attachments
        RemoteAPI-->>DevinAPI: attachment ID
        
        Adapter->>DevinAPI: createSession(attachments, text)
        DevinAPI->>RemoteAPI: POST /v3/.../sessions
        RemoteAPI-->>DevinAPI: session summary
        
        Adapter->>LocalCtx: Store remote binding
        Adapter->>Client: emit thread.started
    else Subsequent Turns
        Adapter->>DevinAPI: sendSessionMessage(text)
        DevinAPI->>RemoteAPI: POST /v3/.../messages
        RemoteAPI-->>DevinAPI: void
    end
    
    Adapter->>Adapter: Start polling fiber
    Adapter-->>Client: emit turn.started
    deactivate Adapter
    
    par Polling Loop
        loop Every poll interval
            Adapter->>DevinAPI: getSession()
            DevinAPI->>RemoteAPI: GET /v3/.../sessions/{id}
            RemoteAPI-->>DevinAPI: session state
            
            Adapter->>DevinAPI: listSessionMessages(pagination)
            DevinAPI->>RemoteAPI: GET /v3/.../messages
            RemoteAPI-->>DevinAPI: messages page
            
            Adapter->>Adapter: Deduplicate by event_id
            Adapter->>Client: emit item.started,<br/>content.delta,<br/>item.completed
            Adapter->>Adapter: Update local state
            Adapter->>Client: emit session.state.changed
            
            alt Session Complete
                Adapter->>Client: emit turn.completed
                Adapter->>Adapter: Stop polling
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

size:XXL, vouch:trusted

Poem

🐰 A new provider hops into the warren,
Devin brings sessions that poll and don't tarry,
With deduped messages and models so keen,
The finest new adapter we've ever seen!
Layers and schemas all neatly combined,
One thoughtful integration, brilliantly designed. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides a clear summary of changes and validation steps, but does not follow the provided template structure with distinct 'What Changed', 'Why', and 'UI Changes' sections. Consider restructuring the description to explicitly follow the template sections (What Changed, Why, UI Changes, Checklist) for consistency with repository conventions.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add Devin provider integration' is clear, concise, and accurately summarizes the main objective of the changeset: integrating Devin as a new provider across client, server, and contract layers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/devin-provider-integration

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 30, 2026
@ranvier2d2
ranvier2d2 force-pushed the feat/devin-provider-integration branch from 993a0d6 to 661aa98 Compare March 30, 2026 23:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
apps/server/src/provider/providerSnapshot.ts (1)

87-95: Hoist normalized-provider set to module scope.

knownProviders is recreated on each call. Hoisting it reduces repeated allocations and keeps intent centralized.

♻️ Suggested refactor
+const NORMALIZED_MODEL_PROVIDERS = new Set<ProviderKind>([
+  "codex",
+  "claudeAgent",
+  "cursor",
+  "opencode",
+]);
+
 export function providerModelsFromSettings(
   builtInModels: ReadonlyArray<ServerProviderModel>,
   provider: ServerProvider["provider"],
   customModels: ReadonlyArray<string>,
 ): ReadonlyArray<ServerProviderModel> {
-  const knownProviders = new Set<ProviderKind>(["codex", "claudeAgent", "cursor", "opencode"]);
   const resolvedBuiltInModels = [...builtInModels];
   const seen = new Set(resolvedBuiltInModels.map((model) => model.slug));
   const customEntries: ServerProviderModel[] = [];

   for (const candidate of customModels) {
-    const normalized = knownProviders.has(provider as ProviderKind)
+    const normalized = NORMALIZED_MODEL_PROVIDERS.has(provider as ProviderKind)
       ? normalizeModelSlug(candidate, provider as ProviderKind)
       : (nonEmptyTrimmed(candidate) ?? null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/providerSnapshot.ts` around lines 87 - 95, The Set
named knownProviders is being recreated on every call; hoist it to module scope
by declaring a single constant Set<ProviderKind> (e.g., knownProviders) at the
top-level of the module so it is allocated once and reused; update any
references in providerSnapshot.ts that currently use the local knownProviders
(the loop using normalizeModelSlug(candidate, provider as ProviderKind) and the
conditional knownProviders.has(provider as ProviderKind)) to use the
module-scoped constant and remove the local declaration to avoid redundant
allocations and centralize intent.
apps/web/src/routes/_chat.settings.tsx (1)

81-136: Centralize the provider custom-model metadata.

supportsCustomModels, placeholders, and the fallback copy now live partly here and partly in apps/web/src/modelSelection.ts, and this branch still hard-codes Devin-specific text. Pull the shared bits into one exported config so the picker and settings page can’t drift the next time a provider changes.

Also applies to: 1286-1326

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_chat.settings.tsx` around lines 81 - 136, The provider
custom-model metadata is duplicated between PROVIDER_SETTINGS (type
InstallProviderSettings and the array) and modelSelection.ts; centralize
supportsCustomModels, customModelPlaceholder and any fallback/harnessDescription
text into one exported shared config so both the settings page and the model
picker import the same source. Create a single exported ProviderModelConfig (or
similar) that contains provider keys (e.g.,
"codex","claudeAgent","cursor","opencode","devin") with supportsCustomModels,
customModelPlaceholder and fallback copy, replace the duplicated fields in
PROVIDER_SETTINGS with references/imports to that shared export, and update
modelSelection.ts to import the same config instead of hard-coding
Devin-specific text. Ensure unique symbols to change are
InstallProviderSettings, PROVIDER_SETTINGS, and the usages in modelSelection.ts.
apps/server/src/provider/Layers/DevinAdapter.ts (1)

51-63: Unbounded seenMessageEventIds Set may cause memory growth in long-running sessions.

The seenMessageEventIds Set grows indefinitely as messages are received. For long-running Devin sessions with many messages, this could lead to significant memory consumption.

Consider implementing a bounded cache (e.g., LRU with a size limit) or periodically pruning old entries based on lastMessageCursor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/DevinAdapter.ts` around lines 51 - 63, The
seenMessageEventIds Set in DevinSessionContext is unbounded and can grow without
limit; replace it with a bounded cache (e.g., an LRU or size-limited set) and
evict old IDs as new ones are added to prevent memory growth. Concretely, change
DevinSessionContext.seenMessageEventIds from Set<string> to a bounded structure
(e.g., BoundedEventIdCache or LRUCache with a fixed capacity) and update any
code that calls add/has/delete on seenMessageEventIds to use the new API;
additionally, prune or compact the cache when lastMessageCursor is advanced (or
periodically) to remove IDs older than the cursor. Ensure the new cache
preserves O(1) membership checks used by message-processing code and choose a
sensible default capacity (or make it configurable).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/provider/devinApi.ts`:
- Around line 233-236: The call to options.apiKey.trim() in createDevinApiClient
can throw if apiKey is undefined/null; update createDevinApiClient to
defensively validate options.apiKey (from DevinApiClientOptions) before
trimming: either throw a clear Error when missing/invalid or coerce safely
(e.g., ensure it's a string via String(options.apiKey) or use a helper like
trimString) and then call .trim(); adjust the local variable apiKey assignment
accordingly so the function never calls .trim() on null/undefined.

In `@apps/server/src/provider/Layers/DevinAdapter.ts`:
- Around line 250-281: The getApiClient Effect currently assumes
settings.providers.devin and settings.orgId exist and calls .trim() directly;
guard these accesses by first checking that settings.providers and
settings.providers.devin are present and that settings.providers.devin.orgId is
a non-empty string before calling .trim(), and return Effect.fail(...) with
toValidationError("DevinAdapter.config", "...") when missing; also ensure the
apiKey extraction uses safe optional chaining/trim (e.g., check process.env keys
exist before trimming) so you only call createDevinApiClient when both the
provider settings and a valid apiKey/orgId are confirmed.
- Around line 542-577: The polling loop is being started outside the Effect
runtime with Effect.runFork(loop), so the forked fiber won't inherit services
like ServerSettingsService used by pollOnce; change the fork to run inside the
Effect.gen runtime and assign the returned Fiber to context.pollFiber by
yielding a runtime fork (e.g., replace context.pollFiber = Effect.runFork(loop)
with context.pollFiber = yield* Effect.forkDaemon(loop) or context.pollFiber =
yield* Effect.forkScoped(loop) inside the generator), ensuring the loop (defined
in ensurePolling) inherits the parent runtime and can access
getApiClient()/ServerSettingsService; also ensure the chosen fork function is
imported.

In `@apps/web/src/hooks/useSettings.ts`:
- Around line 155-159: The migration currently checks Schema.is(ModelSelection)
for legacySettings.textGenerationModelSelection but then filters providers to a
hardcoded allowlist ("codex" | "claudeAgent" | "devin"), which drops valid
providers; update the logic in useSettings by removing the restrictive provider
check and instead assign legacySettings.textGenerationModelSelection to
patch.textGenerationModelSelection when Schema.is(ModelSelection) passes (or
validate against the ModelSelection schema/enumeration dynamically), ensuring
references to Schema.is(ModelSelection),
legacySettings.textGenerationModelSelection, and
patch.textGenerationModelSelection are used to locate and change the code.

In `@packages/contracts/src/orchestration.ts`:
- Around line 79-91: DevinModelSelection currently allows any non-empty string
which is too permissive; change the schema for DevinModelSelection to use
Schema.Literal("devin-default") instead of TrimmedNonEmptyString and update the
corresponding ModelSelection union to reflect that literal; also mirror the same
tightening in ModelSelectionPatch (replace any Devin-related optional/patch
schema field allowing arbitrary strings with the literal "devin-default") so
only the supported devin-default slug is accepted across contracts.

In `@packages/contracts/src/settings.ts`:
- Around line 80-85: DevinSettings currently exposes customModels
(Schema.Array(Schema.String)) which creates a persisted, unusable field
providers.devin.customModels; remove that field from the public contract by
deleting the customModels entry from the DevinSettings Schema (and any duplicate
definitions around lines referenced as also applying) so the schema no longer
accepts or serializes customModels, and update any related patch/validation code
that references DevinSettings.customModels to avoid accepting or emitting that
property; ensure Schema.Struct only contains enabled, orgId and baseUrl to
prevent the wire field from being created.

---

Nitpick comments:
In `@apps/server/src/provider/Layers/DevinAdapter.ts`:
- Around line 51-63: The seenMessageEventIds Set in DevinSessionContext is
unbounded and can grow without limit; replace it with a bounded cache (e.g., an
LRU or size-limited set) and evict old IDs as new ones are added to prevent
memory growth. Concretely, change DevinSessionContext.seenMessageEventIds from
Set<string> to a bounded structure (e.g., BoundedEventIdCache or LRUCache with a
fixed capacity) and update any code that calls add/has/delete on
seenMessageEventIds to use the new API; additionally, prune or compact the cache
when lastMessageCursor is advanced (or periodically) to remove IDs older than
the cursor. Ensure the new cache preserves O(1) membership checks used by
message-processing code and choose a sensible default capacity (or make it
configurable).

In `@apps/server/src/provider/providerSnapshot.ts`:
- Around line 87-95: The Set named knownProviders is being recreated on every
call; hoist it to module scope by declaring a single constant Set<ProviderKind>
(e.g., knownProviders) at the top-level of the module so it is allocated once
and reused; update any references in providerSnapshot.ts that currently use the
local knownProviders (the loop using normalizeModelSlug(candidate, provider as
ProviderKind) and the conditional knownProviders.has(provider as ProviderKind))
to use the module-scoped constant and remove the local declaration to avoid
redundant allocations and centralize intent.

In `@apps/web/src/routes/_chat.settings.tsx`:
- Around line 81-136: The provider custom-model metadata is duplicated between
PROVIDER_SETTINGS (type InstallProviderSettings and the array) and
modelSelection.ts; centralize supportsCustomModels, customModelPlaceholder and
any fallback/harnessDescription text into one exported shared config so both the
settings page and the model picker import the same source. Create a single
exported ProviderModelConfig (or similar) that contains provider keys (e.g.,
"codex","claudeAgent","cursor","opencode","devin") with supportsCustomModels,
customModelPlaceholder and fallback copy, replace the duplicated fields in
PROVIDER_SETTINGS with references/imports to that shared export, and update
modelSelection.ts to import the same config instead of hard-coding
Devin-specific text. Ensure unique symbols to change are
InstallProviderSettings, PROVIDER_SETTINGS, and the usages in modelSelection.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c30fa8b4-9c1f-46d3-96a6-b385413f1636

📥 Commits

Reviewing files that changed from the base of the PR and between 50939b8 and 993a0d6f81055c8545dc01c4122b9a433af9c8ad.

📒 Files selected for processing (38)
  • ai_docs/tasks/007_devin_provider_integration.md
  • apps/server/integration/TestProviderAdapter.integration.ts
  • apps/server/src/provider/Layers/DevinAdapter.test.ts
  • apps/server/src/provider/Layers/DevinAdapter.ts
  • apps/server/src/provider/Layers/DevinProvider.test.ts
  • apps/server/src/provider/Layers/DevinProvider.ts
  • apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderAdapterRegistry.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
  • apps/server/src/provider/Layers/ProviderService.ts
  • apps/server/src/provider/Services/DevinAdapter.ts
  • apps/server/src/provider/Services/DevinProvider.ts
  • apps/server/src/provider/devinApi.test.ts
  • apps/server/src/provider/devinApi.ts
  • apps/server/src/provider/providerCapabilities.ts
  • apps/server/src/provider/providerSnapshot.ts
  • apps/server/src/serverLayers.ts
  • apps/server/src/serverSettings.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/Icons.tsx
  • apps/web/src/components/KeybindingsToast.browser.tsx
  • apps/web/src/components/chat/ProviderModelPicker.tsx
  • apps/web/src/components/chat/composerProviderRegistry.test.tsx
  • apps/web/src/components/chat/composerProviderRegistry.tsx
  • apps/web/src/hooks/useSettings.ts
  • apps/web/src/modelSelection.ts
  • apps/web/src/providerModels.test.ts
  • apps/web/src/providerModels.ts
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/session-logic.test.ts
  • apps/web/src/session-logic.ts
  • apps/web/src/store.ts
  • packages/contracts/src/model.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/provider.test.ts
  • packages/contracts/src/settings.test.ts
  • packages/contracts/src/settings.ts
  • packages/shared/src/model.test.ts

Comment thread apps/server/src/provider/devinApi.ts
Comment thread apps/server/src/provider/Layers/DevinAdapter.ts
Comment on lines +542 to +577
const ensurePolling = (threadId: ThreadId, context: DevinSessionContext) =>
Effect.sync(() => {
if (context.pollFiber || !context.remote) {
return;
}
const loop = Effect.gen(function* () {
let failureDelayMs = 5_000;
while (!context.stopped && context.remote) {
const pollResult = yield* Effect.exit(pollOnce(threadId, context));
if (pollResult._tag === "Success") {
failureDelayMs = 5_000;
if (!pollResult.value.continuePolling || pollResult.value.delayMs === null) {
break;
}
yield* Effect.sleep(pollResult.value.delayMs);
continue;
}

const message = Cause.pretty(pollResult.cause);
yield* emit(
makeEvent(
"runtime.warning",
{
message: "Devin polling failed; backing off before retrying.",
detail: { error: message },
},
{ threadId, turnId: context.activeTurnId },
),
);
yield* Effect.sleep(failureDelayMs);
failureDelayMs = Math.min(failureDelayMs + 5_000, 30_000);
}
context.pollFiber = null;
});
context.pollFiber = Effect.runFork(loop);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Polling fiber runs outside Effect runtime context.

Effect.runFork(loop) on Line 576 executes the polling loop outside the current Effect runtime, which means:

  1. It won't have access to services from the parent scope unless explicitly provided.
  2. Errors may not propagate correctly to the parent runtime.

The getApiClient() call inside pollOnce requires ServerSettingsService, which may not be available in the forked fiber.

🔧 Proposed fix to fork within the Effect runtime

Consider using Effect.forkDaemon or Effect.forkScoped within the generator context instead:

     const ensurePolling = (threadId: ThreadId, context: DevinSessionContext) =>
-      Effect.sync(() => {
+      Effect.gen(function* () {
         if (context.pollFiber || !context.remote) {
           return;
         }
         const loop = Effect.gen(function* () {
           // ... loop body unchanged
         });
-        context.pollFiber = Effect.runFork(loop);
+        context.pollFiber = yield* Effect.forkDaemon(loop);
       });

This ensures the fiber inherits the runtime context including access to ServerSettingsService.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/DevinAdapter.ts` around lines 542 - 577, The
polling loop is being started outside the Effect runtime with
Effect.runFork(loop), so the forked fiber won't inherit services like
ServerSettingsService used by pollOnce; change the fork to run inside the
Effect.gen runtime and assign the returned Fiber to context.pollFiber by
yielding a runtime fork (e.g., replace context.pollFiber = Effect.runFork(loop)
with context.pollFiber = yield* Effect.forkDaemon(loop) or context.pollFiber =
yield* Effect.forkScoped(loop) inside the generator), ensuring the loop (defined
in ensurePolling) inherits the parent runtime and can access
getApiClient()/ServerSettingsService; also ensure the chosen fork function is
imported.

Comment thread apps/web/src/hooks/useSettings.ts Outdated
Comment thread packages/contracts/src/orchestration.ts
Comment thread packages/contracts/src/settings.ts
ranvier2d2 and others added 4 commits March 30, 2026 20:53
Run oxfmt on the two files flagged by CI and replace unnecessary
Effect.fail wrapping of yieldable TaggedError instances.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Combine chained Effect.provide calls into a single provide with
Layer.provideMerge so the Effect language-service plugin does not
flag it as a lifecycle issue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ranvier2d2
ranvier2d2 merged commit 51cc576 into main Mar 31, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant