File size: 3,479 Bytes
5d0a52f
5f8456f
5d0a52f
 
5f8456f
 
 
 
 
 
 
 
 
fce5280
 
5d0a52f
 
 
ab21b87
 
 
5d0a52f
 
 
 
ab21b87
5d0a52f
 
 
 
8b777a2
 
 
 
5f8456f
 
 
8b777a2
5f8456f
 
8b777a2
 
 
 
 
 
 
 
 
 
5d0a52f
2b84f47
 
 
 
 
 
8b777a2
 
5f8456f
 
5d0a52f
8b777a2
5f8456f
8b777a2
5d0a52f
8b777a2
5f8456f
8b777a2
 
 
 
 
 
 
 
 
 
5d0a52f
8b777a2
 
 
 
 
 
5d0a52f
 
 
8b777a2
 
 
5f8456f
 
 
8b777a2
 
 
 
 
 
 
5f8456f
 
 
 
 
fce5280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8b777a2
 
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
 * Model routes — pure route handlers reading from model-store singleton.
 */

import { Hono } from "hono";
import type { OpenAIModel, OpenAIModelList } from "../types/openai.js";
import {
  getModelCatalog,
  getModelAliases,
  getModelInfo,
  getModelStoreDebug,
  type CodexModelInfo,
} from "../models/model-store.js";
import { triggerImmediateRefresh } from "../models/model-fetcher.js";
import { getConfig } from "../config.js";

// --- Routes ---

/** Stable timestamp used for all model `created` fields (2023-11-14T22:13:20Z). */
const MODEL_CREATED_TIMESTAMP = 1700000000;

function toOpenAIModel(info: CodexModelInfo): OpenAIModel {
  return {
    id: info.id,
    object: "model",
    created: MODEL_CREATED_TIMESTAMP,
    owned_by: "openai",
  };
}

export function createModelRoutes(): Hono {
  const app = new Hono();

  app.get("/v1/models", (c) => {
    const catalog = getModelCatalog();
    const aliases = getModelAliases();

    // Include catalog models + aliases as separate entries
    const models: OpenAIModel[] = catalog.map(toOpenAIModel);
    for (const alias of Object.keys(aliases)) {
      models.push({
        id: alias,
        object: "model",
        created: MODEL_CREATED_TIMESTAMP,
        owned_by: "openai",
      });
    }
    const response: OpenAIModelList = { object: "list", data: models };
    return c.json(response);
  });

  // Full catalog with reasoning efforts (for dashboard UI)
  // Must be before :modelId to avoid being matched as a model ID
  app.get("/v1/models/catalog", (c) => {
    return c.json(getModelCatalog());
  });

  app.get("/v1/models/:modelId", (c) => {
    const modelId = c.req.param("modelId");
    const catalog = getModelCatalog();
    const aliases = getModelAliases();

    // Try direct match
    const info = catalog.find((m) => m.id === modelId);
    if (info) return c.json(toOpenAIModel(info));

    // Try alias
    const resolved = aliases[modelId];
    if (resolved) {
      return c.json({
        id: modelId,
        object: "model",
        created: MODEL_CREATED_TIMESTAMP,
        owned_by: "openai",
      });
    }

    c.status(404);
    return c.json({
      error: {
        message: `Model '${modelId}' not found`,
        type: "invalid_request_error",
        param: "model",
        code: "model_not_found",
      },
    });
  });

  // Extended endpoint: model details with reasoning efforts
  app.get("/v1/models/:modelId/info", (c) => {
    const modelId = c.req.param("modelId");
    const aliases = getModelAliases();
    const resolved = aliases[modelId] ?? modelId;
    const info = getModelInfo(resolved);
    if (!info) {
      c.status(404);
      return c.json({ error: `Model '${modelId}' not found` });
    }
    return c.json(info);
  });

  // Debug endpoint: model store internals
  app.get("/debug/models", (c) => {
    return c.json(getModelStoreDebug());
  });

  // Admin endpoint: trigger immediate model refresh
  app.post("/admin/refresh-models", (c) => {
    const config = getConfig();
    const configKey = config.server.proxy_api_key;
    if (configKey) {
      const authHeader = c.req.header("Authorization") ?? "";
      const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
      if (token !== configKey) {
        c.status(401);
        return c.json({ error: "Unauthorized" });
      }
    }
    triggerImmediateRefresh();
    return c.json({ ok: true, message: "Model refresh triggered" });
  });

  return app;
}