File size: 2,424 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
 * `modelService` — model configuration registry. Mirrors
 * `agent-core-v2/llm-adapter/model/model.ts` (`ModelRecordSchema`, including its
 * passthrough of unknown keys) and `agent-core-v2/llm-adapter/protocol/protocol.ts`
 * (`ProtocolSchema` — the four real wire protocols; `kimi` is a provider
 * `type`, not a protocol, and Vertex AI is a `providerOptions` mode of
 * `google-genai`). The schema name stays `modelConfigSchema` while the
 * engine type is `ModelRecord` — the contract-parity test pins that pairing.
 */

import { z } from 'zod';

import { maybe, noResult } from '../helpers.js';
import type { ServiceContract } from '../types.js';

const protocolSchema = z.enum([
  'anthropic',
  'openai',
  'openai_responses',
  'google-genai',
]);

const oAuthRefSchema = z.object({
  storage: z.enum(['file', 'keyring']),
  key: z.string().min(1),
  oauthHost: z.string().min(1).optional(),
});

const modelBaseSchema = z.object({
  providerId: z.string().optional(),

  baseUrl: z.string().optional(),
  apiKey: z.string().optional(),
  oauth: oAuthRefSchema.optional(),

  protocol: protocolSchema.optional(),

  name: z.string().optional(),
  aliases: z.array(z.string()).optional(),

  provider: z.string().optional(),
  model: z.string().optional(),
  maxContextSize: z.number().int().min(1).optional(),
  maxOutputSize: z.number().int().min(1).optional(),
  capabilities: z.array(z.string()).optional(),
  displayName: z.string().optional(),
  reasoningKey: z.string().optional(),
  adaptiveThinking: z.boolean().optional(),
  betaApi: z.boolean().optional(),
  supportEfforts: z.array(z.string()).optional(),
  defaultEffort: z.string().optional(),
});

const modelOverrideSchema = modelBaseSchema
  .omit({
    providerId: true,
    baseUrl: true,
    apiKey: true,
    oauth: true,
    protocol: true,
    name: true,
    aliases: true,
    provider: true,
    model: true,
    betaApi: true,
  })
  .partial();

export const modelConfigSchema = modelBaseSchema
  .extend({ overrides: modelOverrideSchema.optional() })
  .passthrough();

export const modelsContract = {
  get: { input: z.tuple([z.string()]), output: maybe(modelConfigSchema) },
  list: { input: z.tuple([]), output: z.record(z.string(), modelConfigSchema) },
  set: { input: z.tuple([z.string(), modelConfigSchema]), output: noResult },
  delete: { input: z.tuple([z.string()]), output: noResult },
} satisfies ServiceContract;