Skip to main content

Custom Providers

You can create your own provider adapters to integrate any AI service with Toolpack SDK.

Creating a Custom Provider

Implement the ProviderAdapter abstract class:

import { 
ProviderAdapter,
CompletionRequest,
CompletionResponse,
CompletionChunk,
EmbeddingRequest,
EmbeddingResponse,
ProviderModelInfo
} from 'toolpack-sdk';

class MyCustomProvider extends ProviderAdapter {
name = 'my-provider'; // Required: unique provider name

private apiKey: string;

constructor(apiKey: string) {
super();
this.apiKey = apiKey;
}

async generate(request: CompletionRequest): Promise<CompletionResponse> {
// Implement your API call here
const response = await fetch('https://my-api.com/chat', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: request.messages,
model: request.model,
}),
});

const data = await response.json();

return {
content: data.text,
usage: {
prompt_tokens: data.usage?.input_tokens || 0,
completion_tokens: data.usage?.output_tokens || 0,
total_tokens: data.usage?.total_tokens || 0,
},
};
}

async *stream(request: CompletionRequest): AsyncGenerator<CompletionChunk> {
// Implement streaming here
const response = await fetch('https://my-api.com/chat/stream', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: request.messages,
model: request.model,
stream: true,
}),
});

const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');

const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) break;

const text = decoder.decode(value);
yield { delta: text };
}
}

async embed(request: EmbeddingRequest): Promise<EmbeddingResponse> {
// Implement embeddings (or throw if not supported)
throw new Error('Embeddings not supported by this provider');
}

// Optional: provide model list
async getModels(): Promise<ProviderModelInfo[]> {
return [
{
id: 'my-model-v1',
displayName: 'My Model v1',
capabilities: {
chat: true,
streaming: true,
toolCalling: false,
embeddings: false,
vision: false,
},
},
];
}

// Optional: customize display name
getDisplayName(): string {
return 'My Custom Provider';
}
}

Registering Custom Providers

Array Syntax

const myProvider = new MyCustomProvider('api-key');

const toolpack = await Toolpack.init({
customProviders: [myProvider],
// No need to specify 'provider' - first custom provider becomes default
});

Record Syntax

const toolpack = await Toolpack.init({
customProviders: {
'my-provider': new MyCustomProvider('api-key'),
'another-provider': new AnotherProvider('key'),
},
defaultProvider: 'my-provider',
});

Mixing with Built-in Providers

const toolpack = await Toolpack.init({
providers: {
openai: { apiKey: process.env.OPENAI_API_KEY },
},
customProviders: {
'my-provider': new MyCustomProvider('api-key'),
},
defaultProvider: 'openai',
});

// Use built-in
await toolpack.generate('Hello!');

// Switch to custom
toolpack.setProvider('my-provider');
await toolpack.generate('Hello from custom!');

Required Methods

Your custom provider must implement these methods:

MethodDescription
generate(request)Generate a complete response
stream(request)Stream response chunks
embed(request)Generate embeddings

Optional Methods

MethodDescription
getModels()Return available models
getDisplayName()Return human-readable name

Request and Response Types

CompletionRequest

interface CompletionRequest {
messages: Message[];
model: string;
temperature?: number;
max_tokens?: number;
top_p?: number;
stream?: boolean;
tools?: ToolCallRequest[];
tool_choice?: 'auto' | 'none' | 'required';
signal?: AbortSignal;
}

CompletionResponse

interface CompletionResponse {
content: string | null;
usage?: Usage;
finish_reason?: 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'error';
tool_calls?: ToolCallResult[];
}

CompletionChunk

interface CompletionChunk {
delta: string;
usage?: Usage;
finish_reason?: 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'error';
tool_calls?: ToolCallResult[];
}

Tool Calling Support

If your provider supports function/tool calling, handle the tools and tool_choice fields in the request, and return tool_calls in the response:

async generate(request: CompletionRequest): Promise<CompletionResponse> {
// Pass tools to your API
const response = await this.callApi({
messages: request.messages,
model: request.model,
tools: request.tools?.map(t => ({
type: 'function',
function: t.function,
})),
tool_choice: request.tool_choice,
});

// Return tool calls if present
return {
content: response.text,
tool_calls: response.tool_calls?.map(tc => ({
id: tc.id,
name: tc.function.name,
arguments: JSON.parse(tc.function.arguments),
})),
finish_reason: response.tool_calls ? 'tool_calls' : 'stop',
};
}

The SDK will automatically execute tools and continue the conversation.