schema.ts

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: GPL-3.0-or-later
 4
 5import { Type, Kind, type Static, type TObject, type TProperties } from "@sinclair/typebox";
 6
 7const CustomModelSchema = Type.Object({
 8  provider: Type.String(),
 9  api: Type.String(),
10  base_url: Type.String(),
11  id: Type.String(),
12  name: Type.String(),
13  reasoning: Type.Boolean(),
14  input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
15  cost: Type.Object({
16    input: Type.Number(),
17    output: Type.Number(),
18    cache_read: Type.Optional(Type.Number()),
19    cache_write: Type.Optional(Type.Number()),
20  }),
21  context_window: Type.Number(),
22  max_tokens: Type.Number(),
23  api_key: Type.Optional(Type.String()),
24  headers: Type.Optional(Type.Record(Type.String(), Type.String())),
25  compat: Type.Optional(
26    Type.Object({
27      supports_store: Type.Optional(Type.Boolean()),
28      supports_developer_role: Type.Optional(Type.Boolean()),
29      supports_reasoning_effort: Type.Optional(Type.Boolean()),
30      supports_usage_in_streaming: Type.Optional(Type.Boolean()),
31      max_tokens_field: Type.Optional(Type.Union([Type.Literal("max_tokens"), Type.Literal("max_completion_tokens")])),
32      requires_tool_result_name: Type.Optional(Type.Boolean()),
33      requires_assistant_after_tool_result: Type.Optional(Type.Boolean()),
34      requires_thinking_as_text: Type.Optional(Type.Boolean()),
35      requires_mistral_tool_ids: Type.Optional(Type.Boolean()),
36      thinking_format: Type.Optional(Type.Union([Type.Literal("openai"), Type.Literal("zai")])),
37    })
38  ),
39});
40
41export const ConfigSchema = Type.Object({
42  defaults: Type.Object({
43    model: Type.String(),
44    cleanup: Type.Boolean(),
45    kagi_session_token: Type.Optional(Type.String()),
46    tabstack_api_key: Type.Optional(Type.String()),
47  }),
48  web: Type.Object({
49    model: Type.Optional(Type.String()),
50    system_prompt_path: Type.Optional(Type.String()),
51    kagi_session_token: Type.Optional(Type.String()),
52    tabstack_api_key: Type.Optional(Type.String()),
53  }),
54  repo: Type.Object({
55    model: Type.Optional(Type.String()),
56    system_prompt_path: Type.Optional(Type.String()),
57    default_depth: Type.Optional(Type.Number({ minimum: 1 })),
58    blob_limit: Type.Optional(Type.String()),
59  }),
60  custom_models: Type.Optional(Type.Record(Type.String(), CustomModelSchema)),
61});
62
63/** Deep-partial version of ConfigSchema for validating TOML override files. */
64export function partialObject<T extends TProperties>(schema: TObject<T>) {
65  const partial: Record<string, unknown> = {};
66  for (const [key, value] of Object.entries(schema.properties)) {
67    const v = value as any;
68    const inner = v[Kind] === 'Object' && v.properties ? partialObject(v) : v;
69    partial[key] = Type.Optional(inner as any);
70  }
71  return Type.Object(partial as any);
72}
73
74export const PartialConfigSchema = Type.Object({
75  defaults: Type.Optional(partialObject(ConfigSchema.properties.defaults)),
76  web: Type.Optional(partialObject(ConfigSchema.properties.web)),
77  repo: Type.Optional(partialObject(ConfigSchema.properties.repo)),
78  custom_models: Type.Optional(Type.Record(Type.String(), CustomModelSchema)),
79});
80
81export type RumiloConfig = Static<typeof ConfigSchema>;
82export type CustomModelConfig = Static<typeof CustomModelSchema>;