Files
telegram-bot-workers/src/services/vultr-api.ts
kappa 6563ee0650 feat: add server ordering system with session-based flow
- Add server recommendation integration (SERVER_RECOMMEND worker)
- Implement KV-based session management for multi-step ordering
- Add Linode/Vultr API clients for server provisioning
- Add server-tool for Function Calling support

refactor: major code reorganization (Phase 1-3)

- Remove 443 lines of deprecated callback handlers
- Extract handlers to separate files (message-handler, callback-handler)
- Extract cloud-spec-service, server-recommend-service
- Centralize constants (OS_IMAGES, REGION_FLAGS, NUM_EMOJIS)
- webhook.ts reduced from 1,951 to 30 lines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 21:01:38 +09:00

241 lines
5.8 KiB
TypeScript

/**
* Vultr API Client
*
* REST API 클라이언트 for Vultr cloud provider
* - Instance management (create, get)
* - Region listing
* - Automatic retry with exponential backoff
*/
import type { Env, VultrInstance, VultrCreateRequest } from '../types';
import { createLogger } from '../utils/logger';
import { retryWithBackoff } from '../utils/retry';
const logger = createLogger('vultr-api');
/**
* Vultr API Base URLs
*/
const DEFAULT_API_BASE = 'https://api.vultr.com/v2';
/**
* Vultr Region
*/
export interface VultrRegion {
id: string;
city: string;
country: string;
continent: string;
options: string[];
}
/**
* Vultr API Error
*/
export class VultrAPIError extends Error {
constructor(
message: string,
public readonly statusCode: number,
public readonly response?: any
) {
super(message);
this.name = 'VultrAPIError';
}
}
/**
* Create a Vultr instance
*
* @param params - Instance creation parameters
* @param env - Environment variables (API key, base URL)
* @returns Created instance information
* @throws VultrAPIError if API call fails
*/
export async function createInstance(
params: VultrCreateRequest,
env: Env
): Promise<VultrInstance> {
const apiKey = env.VULTR_API_KEY;
if (!apiKey) {
throw new Error('VULTR_API_KEY is not configured');
}
const apiBase = env.VULTR_API_BASE || DEFAULT_API_BASE;
const url = `${apiBase}/instances`;
logger.info('Creating Vultr instance', {
plan: params.plan,
region: params.region,
label: params.label,
});
return retryWithBackoff(
async () => {
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
if (!response.ok) {
const errorBody = await response.json().catch(() => ({}));
logger.error('Vultr API create instance failed', undefined, {
status: response.status,
statusText: response.statusText,
error: errorBody,
});
throw new VultrAPIError(
`Vultr API error: ${response.status} ${response.statusText}`,
response.status,
errorBody
);
}
const responseData = await response.json() as { instance: VultrInstance };
const instance = responseData.instance;
logger.info('Vultr instance created successfully', {
id: instance.id,
label: instance.label,
main_ip: instance.main_ip,
});
return instance;
},
{
maxRetries: 3,
initialDelayMs: 1000,
serviceName: 'vultr-api',
}
);
}
/**
* Get a Vultr instance by ID
*
* @param instanceId - Vultr instance ID
* @param env - Environment variables (API key, base URL)
* @returns Instance information
* @throws VultrAPIError if API call fails
*/
export async function getInstance(
instanceId: string,
env: Env
): Promise<VultrInstance> {
const apiKey = env.VULTR_API_KEY;
if (!apiKey) {
throw new Error('VULTR_API_KEY is not configured');
}
const apiBase = env.VULTR_API_BASE || DEFAULT_API_BASE;
const url = `${apiBase}/instances/${instanceId}`;
logger.info('Getting Vultr instance', { instanceId });
return retryWithBackoff(
async () => {
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
if (!response.ok) {
const errorBody = await response.json().catch(() => ({}));
logger.error('Vultr API get instance failed', undefined, {
status: response.status,
statusText: response.statusText,
instanceId,
error: errorBody,
});
throw new VultrAPIError(
`Vultr API error: ${response.status} ${response.statusText}`,
response.status,
errorBody
);
}
const responseData = await response.json() as { instance: VultrInstance };
const instance = responseData.instance;
logger.info('Vultr instance retrieved successfully', {
id: instance.id,
label: instance.label,
status: instance.status,
});
return instance;
},
{
maxRetries: 3,
initialDelayMs: 1000,
serviceName: 'vultr-api',
}
);
}
/**
* Get list of available Vultr regions
*
* @param env - Environment variables (API key, base URL)
* @returns Array of available regions
* @throws VultrAPIError if API call fails
*/
export async function getRegions(env: Env): Promise<VultrRegion[]> {
const apiKey = env.VULTR_API_KEY;
if (!apiKey) {
throw new Error('VULTR_API_KEY is not configured');
}
const apiBase = env.VULTR_API_BASE || DEFAULT_API_BASE;
const url = `${apiBase}/regions`;
logger.info('Getting Vultr regions');
return retryWithBackoff(
async () => {
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
if (!response.ok) {
const errorBody = await response.json().catch(() => ({}));
logger.error('Vultr API get regions failed', undefined, {
status: response.status,
statusText: response.statusText,
error: errorBody,
});
throw new VultrAPIError(
`Vultr API error: ${response.status} ${response.statusText}`,
response.status,
errorBody
);
}
const responseData = await response.json() as { regions: VultrRegion[] };
const regions = responseData.regions;
logger.info('Vultr regions retrieved successfully', {
count: regions.length,
});
return regions;
},
{
maxRetries: 3,
initialDelayMs: 1000,
serviceName: 'vultr-api',
}
);
}