// Messages API - Anthropic-compatible endpoint // Endpoint: /messages import { type AIMLAPIClient } from '../client'; import { buildPath } from './_paths'; // ===== VERSION ===== const VERSION = 'v2' as const; /** * Message role types */ export type MessageRole = 'user' | 'assistant' | 'system'; /** * Message content types */ export type MessageContent = | string | Array<{ type: 'text' | 'image' | 'tool_use' | 'tool_result' | 'thinking' | 'redacted_thinking'; text?: string; source?: { type: 'base64'; media_type: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp'; data: string; }; tool_use?: { id: string; name: string; input: Record; }; tool_result?: { tool_use_id: string; is_error?: boolean; content?: | string | Array<{ type: 'text' | 'image'; text?: string; source?: { type: 'base64'; media_type: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp'; data: string; }; }>; }; thinking?: string; signature?: string; data?: string; }>; /** * Message stop sequence */ export type MessageStopSequence = | 'content' | 'tool_use' | 'max_tokens' | 'timeout' | 'stop_sequence'; /** * Delta for streaming responses */ export interface MessageDelta { type: 'content_block_delta'; index: number; delta: { type: 'text_delta' | 'input_json_delta'; text?: string; partial_json?: string; }; } /** * Message usage */ export interface MessageUsage { input_tokens: number; output_tokens: number; } /** * Message response */ export interface MessageResponse { id: string; type: 'message'; role: 'assistant'; content: Array<{ type: 'text' | 'image' | 'tool_use' | 'tool_result' | 'thinking'; text?: string; source?: { type: 'base64' | 'url'; media_type: string; data: string; }; name?: string; input?: Record; thinking?: string; thinking_duration_ms?: number; }>; model: string; stop_reason: MessageStopSequence | 'max_tokens' | 'timeout' | string; stop_sequence?: MessageStopSequence; usage: MessageUsage; } /** * Streaming event types */ export type MessageStreamEvent = | { type: 'message_start'; message: MessageResponse } | { type: 'content_block_start'; content_block: MessageResponse['content'][0] } | { type: 'content_block_delta'; delta: MessageDelta } | { type: 'content_block_stop'; index: number } | { type: 'message_delta'; delta: { stop_reason?: string; usage?: MessageUsage } } | { type: 'message_stop'; stop_reason: string }; /** * Tool choice types */ export type ToolChoice = | { type: 'auto' } | { type: 'any' } | { type: 'tool'; name: string } | { type: 'none' }; /** * Tool definition */ export interface Tool { name: string; description?: string; input_schema: { type: 'object'; properties?: Record; required?: string[]; additionalProperties?: boolean | null; }; } /** * Thinking configuration */ export interface Thinking { type: 'enabled'; budget_tokens: number; } /** * Messages API parameters */ export interface MessagesParams { /** * The model to use for the message. */ model: string; /** * The messages to send. */ messages: Array<{ role: MessageRole; content: MessageContent; }>; /** * Maximum number of tokens to generate. */ max_tokens?: number | null; /** * Temperature for sampling. */ temperature?: number | null; /** * Top K sampling. */ top_k?: number | null; /** * Top P sampling. */ top_p?: number | null; /** * Whether to stream the response. */ stream?: boolean | null; /** * System prompt. */ system?: string | null; /** * Metadata to attach to the request. */ metadata?: Record | null; /** * Custom text sequences that will cause the model to stop generating. */ stop_sequences?: string[] | null; /** * Configuration for enabling Claude's extended thinking. */ thinking?: Thinking | null; /** * Controls which (if any) tool is called by the model. */ tool_choice?: ToolChoice | null; /** * Definitions of tools that the model may use. */ tools?: Tool[] | null; } /** * Messages API - Anthropic-compatible endpoint * @see https://docs.aimlapi.com/api-reference/messages */ export class Messages { constructor(private readonly _client: AIMLAPIClient) {} /** * Create a message using the Anthropic-compatible API. * * @example * ```typescript * const response = await client.messages.create({ * model: "claude-3-5-sonnet-20241022", * messages: [ * { role: "user", content: "Hello, Claude!" } * ], * max_tokens: 1024, * }); * console.log(response.content[0].text); * ``` */ async create(params: MessagesParams): Promise { return this._client.post(buildPath('/messages', VERSION), { body: { model: params.model, messages: params.messages, max_tokens: params.max_tokens, temperature: params.temperature, top_k: params.top_k, top_p: params.top_p, stream: params.stream ?? false, system: params.system, metadata: params.metadata, stop_sequences: params.stop_sequences, thinking: params.thinking, tool_choice: params.tool_choice, tools: params.tools, }, }); } /** * Create a streaming message response. * * @example * ```typescript * const stream = await client.messages.create({ * model: "claude-3-5-sonnet-20241022", * messages: [{ role: "user", content: "Write a story" }], * stream: true, * }); * * for await (const event of stream) { * if (event.type === "content_block_delta") { * process.stdout.write(event.delta.delta.text); * } * } * ``` */ async createStream(params: MessagesParams): Promise> { // Use the OpenAI client's streaming capabilities // Cast to OpenAI to access chat.completions const openaiClient = this._client as any; const stream = await openaiClient.chat.completions.create({ model: params.model, messages: params.messages, max_tokens: params.max_tokens, temperature: params.temperature, top_p: params.top_p, stream: true, ...(params.system && { system: params.system }), ...(params.stop_sequences && { stop: params.stop_sequences }), ...(params.tools && { tools: params.tools }), ...(params.tool_choice && { tool_choice: params.tool_choice }), }); // Convert OpenAI stream events to Anthropic-compatible format return this.convertOpenAIStreamToAnthropic(stream); } private async *convertOpenAIStreamToAnthropic( openaiStream: AsyncIterable ): AsyncIterable { const messageId = `msg_${Date.now()}`; let contentIndex = 0; let hasStarted = false; for await (const chunk of openaiStream) { // Send message_start event first if (!hasStarted) { hasStarted = true; yield { type: 'message_start', message: { id: messageId, type: 'message', role: 'assistant', content: [], model: chunk.model || 'unknown', stop_reason: 'content' as any, usage: { input_tokens: 0, output_tokens: 0, }, }, }; } // Handle different OpenAI chunk types if (chunk.choices && chunk.choices.length > 0) { const choice = chunk.choices[0]; if (choice.delta?.content) { // Send content_block_start if this is the first content if (contentIndex === 0) { yield { type: 'content_block_start', content_block: { type: 'text', text: '', }, }; contentIndex++; } // Send content_block_delta with text yield { type: 'content_block_delta', delta: { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: choice.delta.content, }, } as MessageDelta, }; } // Handle finish reason if (choice.finish_reason) { yield { type: 'content_block_stop', index: 0, }; yield { type: 'message_delta', delta: { stop_reason: choice.finish_reason, usage: { input_tokens: chunk.usage?.prompt_tokens || 0, output_tokens: chunk.usage?.completion_tokens || 0, }, }, }; yield { type: 'message_stop', stop_reason: choice.finish_reason, }; } } // Handle usage information if (chunk.usage) { yield { type: 'message_delta', delta: { usage: { input_tokens: chunk.usage.prompt_tokens, output_tokens: chunk.usage.completion_tokens, }, }, }; } } // Ensure we send a message_stop if the stream ends without one if (hasStarted) { yield { type: 'message_stop', stop_reason: 'content', }; } } }