From 6e056ecdcf02b61a96fc1b16879b37a9bf028a22 Mon Sep 17 00:00:00 2001 From: monolithic827 <257007037+monolithic827@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:10:09 +0200 Subject: [PATCH] feat(stage-ui): Include OpenRouter attribution headers (#1436) --- .../providers/openrouter-ai/index.ts | 20 ++++++++++++++++++- .../providers/openrouter/audio-speech.ts | 7 ++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/stage-ui/src/libs/providers/providers/openrouter-ai/index.ts b/packages/stage-ui/src/libs/providers/providers/openrouter-ai/index.ts index f365318dd..de7847405 100644 --- a/packages/stage-ui/src/libs/providers/providers/openrouter-ai/index.ts +++ b/packages/stage-ui/src/libs/providers/providers/openrouter-ai/index.ts @@ -4,6 +4,11 @@ import { z } from 'zod' import { createOpenAICompatibleValidators } from '../../validators/openai-compatible' import { defineProvider } from '../registry' +export const OPENROUTER_ATTRIBUTION_HEADERS: Record = { + 'HTTP-Referer': 'https://airi.moeru.ai/', + 'X-OpenRouter-Title': 'Project AIRI', +} + const openRouterConfigSchema = z.object({ apiKey: z .string('API Key'), @@ -39,7 +44,19 @@ export const providerOpenRouterAI = defineProvider({ }), }), createProvider(config) { - return createOpenRouter(config.apiKey, config.baseUrl) + const base = createOpenRouter(config.apiKey, config.baseUrl) + return { + ...base, + chat: (model: string) => ({ + ...base.chat(model), + fetch: (input: RequestInfo | URL, init?: RequestInit) => { + const headers = new Headers(init?.headers) + for (const [k, v] of Object.entries(OPENROUTER_ATTRIBUTION_HEADERS)) + headers.set(k, v) + return globalThis.fetch(input, { ...init, headers }) + }, + }), + } }, validationRequiredWhen(config) { @@ -48,6 +65,7 @@ export const providerOpenRouterAI = defineProvider({ validators: { ...createOpenAICompatibleValidators({ checks: ['connectivity', 'model_list'], + additionalHeaders: OPENROUTER_ATTRIBUTION_HEADERS, }), }, }) diff --git a/packages/stage-ui/src/stores/providers/openrouter/audio-speech.ts b/packages/stage-ui/src/stores/providers/openrouter/audio-speech.ts index 3c2de93ba..188d2f37f 100644 --- a/packages/stage-ui/src/stores/providers/openrouter/audio-speech.ts +++ b/packages/stage-ui/src/stores/providers/openrouter/audio-speech.ts @@ -2,6 +2,8 @@ import type { SpeechProvider } from '@xsai-ext/providers/utils' import type { ModelInfo, ProviderMetadata, VoiceInfo } from '../../providers' +import { OPENROUTER_ATTRIBUTION_HEADERS } from '../../../libs/providers/providers/openrouter-ai' + const DEFAULT_BASE_URL = 'https://openrouter.ai/api/v1/' const DEFAULT_MODEL = 'openai/gpt-audio-mini' const PROVIDER_ID = 'openrouter-audio-speech' @@ -145,6 +147,7 @@ function createAudioFetch(apiKey: string, baseUrl: string, model: string) { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', + ...OPENROUTER_ATTRIBUTION_HEADERS, }, body: JSON.stringify({ model, @@ -187,7 +190,9 @@ function createSpeechProvider(apiKey: string, baseUrl: string): SpeechProvider { } async function listModels(baseUrl: string): Promise { - const res = await fetch(`${baseUrl}models?output_modality=audio`) + const res = await fetch(`${baseUrl}models?output_modality=audio`, { + headers: OPENROUTER_ATTRIBUTION_HEADERS, + }) if (!res.ok) return []