The first 2 weeks is ended.
Some checks failed
CI / validate (push) Failing after 14m50s

This commit is contained in:
2026-08-14 10:48:54 +05:00
commit 1774496cf9
48 changed files with 10825 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import { buildJsonCompletion, parseJsonCompletion, resolveAiProvider } from './ai-provider'
const request = { system: 'Return JSON.', messages: [{ role: 'user' as const, content: 'Create a world' }], schemaName: 'world', jsonSchema: { type: 'object' } }
describe('AI provider adapter', () => {
it('keeps OpenRouter structured outputs', () => {
const completion = buildJsonCompletion({ aiProvider: 'openrouter', openrouterApiKey: 'key', openrouterModel: 'model' }, request)
expect(completion.endpoint).toBe('https://openrouter.ai/api/v1/chat/completions')
expect(completion.body.response_format).toEqual({ type: 'json_schema', json_schema: { name: 'world', strict: true, schema: { type: 'object' } } })
})
it('uses official DeepSeek JSON mode', () => {
const completion = buildJsonCompletion({ aiProvider: 'deepseek', deepseekApiKey: 'key', deepseekModel: 'deepseek-v4-flash' }, request)
expect(completion.endpoint).toBe('https://api.deepseek.com/chat/completions')
expect(completion.body.response_format).toEqual({ type: 'json_object' })
expect(completion.body['thinking']).toEqual({ type: 'disabled' })
})
it('rejects missing credentials and malformed responses', () => {
expect(() => resolveAiProvider({ aiProvider: 'deepseek' })).toThrow('DEEPSEEK_API_KEY')
expect(() => parseJsonCompletion({ choices: [] })).toThrow()
expect(parseJsonCompletion({ choices: [{ message: { content: '{"ok":true}' } }] })).toEqual({ ok: true })
})
})

View File

@@ -0,0 +1,103 @@
import { z } from 'zod'
const ProviderSchema = z.enum(['openrouter', 'deepseek'])
const CompletionResponseSchema = z.object({
choices: z.array(z.object({
message: z.object({ content: z.string().min(1) }),
})).min(1),
})
export type AiProvider = z.infer<typeof ProviderSchema>
export interface AiRuntimeConfig {
aiProvider?: unknown
openrouterApiKey?: unknown
openrouterModel?: unknown
openrouterApiEndpoint?: unknown
deepseekApiKey?: unknown
deepseekModel?: unknown
deepseekApiEndpoint?: unknown
}
export interface JsonCompletionRequest {
system: string
messages: Array<{ role: 'user' | 'assistant'; content: string }>
schemaName: string
jsonSchema: Record<string, unknown>
}
interface JsonCompletion {
endpoint: string
headers: Record<string, string>
body: Record<string, unknown>
}
const nonEmptyString = (value: unknown, name: string) => {
const result = z.string().trim().min(1).safeParse(value)
if (!result.success) throw new Error(`${name} is required for the selected AI provider`)
return result.data
}
const endpoint = (value: unknown, fallback: string, name: string) => {
const result = z.string().url().safeParse(value || fallback)
if (!result.success) throw new Error(`${name} must be a valid URL`)
return result.data
}
export function resolveAiProvider(config: AiRuntimeConfig) {
const providerResult = ProviderSchema.safeParse(config.aiProvider || 'openrouter')
if (!providerResult.success) throw new Error('AI_PROVIDER must be either "openrouter" or "deepseek"')
if (providerResult.data === 'deepseek') {
return {
provider: 'deepseek' as const,
apiKey: nonEmptyString(config.deepseekApiKey, 'DEEPSEEK_API_KEY'),
model: nonEmptyString(config.deepseekModel || 'deepseek-v4-flash', 'DEEPSEEK_MODEL'),
endpoint: endpoint(config.deepseekApiEndpoint, 'https://api.deepseek.com/chat/completions', 'DEEPSEEK_API_ENDPOINT'),
}
}
return {
provider: 'openrouter' as const,
apiKey: nonEmptyString(config.openrouterApiKey, 'OPENROUTER_API_KEY'),
model: nonEmptyString(config.openrouterModel || 'deepseek/deepseek-v4-flash', 'OPENROUTER_MODEL'),
endpoint: endpoint(config.openrouterApiEndpoint, 'https://openrouter.ai/api/v1/chat/completions', 'OPENROUTER_API_ENDPOINT'),
}
}
export function buildJsonCompletion(config: AiRuntimeConfig, request: JsonCompletionRequest): JsonCompletion {
const provider = resolveAiProvider(config)
const messages = [{ role: 'system' as const, content: request.system }, ...request.messages]
const common = { model: provider.model, messages, stream: false }
if (provider.provider === 'deepseek') {
return {
endpoint: provider.endpoint,
headers: { Authorization: `Bearer ${provider.apiKey}`, 'Content-Type': 'application/json' },
body: {
...common,
messages: [
{ role: 'system' as const, content: `${request.system}\nReturn only valid JSON matching this JSON Schema: ${JSON.stringify(request.jsonSchema)}` },
...request.messages,
],
response_format: { type: 'json_object' },
thinking: { type: 'disabled' },
},
}
}
return {
endpoint: provider.endpoint,
headers: { Authorization: `Bearer ${provider.apiKey}`, 'Content-Type': 'application/json', 'X-Title': 'Dungeons & Ground' },
body: {
...common,
response_format: { type: 'json_schema', json_schema: { name: request.schemaName, strict: true, schema: request.jsonSchema } },
provider: { require_parameters: true, data_collection: 'deny' },
},
}
}
export function parseJsonCompletion(payload: unknown): unknown {
const completion = CompletionResponseSchema.parse(payload)
return JSON.parse(completion.choices[0]!.message.content)
}

View File

@@ -0,0 +1,12 @@
import { describe, expect, it } from 'vitest'
import { assertRequiredSupabaseConfig } from './runtime-config'
describe('required Supabase runtime config', () => {
it('accepts complete credentials', () => {
expect(() => assertRequiredSupabaseConfig({ supabaseUrl: 'https://example.supabase.co', supabaseServiceRoleKey: 'service', public: { supabaseUrl: 'https://example.supabase.co', supabaseAnonKey: 'anon' } })).not.toThrow()
})
it('reports missing credentials clearly', () => {
expect(() => assertRequiredSupabaseConfig({ public: {} })).toThrow('SUPABASE_SERVICE_ROLE_KEY is required')
})
})

View File

@@ -0,0 +1,18 @@
import { z } from 'zod'
const RequiredSupabaseConfigSchema = z.object({
supabaseUrl: z.string({ error: 'SUPABASE_URL is required' }).trim().url('SUPABASE_URL must be a valid URL'),
supabaseServiceRoleKey: z.string({ error: 'SUPABASE_SERVICE_ROLE_KEY is required' }).trim().min(1, 'SUPABASE_SERVICE_ROLE_KEY is required'),
public: z.object({
supabaseUrl: z.string({ error: 'NUXT_PUBLIC_SUPABASE_URL is required' }).trim().url('NUXT_PUBLIC_SUPABASE_URL must be a valid URL'),
supabaseAnonKey: z.string({ error: 'NUXT_PUBLIC_SUPABASE_ANON_KEY is required' }).trim().min(1, 'NUXT_PUBLIC_SUPABASE_ANON_KEY is required'),
}),
})
export function assertRequiredSupabaseConfig(config: unknown): void {
const result = RequiredSupabaseConfigSchema.safeParse(config)
if (!result.success) {
const details = result.error.issues.map(issue => issue.message).join('; ')
throw new Error(`Invalid Supabase runtime configuration: ${details}`)
}
}