Skip to content

feat: add per-task LLM provider selection for scheduled tasks#450

Merged
DaniAkash merged 3 commits intomainfrom
feat/scheduled-task-model-selector
Mar 16, 2026
Merged

feat: add per-task LLM provider selection for scheduled tasks#450
DaniAkash merged 3 commits intomainfrom
feat/scheduled-task-model-selector

Conversation

@shivammittal274
Copy link
Contributor

Summary

  • Users can now select which AI provider a scheduled task runs with when creating or editing a task
  • Reuses the existing ChatProviderSelector component from the new-tab chat page
  • Falls back to the global default provider when none is selected or if the selected provider has been deleted
  • Task cards display the selected provider's icon and name
  • The llmProviderId field syncs to the GraphQL backend

Design

Adds an optional providerId field to ScheduledJob that references an existing LlmProviderConfig by UUID. At execution time, getChatServerResponse resolves the task-specific provider from storage, falling back to the global default. The GraphQL schema, sync logic, and UI are updated to support the new field. Fully backward compatible — existing tasks without a provider continue using the default.

Files changed (8)

  • scheduleTypes.ts — Added providerId field to ScheduledJob
  • NewScheduledTaskDialog.tsx — Provider selector UI in create/edit dialog
  • getChatServerResponse.ts — Provider resolution with fallback
  • scheduledJobRuns.ts — Pass providerId to execution
  • ScheduledTaskCard.tsx — Display provider icon + name on task cards
  • schema.graphqlllmProviderId on ScheduledJob, ScheduledJobInput, ScheduledJobPatch
  • syncSchedulesDocument.ts — GraphQL query/mutation updates
  • syncSchedulesToBackend.ts — Sync logic for new field

Test plan

  • Create a scheduled task — verify provider selector appears with configured providers
  • Select a non-default provider and save — verify task card shows the provider icon/name
  • Run the task — verify it uses the selected provider (check server logs)
  • Edit a task and change its provider — verify the change persists
  • Delete a provider that's assigned to a task — verify the task falls back to the default provider
  • Create a task without selecting a provider — verify it uses the global default

Allow users to choose which AI provider a scheduled task runs with,
using the same ChatProviderSelector component from the new-tab page.
Falls back to the global default provider when none is selected or
if the selected provider has been deleted.
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 15, 2026

Greptile Summary

This PR adds per-task LLM provider selection for scheduled tasks, allowing users to choose which AI provider a task runs with instead of always using the global default. The implementation is clean and well-structured, touching all the right layers: type definitions, UI (create/edit dialog + task card display), execution logic (provider resolution with fallback), GraphQL schema, and backend sync.

  • Adds optional providerId to ScheduledJob and wires it through the full stack (storage, sync, execution)
  • Reuses the existing ChatProviderSelector component from the chat page for provider selection UI
  • resolveProvider() in getChatServerResponse.ts handles fallback gracefully: task-specific provider → global default → built-in BrowserOS provider
  • Task cards display the selected provider's icon and name for at-a-glance visibility
  • Fully backward compatible — existing tasks without a provider continue using the global default
  • Minor style suggestions: redundant type cast in the dialog component, and a double storage read in the provider resolution fallback path

Confidence Score: 4/5

  • This PR is safe to merge with only minor style improvements suggested.
  • The feature is well-implemented with proper fallback handling, consistent field mapping across sync layers, and backward compatibility. Only style-level suggestions were found — no logic bugs or security concerns. The PR correctly handles edge cases like deleted providers and missing selections.
  • No files require special attention. getChatServerResponse.ts has a minor inefficiency in provider resolution fallback that could be cleaned up.

Important Files Changed

