Skip to content

Commit 3d6bd04

Browse files
committed
feat: add Avian as LLM provider
Add Avian (api.avian.io) as a new OpenAI-compatible inference provider with four models: DeepSeek V3.2, Kimi K2.5, GLM-5, and MiniMax M2.5. Changes: - New provider module (web/src/llm-api/avian.ts) with streaming and non-streaming support, per-model pricing, usage tracking, and billing - Route avian/* models through the Avian provider in chat completions API - Add AVIAN_API_KEY to server env schema - Register avian models in model-config constants and agent type definitions
1 parent b6cc513 commit 3d6bd04

File tree

7 files changed

+717
-9
lines changed

7 files changed

+717
-9
lines changed

.agents/types/agent-definition.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,12 @@ export type ModelName =
418418
| 'deepseek/deepseek-r1-0528'
419419
| 'deepseek/deepseek-r1-0528:nitro'
420420

421+
// Avian (OpenAI-compatible inference API at api.avian.io)
422+
| 'avian/deepseek-v3.2'
423+
| 'avian/kimi-k2.5'
424+
| 'avian/glm-5'
425+
| 'avian/minimax-m2.5'
426+
421427
// Other open source models
422428
| 'moonshotai/kimi-k2'
423429
| 'moonshotai/kimi-k2:nitro'

agents/types/agent-definition.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,12 @@ export type ModelName =
418418
| 'deepseek/deepseek-r1-0528'
419419
| 'deepseek/deepseek-r1-0528:nitro'
420420

421+
// Avian (OpenAI-compatible inference API at api.avian.io)
422+
| 'avian/deepseek-v3.2'
423+
| 'avian/kimi-k2.5'
424+
| 'avian/glm-5'
425+
| 'avian/minimax-m2.5'
426+
421427
// Other open source models
422428
| 'moonshotai/kimi-k2'
423429
| 'moonshotai/kimi-k2:nitro'

common/src/constants/model-config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isExplicitlyDefinedModel } from '../util/model-utils'
33
// Allowed model prefixes for validation
44
export const ALLOWED_MODEL_PREFIXES = [
55
'anthropic',
6+
'avian',
67
'openai',
78
'google',
89
'x-ai',
@@ -51,6 +52,14 @@ export const openrouterModels = {
5152
export type openrouterModel =
5253
(typeof openrouterModels)[keyof typeof openrouterModels]
5354

55+
export const avianModels = {
56+
avian_deepseek_v3_2: 'avian/deepseek-v3.2',
57+
avian_kimi_k2_5: 'avian/kimi-k2.5',
58+
avian_glm_5: 'avian/glm-5',
59+
avian_minimax_m2_5: 'avian/minimax-m2.5',
60+
} as const
61+
export type AvianModel = (typeof avianModels)[keyof typeof avianModels]
62+
5463
export const deepseekModels = {
5564
deepseekChat: 'deepseek-chat',
5665
deepseekReasoner: 'deepseek-reasoner',
@@ -86,6 +95,7 @@ export type FinetunedVertexModel =
8695

8796
export const models = {
8897
...openaiModels,
98+
...avianModels,
8999
...deepseekModels,
90100
...openrouterModels,
91101
...finetunedVertexModels,
@@ -114,6 +124,12 @@ export const providerModelNames = {
114124
'openai' as const,
115125
]),
116126
),
127+
...Object.fromEntries(
128+
Object.entries(avianModels).map(([name, model]) => [
129+
model,
130+
'avian' as const,
131+
]),
132+
),
117133
...Object.fromEntries(
118134
Object.entries(openrouterModels).map(([name, model]) => [
119135
model,
@@ -166,6 +182,7 @@ export function getModelFromShortName(
166182
}
167183

168184
export const providerDomains = {
185+
avian: 'avian.io',
169186
google: 'google.com',
170187
anthropic: 'anthropic.com',
171188
openai: 'chatgpt.com',
@@ -178,6 +195,8 @@ export function getLogoForModel(modelName: string): string | undefined {
178195

179196
if (Object.values(openaiModels).includes(modelName as OpenAIModel))
180197
domain = providerDomains.openai
198+
else if (Object.values(avianModels).includes(modelName as AvianModel))
199+
domain = providerDomains.avian
181200
else if (Object.values(deepseekModels).includes(modelName as DeepseekModel))
182201
domain = providerDomains.deepseek
183202
else if (modelName.includes('claude')) domain = providerDomains.anthropic

common/src/templates/initial-agents-dir/types/agent-definition.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,12 @@ export type ModelName =
418418
| 'deepseek/deepseek-r1-0528'
419419
| 'deepseek/deepseek-r1-0528:nitro'
420420

421+
// Avian (OpenAI-compatible inference API at api.avian.io)
422+
| 'avian/deepseek-v3.2'
423+
| 'avian/kimi-k2.5'
424+
| 'avian/glm-5'
425+
| 'avian/minimax-m2.5'
426+
421427
// Other open source models
422428
| 'moonshotai/kimi-k2'
423429
| 'moonshotai/kimi-k2:nitro'

packages/internal/src/env-schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const serverEnvSchema = clientEnvSchema.extend({
66
OPEN_ROUTER_API_KEY: z.string().min(1),
77
OPENAI_API_KEY: z.string().min(1),
88
ANTHROPIC_API_KEY: z.string().min(1),
9+
AVIAN_API_KEY: z.string().min(1).optional(),
910
FIREWORKS_API_KEY: z.string().min(1),
1011
CANOPYWAVE_API_KEY: z.string().min(1).optional(),
1112
SILICONFLOW_API_KEY: z.string().min(1).optional(),
@@ -53,6 +54,7 @@ export const serverProcessEnv: ServerInput = {
5354
OPEN_ROUTER_API_KEY: process.env.OPEN_ROUTER_API_KEY,
5455
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
5556
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
57+
AVIAN_API_KEY: process.env.AVIAN_API_KEY,
5658
FIREWORKS_API_KEY: process.env.FIREWORKS_API_KEY,
5759
CANOPYWAVE_API_KEY: process.env.CANOPYWAVE_API_KEY,
5860
SILICONFLOW_API_KEY: process.env.SILICONFLOW_API_KEY,

web/src/app/api/v1/chat/completions/_post.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ import type { NextRequest } from 'next/server'
3535

3636
import type { ChatCompletionRequestBody } from '@/llm-api/types'
3737

38+
import {
39+
AvianError,
40+
handleAvianNonStream,
41+
handleAvianStream,
42+
isAvianModel,
43+
} from '@/llm-api/avian'
3844
import {
3945
CanopyWaveError,
4046
handleCanopyWaveNonStream,
@@ -469,11 +475,12 @@ export async function postChatCompletions(params: {
469475
// Handle streaming vs non-streaming
470476
try {
471477
if (bodyStream) {
472-
// Streaming request — route to SiliconFlow/CanopyWave/Fireworks for supported models
478+
// Streaming request — route to provider for supported models
473479
const useSiliconFlow = false // isSiliconFlowModel(typedBody.model)
474480
const useCanopyWave = false // isCanopyWaveModel(typedBody.model)
475-
const useFireworks = isFireworksModel(typedBody.model)
476-
const useOpenAIDirect = !useFireworks && isOpenAIDirectModel(typedBody.model)
481+
const useAvian = isAvianModel(typedBody.model)
482+
const useFireworks = !useAvian && isFireworksModel(typedBody.model)
483+
const useOpenAIDirect = !useAvian && !useFireworks && isOpenAIDirectModel(typedBody.model)
477484
const stream = useSiliconFlow
478485
? await handleSiliconFlowStream({
479486
body: typedBody,
@@ -494,6 +501,16 @@ export async function postChatCompletions(params: {
494501
logger,
495502
insertMessageBigquery,
496503
})
504+
: useAvian
505+
? await handleAvianStream({
506+
body: typedBody,
507+
userId,
508+
stripeCustomerId,
509+
agentId,
510+
fetch,
511+
logger,
512+
insertMessageBigquery,
513+
})
497514
: useFireworks
498515
? await handleFireworksStream({
499516
body: typedBody,
@@ -544,13 +561,14 @@ export async function postChatCompletions(params: {
544561
},
545562
})
546563
} else {
547-
// Non-streaming request — route to SiliconFlow/CanopyWave/Fireworks for supported models
564+
// Non-streaming request — route to provider for supported models
548565
// TEMPORARILY DISABLED: route through OpenRouter
549566
const model = typedBody.model
550567
const useSiliconFlow = false // isSiliconFlowModel(model)
551568
const useCanopyWave = false // isCanopyWaveModel(model)
552-
const useFireworks = isFireworksModel(model)
553-
const shouldUseOpenAIEndpoint = !useFireworks && isOpenAIDirectModel(model)
569+
const useAvianNonStream = isAvianModel(model)
570+
const useFireworks = !useAvianNonStream && isFireworksModel(model)
571+
const shouldUseOpenAIEndpoint = !useAvianNonStream && !useFireworks && isOpenAIDirectModel(model)
554572

555573
const nonStreamRequest = useSiliconFlow
556574
? handleSiliconFlowNonStream({
@@ -572,6 +590,16 @@ export async function postChatCompletions(params: {
572590
logger,
573591
insertMessageBigquery,
574592
})
593+
: useAvianNonStream
594+
? handleAvianNonStream({
595+
body: typedBody,
596+
userId,
597+
stripeCustomerId,
598+
agentId,
599+
fetch,
600+
logger,
601+
insertMessageBigquery,
602+
})
575603
: useFireworks
576604
? handleFireworksNonStream({
577605
body: typedBody,
@@ -622,6 +650,10 @@ export async function postChatCompletions(params: {
622650
if (error instanceof OpenRouterError) {
623651
openrouterError = error
624652
}
653+
let avianError: AvianError | undefined
654+
if (error instanceof AvianError) {
655+
avianError = error
656+
}
625657
let fireworksError: FireworksError | undefined
626658
if (error instanceof FireworksError) {
627659
fireworksError = error
@@ -641,7 +673,7 @@ export async function postChatCompletions(params: {
641673

642674
// Log detailed error information for debugging
643675
const errorDetails = openrouterError?.toJSON()
644-
const providerLabel = siliconflowError ? 'SiliconFlow' : canopywaveError ? 'CanopyWave' : fireworksError ? 'Fireworks' : openaiError ? 'OpenAI' : 'OpenRouter'
676+
const providerLabel = avianError ? 'Avian' : siliconflowError ? 'SiliconFlow' : canopywaveError ? 'CanopyWave' : fireworksError ? 'Fireworks' : openaiError ? 'OpenAI' : 'OpenRouter'
645677
logger.error(
646678
{
647679
error: getErrorObject(error),
@@ -655,8 +687,8 @@ export async function postChatCompletions(params: {
655687
? typedBody.messages.length
656688
: 0,
657689
messages: typedBody.messages,
658-
providerStatusCode: (openrouterError ?? fireworksError ?? canopywaveError ?? siliconflowError ?? openaiError)?.statusCode,
659-
providerStatusText: (openrouterError ?? fireworksError ?? canopywaveError ?? siliconflowError ?? openaiError)?.statusText,
690+
providerStatusCode: (openrouterError ?? avianError ?? fireworksError ?? canopywaveError ?? siliconflowError ?? openaiError)?.statusCode,
691+
providerStatusText: (openrouterError ?? avianError ?? fireworksError ?? canopywaveError ?? siliconflowError ?? openaiError)?.statusText,
660692
openrouterErrorCode: errorDetails?.error?.code,
661693
openrouterErrorType: errorDetails?.error?.type,
662694
openrouterErrorMessage: errorDetails?.error?.message,
@@ -681,6 +713,9 @@ export async function postChatCompletions(params: {
681713
if (error instanceof OpenRouterError) {
682714
return NextResponse.json(error.toJSON(), { status: error.statusCode })
683715
}
716+
if (error instanceof AvianError) {
717+
return NextResponse.json(error.toJSON(), { status: error.statusCode })
718+
}
684719
if (error instanceof FireworksError) {
685720
return NextResponse.json(error.toJSON(), { status: error.statusCode })
686721
}

0 commit comments

Comments
 (0)