oh-pi 0.1.45 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tui/provider-setup.js +80 -46
- package/package.json +1 -1
|
@@ -2,22 +2,69 @@ import * as p from "@clack/prompts";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { t } from "../i18n.js";
|
|
4
4
|
import { PROVIDERS } from "../types.js";
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/** Provider API base URLs for dynamic model fetching */
|
|
6
|
+
const PROVIDER_API_URLS = {
|
|
7
|
+
anthropic: "https://api.anthropic.com",
|
|
8
|
+
openai: "https://api.openai.com",
|
|
9
|
+
google: "https://generativelanguage.googleapis.com",
|
|
10
|
+
groq: "https://api.groq.com",
|
|
11
|
+
openrouter: "https://openrouter.ai",
|
|
12
|
+
xai: "https://api.x.ai",
|
|
13
|
+
mistral: "https://api.mistral.ai",
|
|
14
|
+
};
|
|
15
|
+
/** Fetch models dynamically — tries multiple API styles */
|
|
16
|
+
async function fetchModels(provider, baseUrl, apiKey) {
|
|
17
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
18
|
+
const resolvedKey = process.env[apiKey] ?? apiKey;
|
|
19
|
+
// Try Anthropic-style: GET /v1/models with x-api-key header
|
|
20
|
+
if (provider === "anthropic") {
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(`${base}/v1/models`, {
|
|
23
|
+
headers: { "x-api-key": resolvedKey, "anthropic-version": "2023-06-01" },
|
|
24
|
+
signal: AbortSignal.timeout(8000),
|
|
25
|
+
});
|
|
26
|
+
if (res.ok) {
|
|
27
|
+
const json = await res.json();
|
|
28
|
+
const models = (json.data ?? []).map(m => m.id).sort();
|
|
29
|
+
if (models.length > 0)
|
|
30
|
+
return models;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch { /* fall through */ }
|
|
34
|
+
}
|
|
35
|
+
// Try Google-style: GET /v1beta/models with key param
|
|
36
|
+
if (provider === "google") {
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(`${base}/v1beta/models?key=${resolvedKey}`, {
|
|
39
|
+
signal: AbortSignal.timeout(8000),
|
|
40
|
+
});
|
|
41
|
+
if (res.ok) {
|
|
42
|
+
const json = await res.json();
|
|
43
|
+
const models = (json.models ?? [])
|
|
44
|
+
.map(m => m.name.replace("models/", ""))
|
|
45
|
+
.filter(m => m.includes("gemini"))
|
|
46
|
+
.sort();
|
|
47
|
+
if (models.length > 0)
|
|
48
|
+
return models;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch { /* fall through */ }
|
|
52
|
+
}
|
|
53
|
+
// Try OpenAI-compatible: GET /v1/models with Bearer auth
|
|
8
54
|
try {
|
|
9
|
-
const res = await fetch(
|
|
10
|
-
headers: { Authorization: `Bearer ${
|
|
55
|
+
const res = await fetch(`${base}/v1/models`, {
|
|
56
|
+
headers: { Authorization: `Bearer ${resolvedKey}` },
|
|
11
57
|
signal: AbortSignal.timeout(8000),
|
|
12
58
|
});
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return [];
|
|
59
|
+
if (res.ok) {
|
|
60
|
+
const json = await res.json();
|
|
61
|
+
const models = (json.data ?? []).map(m => m.id).sort();
|
|
62
|
+
if (models.length > 0)
|
|
63
|
+
return models;
|
|
64
|
+
}
|
|
20
65
|
}
|
|
66
|
+
catch { /* fall through */ }
|
|
67
|
+
return [];
|
|
21
68
|
}
|
|
22
69
|
export async function setupProviders(env) {
|
|
23
70
|
const entries = Object.entries(PROVIDERS);
|
|
@@ -95,8 +142,9 @@ export async function setupProviders(env) {
|
|
|
95
142
|
else {
|
|
96
143
|
apiKey = await promptKey(info.label);
|
|
97
144
|
}
|
|
98
|
-
//
|
|
99
|
-
const
|
|
145
|
+
// Dynamic model fetch — always try, fall back to static list
|
|
146
|
+
const fetchUrl = baseUrl || PROVIDER_API_URLS[name];
|
|
147
|
+
const defaultModel = await selectModel(name, info.label, info.models, fetchUrl, apiKey);
|
|
100
148
|
configs.push({ name, apiKey, defaultModel, baseUrl });
|
|
101
149
|
p.log.success(t("provider.configured", { label: info.label }));
|
|
102
150
|
}
|
|
@@ -131,53 +179,39 @@ async function setupCustomProvider() {
|
|
|
131
179
|
apiKey = await promptKey(name);
|
|
132
180
|
}
|
|
133
181
|
// Dynamic model fetch
|
|
134
|
-
const
|
|
135
|
-
s.start(t("provider.fetchingModels", { source: baseUrl }));
|
|
136
|
-
const models = await fetchModels(baseUrl, apiKey);
|
|
137
|
-
s.stop(models.length > 0 ? t("provider.foundModels", { count: models.length }) : t("provider.noModels"));
|
|
138
|
-
let defaultModel;
|
|
139
|
-
if (models.length > 0) {
|
|
140
|
-
const model = await p.select({
|
|
141
|
-
message: t("provider.selectModel", { label: name }),
|
|
142
|
-
options: models.slice(0, 30).map(m => ({ value: m, label: m })),
|
|
143
|
-
});
|
|
144
|
-
if (p.isCancel(model)) {
|
|
145
|
-
p.cancel(t("cancelled"));
|
|
146
|
-
process.exit(0);
|
|
147
|
-
}
|
|
148
|
-
defaultModel = model;
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
const model = await p.text({
|
|
152
|
-
message: t("provider.modelName", { label: name }),
|
|
153
|
-
placeholder: t("provider.modelNamePlaceholder"),
|
|
154
|
-
validate: (v) => (!v || v.trim().length === 0) ? t("provider.modelNameRequired") : undefined,
|
|
155
|
-
});
|
|
156
|
-
if (p.isCancel(model)) {
|
|
157
|
-
p.cancel(t("cancelled"));
|
|
158
|
-
process.exit(0);
|
|
159
|
-
}
|
|
160
|
-
defaultModel = model;
|
|
161
|
-
}
|
|
182
|
+
const defaultModel = await selectModel(name, name, [], baseUrl, apiKey);
|
|
162
183
|
p.log.success(t("provider.customConfigured", { name, url: baseUrl }));
|
|
163
184
|
return { name, apiKey, defaultModel, baseUrl };
|
|
164
185
|
}
|
|
165
|
-
async function selectModel(label, staticModels, baseUrl, apiKey) {
|
|
186
|
+
async function selectModel(provider, label, staticModels, baseUrl, apiKey) {
|
|
166
187
|
let models = staticModels;
|
|
167
|
-
//
|
|
188
|
+
// Always try dynamic fetch
|
|
168
189
|
if (baseUrl && apiKey) {
|
|
169
190
|
const s = p.spinner();
|
|
170
191
|
s.start(t("provider.fetchingModels", { source: label }));
|
|
171
|
-
const fetched = await fetchModels(baseUrl, apiKey);
|
|
192
|
+
const fetched = await fetchModels(provider, baseUrl, apiKey);
|
|
172
193
|
s.stop(fetched.length > 0 ? t("provider.foundModels", { count: fetched.length }) : t("provider.defaultModelList"));
|
|
173
194
|
if (fetched.length > 0)
|
|
174
195
|
models = fetched;
|
|
175
196
|
}
|
|
197
|
+
if (models.length === 0) {
|
|
198
|
+
// No models found — manual input
|
|
199
|
+
const model = await p.text({
|
|
200
|
+
message: t("provider.modelName", { label }),
|
|
201
|
+
placeholder: t("provider.modelNamePlaceholder"),
|
|
202
|
+
validate: (v) => (!v || v.trim().length === 0) ? t("provider.modelNameRequired") : undefined,
|
|
203
|
+
});
|
|
204
|
+
if (p.isCancel(model)) {
|
|
205
|
+
p.cancel(t("cancelled"));
|
|
206
|
+
process.exit(0);
|
|
207
|
+
}
|
|
208
|
+
return model;
|
|
209
|
+
}
|
|
176
210
|
if (models.length === 1)
|
|
177
211
|
return models[0];
|
|
178
212
|
const model = await p.select({
|
|
179
213
|
message: t("provider.selectModel", { label }),
|
|
180
|
-
options: models.slice(0,
|
|
214
|
+
options: models.slice(0, 50).map(m => ({ value: m, label: m })),
|
|
181
215
|
});
|
|
182
216
|
if (p.isCancel(model)) {
|
|
183
217
|
p.cancel(t("cancelled"));
|