Filename Overview
packages/browseros-agent/apps/agent/lib/schedules/scheduleTypes.ts Adds optional providerId field to ScheduledJob interface. Clean, backward-compatible change.
packages/browseros-agent/apps/agent/entrypoints/app/scheduled-tasks/NewScheduledTaskDialog.tsx Adds provider selector UI to task creation/edit dialog. Correctly loads providers on dialog open, resolves selected provider with fallback. Minor redundant type cast on ProviderIcon.
packages/browseros-agent/apps/agent/lib/schedules/getChatServerResponse.ts Adds resolveProvider function with fallback logic. Correct behavior, but does a redundant providersStorage read when falling back from an unmatched providerId.
packages/browseros-agent/apps/agent/entrypoints/background/scheduledJobRuns.ts Passes job.providerId to getChatServerResponse. Simple, correct plumbing change.
packages/browseros-agent/apps/agent/entrypoints/app/scheduled-tasks/ScheduledTaskCard.tsx Displays provider icon and name on task cards when a provider is explicitly set. Properly handles the browseros provider type with its dedicated icon. Clean implementation.
packages/browseros-agent/apps/agent/schema/schema.graphql Adds llmProviderId: String to ScheduledJob type, ScheduledJobInput, and ScheduledJobPatch. Consistent nullable field addition across all relevant schema types.
packages/browseros-agent/apps/agent/lib/schedules/graphql/syncSchedulesDocument.ts Adds llmProviderId field to the query fetching scheduled jobs. Simple, correct addition.
packages/browseros-agent/apps/agent/lib/schedules/syncSchedulesToBackend.ts Maps local providerId to remote llmProviderId consistently across all sync paths (create, update, compare, and remote-to-local conversion). Correctly normalizes undefined/null differences.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User creates/edits task] --> B[NewScheduledTaskDialog]
    B --> C{Provider selected?}
    C -->|Yes| D[Save providerId to ScheduledJob]
    C -->|No| E[providerId = undefined]
    D --> F[Sync to backend as llmProviderId]
    E --> F
    F --> G[Chrome alarm triggers job]
    G --> H[scheduledJobRuns.executeScheduledJob]
    H --> I[getChatServerResponse with providerId]
    I --> J[resolveProvider]
    J --> K{providerId set & found?}
    K -->|Yes| L[Use task-specific provider]
    K -->|No| M{Global default exists?}
    M -->|Yes| N[Use global default provider]
    M -->|No| O[Use built-in BrowserOS provider]
    L --> P[Send chat request to agent server]
    N --> P
    O --> P
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/browseros-agent/apps/agent/entrypoints/app/scheduled-tasks/NewScheduledTaskDialog.tsx
Line: 257-260

Comment:
**Redundant type cast**

`resolvedProvider.type` is already `ProviderType` (the `Provider` interface defines `type: ProviderType`), so the `as ProviderType` cast is unnecessary.

```suggestion
                          <ProviderIcon
                            type={resolvedProvider.type}
                            size={16}
                          />
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/browseros-agent/apps/agent/lib/schedules/getChatServerResponse.ts
Line: 80-89

Comment:
**Redundant `providersStorage` read on fallback**

When `providerId` is provided but doesn't match any configured provider (e.g., provider was deleted), `resolveProvider` reads `providersStorage` at line 84, finds no match, then calls `getDefaultProvider()` which reads `providersStorage` again at line 71. Consider reusing the already-fetched providers list to avoid the duplicate storage read:

```
const resolveProvider = async (
  providerId?: string,
): Promise<LlmProviderConfig> => {
  const providers = await providersStorage.getValue()
  if (providerId) {
    const match = providers?.find((p) => p.id === providerId)
    if (match) return match
  }
  if (!providers?.length) return createDefaultBrowserOSProvider()
  const defaultProviderId = await defaultProviderIdStorage.getValue()
  return providers.find((p) => p.id === defaultProviderId) ?? providers[0] ?? createDefaultBrowserOSProvider()
}
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 9ec90fb

@DaniAkash DaniAkash merged commit 41c9b15 into main Mar 16, 2026
2 checks passed
@DaniAkash DaniAkash deleted the feat/scheduled-task-model-selector branch March 16, 2026 12:33
@github-actions github-actions bot locked and limited conversation to collaborators Mar 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants