Extend

Source Adapters, capabilities, and Actions

Bind a Source to Profiles and optional Provider operations with compile-time consistency.

A Source Adapter owns one Source's configuration and operations. It may import exactly one Provider or operate providerlessly.

Capabilities gate operations

CapabilityRequired implementation
synccursor-driven emission of Resource upserts, removals, warnings, and checkpoints
search-remoteprovider search returning bounded normalized results and optional continuation
retrieveexact complete Resource retrieval by Ref
downloadstreaming one resolved Artifact's bytes into managed storage

Declaring a capability requires its operation; omitting a capability forbids that operation. The SDK preserves this relationship through inference and core validates it again before activation.

Search routing (indexed, federated, or hybrid) is not a capability. CLI routing flags override Source configuration, which overrides the Adapter's decision.

Provider access

A provider-backed Adapter declares only its additional scopes and API hosts:

defineAdapter({
  id: 'acme.mailbox',
  provider: acmeProvider,
  access: { scopes: ['mail.read'] },
  providerApiHosts: ['mail.api.example.com'],
  // configSchema, profiles, routing, capabilities, operations, actions
})

The host supplies scoped context.fetch; do not import stored tokens or core runtime internals.

Return portable failures

Throw syncError(...) for expected provider failures instead of relying on a package-local Error subclass. The marker is structurally recognized across separately installed SDK copies while its schema keeps diagnostics bounded:

import { syncError } from '@ctxindex/extension-sdk'

if (response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0') {
  throw syncError('rate_limited', {
    message: 'GitHub anonymous API rate limit exhausted',
    retryAfterMs: millisecondsUntilReset(response.headers),
  })
}

if (!response.ok) {
  throw syncError('provider_bad_response', {
    message: `GitHub returned HTTP ${response.status}`,
  })
}

Use the narrowest stable code. Do not include response bodies, tokens, filesystem paths, or unbounded provider diagnostics in messages. Preserve cancellation instead of turning it into a provider failure.

Actions

Profiles declare Action identity, effect, input, and output Profile. Each Adapter binds only the Actions it supports and supplies the implementation:

actions: {
  'acme.note.draft.create': {
    profile: noteProfile,
    input: createDraftInput,
    output: noteProfile,
    run: createDraft,
  },
}

An Adapter cannot implement an undeclared Profile Action. Action contexts expose the selected Source, validated input, scoped fetch/logger/cancellation, and narrow read-only Resource/Artifact resolvers—not database or arbitrary provider access.

Operation checklist

  • Parse context.source.config with the same strict schema the Adapter declares.
  • Validate provider JSON before normalizing it into Profile payloads.
  • Pass context.signal to every fetch.
  • Keep Refs stable and treat the suffix after the Source id as Adapter-owned opaque identity.
  • Bound pages, results, cursors, warning text, and downloaded bytes.
  • Emit a checkpoint only after every Resource covered by that cursor has been emitted.

The external GitHub Issues Adapter is the complete checked example for sync, pagination, cursor validation, portable failures, Profile reuse, and synthetic provider tests.