Skip to main content

Adapter API Reference

This document describes how to create custom adapters for the AI Primitives Hub extension.

Overview

Adapters provide a unified interface for fetching bundles from different sources. The AI Primitives Hub uses the adapter pattern to support multiple source types (GitHub, local files, and curated collections).

IRepositoryAdapter Interface

All adapters must implement the IRepositoryAdapter interface:

interface IRepositoryAdapter {
// The type of repository this adapter handles
readonly type: string;

// The source configuration
readonly source: RegistrySource;

// Fetch all bundles from this source
fetchBundles(): Promise<Bundle[]>;

// Download a specific bundle (returns zip Buffer)
downloadBundle(bundle: Bundle): Promise<Buffer>;

// Get metadata about the source
fetchMetadata(): Promise<SourceMetadata>;

// Validate source configuration
validate(): Promise<ValidationResult>;

// Check if source requires authentication
requiresAuthentication(): boolean;

// Get URLs for bundles
getManifestUrl(bundleId: string, version?: string): string;
getDownloadUrl(bundleId: string, version?: string): string;

// Force re-authentication (optional)
forceAuthentication?(): Promise<void>;
}

Installation Paths

Adapters can use one of two installation paths:

URL-Based Installation

For pre-packaged zip bundles on remote servers. The adapter returns a download URL, and BundleInstaller.install() handles the download.

Used by: GitHub, AwesomeCopilot adapters

// Adapter returns URL string
getDownloadUrl(bundleId: string, version: string): string {
return `https://example.com/bundles/${bundleId}/${version}.zip`;
}

Buffer-Based Installation

For dynamically created bundles. The adapter builds the zip in memory and returns a Buffer. BundleInstaller.installFromBuffer() handles extraction.

Used by: AwesomeCopilot, Local adapters

// Adapter returns Buffer
async downloadBundle(bundle: Bundle): Promise<Buffer> {
const archive = archiver('zip');
// ... build zip contents
return archive.finalize();
}

Creating a Custom Adapter

Step 1: Implement the Interface

import { IRepositoryAdapter, Bundle, SourceMetadata, ValidationResult } from '../types';

export class MyCustomAdapter implements IRepositoryAdapter {
constructor(private config: MyAdapterConfig) {}

async fetchBundles(): Promise<Bundle[]> {
// Fetch bundle list from your source
const response = await fetch(this.config.apiUrl);
const data = await response.json();

return data.bundles.map(item => ({
id: item.id,
name: item.name,
version: item.version,
description: item.description,
// ... other bundle properties
}));
}

async downloadBundle(bundle: Bundle): Promise<Buffer> {
// For buffer-based adapters
const response = await fetch(`${this.config.apiUrl}/download/${bundle.id}`);
return Buffer.from(await response.arrayBuffer());
}

async fetchMetadata(): Promise<SourceMetadata> {
return {
name: this.config.name,
type: 'my-custom',
url: this.config.apiUrl,
};
}

async validate(): Promise<ValidationResult> {
try {
await fetch(this.config.apiUrl);
return { valid: true };
} catch (error) {
return { valid: false, error: error.message };
}
}

getManifestUrl(bundleId: string, version: string): string {
return `${this.config.apiUrl}/manifests/${bundleId}/${version}`;
}

getDownloadUrl(bundleId: string, version: string): string {
return `${this.config.apiUrl}/download/${bundleId}/${version}`;
}
}

Step 2: Register the Adapter

Register your adapter with the RepositoryAdapterFactory:

import { RepositoryAdapterFactory } from '../adapters/RepositoryAdapterFactory';
import { MyCustomAdapter } from './MyCustomAdapter';

// Register the adapter type
RepositoryAdapterFactory.register('my-custom', MyCustomAdapter);

Step 3: Update Source Types

Add your adapter type to the SourceType union in src/types/registry.ts:

export type SourceType =
| 'github'
| 'local'
| 'awesome-copilot'
| 'local-awesome-copilot'
| 'apm'
| 'local-apm'
| 'my-custom';

Built-in Adapters

AdapterSource TypeDescriptionStatus
GitHubAdaptergithubFetches releases and assets from GitHub repositoriesActive
LocalAdapterlocalInstalls from local file system directoriesActive
AwesomeCopilotAdapterawesome-copilotFetches YAML collections from GitHub, builds zips on-the-flyActive
LocalAwesomeCopilotAdapterlocal-awesome-copilotLocal YAML collections for developmentActive
ApmAdapterapmAPM package repositoriesActive
LocalApmAdapterlocal-apmLocal APM packagesActive
SkillsAdapterskillsFetches skills from a GitHub repository's skills/ directoryActive
LocalSkillsAdapterlocal-skillsLocal filesystem skills directoryActive

Authentication

Adapters that access private repositories should implement authentication. The GitHub and AwesomeCopilot adapters use a three-tier authentication chain:

  1. VS Code GitHub Authentication — Uses the built-in VS Code GitHub auth
  2. GitHub CLI — Falls back to gh auth token if available
  3. Explicit Token — Uses a configured token from source config
private async getAuthenticationToken(): Promise<string | undefined> {
// 1. Try VSCode GitHub authentication
const session = await vscode.authentication.getSession('github', ['repo'], { silent: true });
if (session) return session.accessToken;

// 2. Try GitHub CLI
const { stdout } = await execAsync('gh auth token');
if (stdout.trim()) return stdout.trim();

// 3. Try explicit token from source config
const explicitToken = this.getAuthToken();
if (explicitToken) return explicitToken;

return undefined;
}

Use Bearer token format for authenticated requests:

headers['Authorization'] = `Bearer ${token}`;

Bundle Manifest Format

Bundles must include a deployment-manifest.yml file:

version: "1.0"
id: "my-bundle"
name: "My Custom Bundle"
prompts:
- id: "my-prompt"
name: "My Prompt"
type: "prompt"
file: "prompts/my-prompt.prompt.md"
tags: ["custom", "example"]

Error Handling

Adapters should handle errors gracefully and return meaningful error messages:

async fetchBundles(): Promise<Bundle[]> {
try {
const response = await fetch(this.config.apiUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
Logger.getInstance().error(`[MyAdapter] Failed to fetch bundles: ${error.message}`);
throw error;
}
}

See Also