Defining Plugin Credentials
Credentials are used to define authentication information that users need to connect to third-party services, such as API Keys, Access Tokens, or Base URLs.
Credentials in the Atomemo plugin system have two main purposes:
- Model Authentication: Used to configure LLM adapters (Adapter), enabling the system to call model services like OpenAI and Anthropic.
- Tool Authorization: Passed to tools via
args.credentials, enabling tools to call protected external APIs.
1. File Structure
It's recommended to place credential definition files in the src/credentials directory.
src/
credentials/
openai-api.ts # Credential definition file
index.ts # Plugin entry point2. Implement Credential Definition
You need to create an object that implements the CredentialDefinition interface.
Here's a complete example defining an OpenAI API Key credential:
import type { CredentialDefinition } from "@choiceopen/atomemo-plugin-sdk-js/types"
export const openaiCredential = {
// Unique identifier for the credential
name: "openai-api-key",
// Display name and description
display_name: { en_US: "OpenAI API Key" },
description: { en_US: "OpenAI API credential for authentication" },
// Icon
icon: "🔑",
// Define fields that users need to input
parameters: [
{
name: "api_key",
type: "string",
required: true,
display_name: { en_US: "API Key" },
ui: {
component: "input",
placeholder: "sk-...",
sensitive: true, // Mark as sensitive field, displayed as password input in UI
width: "full",
},
},
{
name: "base_url",
type: "string",
required: false,
display_name: { en_US: "Base URL" },
default: "https://api.openai.com/v1",
ui: {
component: "input",
width: "full",
},
},
],
// Authentication function (only for models)
// Note: If used for tools, this function will not be called
async authenticate({ args: { credential, extra } }) {
// Get the current model name from extra (if any)
const model = extra.model ?? "gpt-4"
// Return adapter configuration
return {
// Specify which built-in adapter to use: openai | anthropic | google_ai | deepseek
adapter: "openai",
// API key
api_key: credential.api_key ?? "",
// API endpoint
endpoint: credential.base_url || "https://api.openai.com/v1",
// Request header configuration
headers: {
Authorization: `Bearer ${credential.api_key}`,
},
}
},
} satisfies CredentialDefinitionKey Sections Explained
Parameters (Parameter Definition)
Define form fields through the parameters array. Each field is a PropertyScalar object supporting UI component configuration (like input, select), required state, and sensitivity marking (sensitive: true).
Authenticate (Authentication Function)
The authenticate function only executes when credentials are used for model calls. Its purpose is to convert user-input credentials into the configuration needed by the underlying LLM adapter.
- Input:
credential: Parameters filled by users (likeapi_key).extra: Context information, such as the currentmodelname.
- Output:
adapter: Specifies the underlying protocol adapter to use.api_key: The API key (required).endpoint: API address.headers: HTTP request headers (typically used for Authorization).
3. Using Credentials in Tools
When credentials are used for tools (Tool), the authenticate function will not be called. Credential data is passed to the tool's invoke function via args.credentials.
When defining tools, you can specify which credential types the tool requires.
// Get credentials in the tool definition's invoke function
invoke: async ({ args }) => {
const { parameters, credentials } = args
// Access credential fields directly
const credentialId = parameters.credential_id
const apiKey = credentials?.[credentialId]?.api_key
// Use credentials to call external APIs
// ...
}4. Register Credentials
Finally, register the credential in your plugin's entry file:
import { createPlugin } from "@choiceopen/atomemo-plugin-sdk-js"
import { openaiCredential } from "./credentials/openai-api"
const plugin = await createPlugin({
/* ... */
})
// Register credential
plugin.addCredential(openaiCredential)
plugin.run()5. OAuth2 Credential
If your plugin requires OAuth2 authentication (e.g., Google Drive, Slack), you can enable OAuth2 support by setting oauth2: true.
Grant Type
You can optionally declare the OAuth2 grant type with oauth2_grant_type:
authorization_codeclient_credentials
If oauth2_grant_type is omitted, the Hub defaults to authorization_code.
Use authorization_code for user-consent flows where the user is redirected to the provider's authorization page. Use client_credentials for server-to-server flows where the plugin exchanges its client credentials directly for an access token.
Required Parameters
When oauth2 is enabled, the parameters array must include the following fields:
access_token(encrypted_string)refresh_token(encrypted_string)expires_at(integer)
Required Functions
The callbacks you implement depend on the grant type:
- oauth2_build_authorize_url: Constructs the authorization URL to redirect the user. This is used for
authorization_codeflows. - oauth2_get_token: Exchanges the authorization code for an access token, or requests an access token directly for
client_credentialsflows. - oauth2_refresh_token: Refreshes the access token using the refresh token when the provider supports refresh tokens.
Example
Here is an example of a Google Drive OAuth2 credential:
import type { CredentialDefinition } from "@choiceopen/atomemo-plugin-sdk-js/types"
export const googleDriveOAuth2Credential = {
name: "google-drive-oauth2",
display_name: { en_US: "Google Drive OAuth2" },
description: { en_US: "Google Drive integration" },
icon: "link:google-drive",
// Enable OAuth2 support
oauth2: true,
oauth2_grant_type: "authorization_code",
parameters: [
{
name: "client_id",
type: "string",
required: true,
display_name: { en_US: "Client ID" },
ui: {
component: "input",
placeholder: "Enter Client ID",
},
},
{
name: "client_secret",
type: "encrypted_string",
required: true,
display_name: { en_US: "Client Secret" },
ui: {
component: "encrypted-input",
placeholder: "Enter Client Secret",
},
},
// Internal fields for storing tokens. Keep them in the definition even if you do not expose them in the UI.
{ name: "access_token", type: "encrypted_string" },
{ name: "refresh_token", type: "encrypted_string" },
{ name: "expires_at", type: "integer" },
],
// 1. Build Authorization URL
async oauth2_build_authorize_url({ args }) {
const { client_id } = args.credential
const { redirect_uri, state } = args
const params = new URLSearchParams({
client_id: client_id as string,
redirect_uri,
state,
response_type: "code",
scope: "https://www.googleapis.com/auth/drive.readonly",
access_type: "offline", // Important for getting refresh_token
prompt: "consent",
})
return {
url: `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`,
}
},
// 2. Exchange Code for Token
async oauth2_get_token({ args }) {
const { client_id, client_secret } = args.credential
const { code, redirect_uri } = args
// specific implementation to fetch token from provider...
const response = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: client_id as string,
client_secret: client_secret as string,
code,
redirect_uri,
grant_type: "authorization_code",
}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(`Failed to get token: ${data.error_description || data.error}`)
}
return {
parameters_patch: {
access_token: data.access_token,
refresh_token: data.refresh_token, // Only returned on first consent if access_type=offline
expires_at: Math.floor(Date.now() / 1000) + data.expires_in,
},
}
},
// 3. Refresh Token
async oauth2_refresh_token({ args }) {
const { client_id, client_secret, refresh_token } = args.credential
// specific implementation to refresh token...
const response = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: client_id as string,
client_secret: client_secret as string,
refresh_token: refresh_token as string,
grant_type: "refresh_token",
}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(`Failed to refresh token: ${data.error_description || data.error}`)
}
return {
parameters_patch: {
access_token: data.access_token,
expires_at: Math.floor(Date.now() / 1000) + data.expires_in,
},
}
},
} satisfies CredentialDefinitionReference
- Type Definition:
@choiceopen/atomemo-plugin-schema/typesforCredentialDefinition - Schema:
@choiceopen/atomemo-plugin-schema/schemasforCredentialDefinitionSchema