@ottocode/sdk 0.1.244 → 0.1.246
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/package.json +3 -2
- package/src/auth/src/index.ts +2 -2
- package/src/auth/src/wallet.ts +5 -5
- package/src/config/src/index.ts +7 -2
- package/src/config/src/manager.ts +106 -30
- package/src/core/src/index.ts +6 -1
- package/src/core/src/providers/resolver.ts +37 -9
- package/src/core/src/tools/builtin/bash.ts +2 -2
- package/src/core/src/tools/builtin/bash.txt +1 -1
- package/src/core/src/tools/builtin/fs/edit-shared.ts +1 -1
- package/src/core/src/tools/builtin/fs/edit.txt +2 -2
- package/src/core/src/tools/builtin/fs/write.txt +1 -1
- package/src/core/src/tools/loader.ts +133 -81
- package/src/core/src/utils/debug.ts +13 -0
- package/src/index.ts +47 -12
- package/src/prompts/src/agents/build.txt +3 -4
- package/src/prompts/src/providers/default.txt +1 -1
- package/src/prompts/src/providers/glm.txt +1 -1
- package/src/prompts/src/providers/google.txt +2 -2
- package/src/prompts/src/providers/moonshot.txt +1 -1
- package/src/prompts/src/providers/openai.txt +3 -3
- package/src/prompts/src/providers.ts +15 -0
- package/src/providers/src/authorization.ts +26 -1
- package/src/providers/src/catalog-manual.ts +41 -23
- package/src/providers/src/catalog-merged.ts +2 -2
- package/src/providers/src/catalog.ts +10284 -10283
- package/src/providers/src/env.ts +11 -6
- package/src/providers/src/index.ts +38 -12
- package/src/providers/src/ollama-discovery.ts +149 -0
- package/src/providers/src/{setu-client.ts → ottorouter-client.ts} +30 -30
- package/src/providers/src/pricing.ts +4 -1
- package/src/providers/src/registry.ts +258 -0
- package/src/providers/src/utils.ts +11 -4
- package/src/providers/src/validate.ts +63 -2
- package/src/skills/index.ts +3 -0
- package/src/skills/tool.ts +28 -36
- package/src/types/src/config.ts +34 -8
- package/src/types/src/index.ts +4 -0
- package/src/types/src/provider.ts +34 -4
package/src/providers/src/env.ts
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
import type { ProviderId } from '../../types/src/index.ts';
|
|
1
|
+
import type { BuiltInProviderId, ProviderId } from '../../types/src/index.ts';
|
|
2
2
|
|
|
3
|
-
const ENV_VARS: Record<
|
|
3
|
+
const ENV_VARS: Record<BuiltInProviderId, string> = {
|
|
4
4
|
openai: 'OPENAI_API_KEY',
|
|
5
5
|
anthropic: 'ANTHROPIC_API_KEY',
|
|
6
6
|
google: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
7
|
+
'ollama-cloud': 'OLLAMA_API_KEY',
|
|
7
8
|
openrouter: 'OPENROUTER_API_KEY',
|
|
8
9
|
opencode: 'OPENCODE_API_KEY',
|
|
9
10
|
copilot: 'GITHUB_TOKEN',
|
|
10
|
-
|
|
11
|
+
ottorouter: 'OTTOROUTER_PRIVATE_KEY',
|
|
11
12
|
zai: 'ZAI_API_KEY',
|
|
12
13
|
'zai-coding': 'ZAI_CODING_API_KEY',
|
|
13
14
|
moonshot: 'MOONSHOT_API_KEY',
|
|
14
15
|
minimax: 'MINIMAX_API_KEY',
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
export function providerEnvVar(provider: ProviderId): string {
|
|
18
|
-
return ENV_VARS[provider];
|
|
18
|
+
export function providerEnvVar(provider: ProviderId): string | undefined {
|
|
19
|
+
return ENV_VARS[provider as BuiltInProviderId];
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function readEnvKey(provider: ProviderId): string | undefined {
|
|
23
|
+
if (!(provider in ENV_VARS) && provider !== 'copilot') {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
22
26
|
if (provider === 'copilot') {
|
|
23
27
|
const copilotToken =
|
|
24
28
|
process.env.COPILOT_GITHUB_TOKEN ??
|
|
@@ -28,13 +32,14 @@ export function readEnvKey(provider: ProviderId): string | undefined {
|
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
const key = providerEnvVar(provider);
|
|
35
|
+
if (!key) return undefined;
|
|
31
36
|
const value = process.env[key];
|
|
32
37
|
return value?.length ? value : undefined;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export function setEnvKey(provider: ProviderId, value: string | undefined) {
|
|
36
41
|
const key = providerEnvVar(provider);
|
|
37
|
-
if (value) {
|
|
42
|
+
if (key && value) {
|
|
38
43
|
process.env[key] = value;
|
|
39
44
|
}
|
|
40
45
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export { isProviderAuthorized, ensureProviderEnv } from './authorization.ts';
|
|
2
2
|
export { catalog } from './catalog-merged.ts';
|
|
3
3
|
export type {
|
|
4
|
+
BuiltInProviderId,
|
|
4
5
|
ProviderId,
|
|
6
|
+
ProviderCompatibility,
|
|
7
|
+
ProviderPromptFamily,
|
|
5
8
|
ModelInfo,
|
|
6
9
|
ModelProviderBinding,
|
|
7
10
|
ProviderCatalogEntry,
|
|
@@ -21,26 +24,49 @@ export {
|
|
|
21
24
|
modelSupportsReasoning,
|
|
22
25
|
} from './utils.ts';
|
|
23
26
|
export type { UnderlyingProviderKey } from './utils.ts';
|
|
27
|
+
export {
|
|
28
|
+
discoverOllamaModels,
|
|
29
|
+
normalizeOllamaBaseURL,
|
|
30
|
+
} from './ollama-discovery.ts';
|
|
31
|
+
export type {
|
|
32
|
+
DiscoverOllamaOptions,
|
|
33
|
+
DiscoverOllamaResult,
|
|
34
|
+
} from './ollama-discovery.ts';
|
|
35
|
+
export {
|
|
36
|
+
isBuiltInProviderId,
|
|
37
|
+
getProviderSettings,
|
|
38
|
+
getProviderDefinition,
|
|
39
|
+
hasConfiguredProvider,
|
|
40
|
+
getConfiguredProviderIds,
|
|
41
|
+
getConfiguredProviderModels,
|
|
42
|
+
getConfiguredProviderDefaultModel,
|
|
43
|
+
providerAllowsAnyModel,
|
|
44
|
+
hasConfiguredModel,
|
|
45
|
+
getConfiguredProviderFamily,
|
|
46
|
+
getConfiguredProviderEnvVar,
|
|
47
|
+
getConfiguredProviderApiKey,
|
|
48
|
+
} from './registry.ts';
|
|
49
|
+
export type { ResolvedProviderDefinition } from './registry.ts';
|
|
24
50
|
export { validateProviderModel } from './validate.ts';
|
|
25
51
|
export { estimateModelCostUsd } from './pricing.ts';
|
|
26
52
|
export { providerEnvVar, readEnvKey, setEnvKey } from './env.ts';
|
|
27
53
|
export {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
createOttoRouter,
|
|
55
|
+
createOttoRouterFetch,
|
|
56
|
+
createOttoRouterModel,
|
|
57
|
+
fetchOttoRouterBalance,
|
|
32
58
|
getPublicKeyFromPrivate,
|
|
33
59
|
fetchSolanaUsdcBalance,
|
|
34
|
-
} from './
|
|
60
|
+
} from './ottorouter-client.ts';
|
|
35
61
|
export type {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
OttoRouterAuth,
|
|
63
|
+
OttoRouterInstance,
|
|
64
|
+
OttoRouterProviderOptions,
|
|
65
|
+
OttoRouterPaymentCallbacks,
|
|
66
|
+
OttoRouterBalanceUpdate,
|
|
67
|
+
OttoRouterBalanceResponse,
|
|
42
68
|
SolanaUsdcBalanceResponse,
|
|
43
|
-
} from './
|
|
69
|
+
} from './ottorouter-client.ts';
|
|
44
70
|
export {
|
|
45
71
|
createOpenAIOAuthFetch,
|
|
46
72
|
createOpenAIOAuthModel,
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { ModelInfo } from '../../types/src/index.ts';
|
|
2
|
+
|
|
3
|
+
export type DiscoverOllamaOptions = {
|
|
4
|
+
baseURL: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
fetch?: typeof globalThis.fetch;
|
|
7
|
+
includeDetails?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type DiscoverOllamaResult = {
|
|
11
|
+
baseURL: string;
|
|
12
|
+
models: ModelInfo[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type OllamaTagsResponse = {
|
|
16
|
+
models?: Array<
|
|
17
|
+
{
|
|
18
|
+
name?: string;
|
|
19
|
+
model?: string;
|
|
20
|
+
} & Record<string, unknown>
|
|
21
|
+
>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type OllamaShowResponse = {
|
|
25
|
+
capabilities?: string[];
|
|
26
|
+
details?: {
|
|
27
|
+
family?: string;
|
|
28
|
+
parameter_size?: string;
|
|
29
|
+
quantization_level?: string;
|
|
30
|
+
};
|
|
31
|
+
model_info?: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function normalizeOllamaBaseURL(baseURL: string): string {
|
|
35
|
+
const trimmed = baseURL.trim().replace(/\/$/, '');
|
|
36
|
+
return trimmed
|
|
37
|
+
.replace(/\/api\/(chat|generate|embed|show|tags)\/?$/, '')
|
|
38
|
+
.replace(/\/api\/?$/, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function discoverOllamaModels(
|
|
42
|
+
options: DiscoverOllamaOptions,
|
|
43
|
+
): Promise<DiscoverOllamaResult> {
|
|
44
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
45
|
+
const baseURL = normalizeOllamaBaseURL(options.baseURL);
|
|
46
|
+
const headers = buildHeaders(options.apiKey);
|
|
47
|
+
|
|
48
|
+
const tagsResponse = await fetchImpl(`${baseURL}/api/tags`, {
|
|
49
|
+
method: 'GET',
|
|
50
|
+
headers,
|
|
51
|
+
});
|
|
52
|
+
if (!tagsResponse.ok) {
|
|
53
|
+
throw new Error(`Failed to fetch Ollama models: ${tagsResponse.status}`);
|
|
54
|
+
}
|
|
55
|
+
const tagsPayload = (await tagsResponse.json()) as OllamaTagsResponse;
|
|
56
|
+
const ids = (tagsPayload.models ?? [])
|
|
57
|
+
.map((model) => String(model.name ?? model.model ?? '').trim())
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
|
|
60
|
+
if (options.includeDetails === false) {
|
|
61
|
+
return {
|
|
62
|
+
baseURL,
|
|
63
|
+
models: ids.map((id) => ({ id, label: id })),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const models = await Promise.all(
|
|
68
|
+
ids.map(
|
|
69
|
+
async (id) =>
|
|
70
|
+
await discoverSingleOllamaModel(fetchImpl, baseURL, headers, id),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
baseURL,
|
|
76
|
+
models: models.filter((model): model is ModelInfo => Boolean(model)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function discoverSingleOllamaModel(
|
|
81
|
+
fetchImpl: typeof globalThis.fetch,
|
|
82
|
+
baseURL: string,
|
|
83
|
+
headers: HeadersInit | undefined,
|
|
84
|
+
id: string,
|
|
85
|
+
): Promise<ModelInfo> {
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetchImpl(`${baseURL}/api/show`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: {
|
|
90
|
+
'content-type': 'application/json',
|
|
91
|
+
...(headers ?? {}),
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({ model: id }),
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) return { id, label: id };
|
|
96
|
+
const payload = (await response.json()) as OllamaShowResponse;
|
|
97
|
+
return mapOllamaShowResponse(id, payload);
|
|
98
|
+
} catch {
|
|
99
|
+
return { id, label: id };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function mapOllamaShowResponse(
|
|
104
|
+
id: string,
|
|
105
|
+
payload: OllamaShowResponse,
|
|
106
|
+
): ModelInfo {
|
|
107
|
+
const capabilities = new Set(
|
|
108
|
+
(payload.capabilities ?? []).map((cap) => cap.toLowerCase()),
|
|
109
|
+
);
|
|
110
|
+
const inputModalities = ['text'];
|
|
111
|
+
if (capabilities.has('vision')) inputModalities.push('image');
|
|
112
|
+
if (capabilities.has('audio')) inputModalities.push('audio');
|
|
113
|
+
|
|
114
|
+
const contextLength = extractContextLength(payload.model_info);
|
|
115
|
+
const parameterSize = payload.details?.parameter_size;
|
|
116
|
+
const quantization = payload.details?.quantization_level;
|
|
117
|
+
const labelParts = [id];
|
|
118
|
+
if (parameterSize) labelParts.push(parameterSize);
|
|
119
|
+
if (quantization) labelParts.push(quantization);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
id,
|
|
123
|
+
label: labelParts.join(' · '),
|
|
124
|
+
toolCall: capabilities.has('tools'),
|
|
125
|
+
reasoningText: capabilities.has('thinking'),
|
|
126
|
+
modalities: {
|
|
127
|
+
input: inputModalities,
|
|
128
|
+
output: ['text'],
|
|
129
|
+
},
|
|
130
|
+
limit: contextLength ? { context: contextLength } : undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function extractContextLength(
|
|
135
|
+
modelInfo: Record<string, unknown> | undefined,
|
|
136
|
+
): number | undefined {
|
|
137
|
+
if (!modelInfo) return undefined;
|
|
138
|
+
for (const [key, value] of Object.entries(modelInfo)) {
|
|
139
|
+
if (!key.endsWith('.context_length')) continue;
|
|
140
|
+
const parsed = Number(value);
|
|
141
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildHeaders(apiKey?: string): HeadersInit | undefined {
|
|
147
|
+
if (!apiKey) return undefined;
|
|
148
|
+
return { Authorization: `Bearer ${apiKey}` };
|
|
149
|
+
}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
createOttoRouter,
|
|
3
3
|
fetchBalance,
|
|
4
4
|
fetchWalletUsdcBalance,
|
|
5
5
|
getPublicKeyFromPrivate as _getPublicKeyFromPrivate,
|
|
6
|
-
type
|
|
6
|
+
type OttoRouterConfig,
|
|
7
7
|
type PaymentCallbacks,
|
|
8
8
|
type BalanceUpdate,
|
|
9
9
|
type BalanceResponse,
|
|
10
10
|
type WalletUsdcBalance,
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
} from '@
|
|
11
|
+
type OttoRouterAuth as _OttoRouterAuth,
|
|
12
|
+
type OttoRouterInstance,
|
|
13
|
+
} from '@ottorouter/ai-sdk';
|
|
14
14
|
import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
15
15
|
|
|
16
|
-
export type
|
|
16
|
+
export type OttoRouterBalanceUpdate = BalanceUpdate;
|
|
17
17
|
|
|
18
|
-
export type
|
|
18
|
+
export type OttoRouterPaymentCallbacks = PaymentCallbacks;
|
|
19
19
|
|
|
20
|
-
export type
|
|
20
|
+
export type OttoRouterProviderOptions = {
|
|
21
21
|
baseURL?: string;
|
|
22
22
|
rpcURL?: string;
|
|
23
23
|
network?: string;
|
|
24
24
|
maxRequestAttempts?: number;
|
|
25
25
|
maxPaymentAttempts?: number;
|
|
26
|
-
callbacks?:
|
|
26
|
+
callbacks?: OttoRouterPaymentCallbacks;
|
|
27
27
|
providerNpm?: string;
|
|
28
28
|
promptCacheKey?: string;
|
|
29
29
|
promptCacheRetention?: 'in_memory' | '24h';
|
|
@@ -32,33 +32,33 @@ export type SetuProviderOptions = {
|
|
|
32
32
|
middleware?: LanguageModelV3Middleware | LanguageModelV3Middleware[];
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export type
|
|
35
|
+
export type OttoRouterAuth = _OttoRouterAuth;
|
|
36
36
|
|
|
37
|
-
export type
|
|
37
|
+
export type OttoRouterBalanceResponse = BalanceResponse;
|
|
38
38
|
|
|
39
39
|
export type SolanaUsdcBalanceResponse = WalletUsdcBalance;
|
|
40
40
|
|
|
41
|
-
export function
|
|
42
|
-
auth:
|
|
43
|
-
options:
|
|
41
|
+
export function createOttoRouterFetch(
|
|
42
|
+
auth: OttoRouterAuth,
|
|
43
|
+
options: OttoRouterProviderOptions = {},
|
|
44
44
|
): typeof fetch {
|
|
45
|
-
const
|
|
46
|
-
return
|
|
45
|
+
const ottorouter = createOttoRouter(buildOttoRouterConfig(auth, options));
|
|
46
|
+
return ottorouter.fetch() as typeof fetch;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export function
|
|
49
|
+
export function createOttoRouterModel(
|
|
50
50
|
model: string,
|
|
51
|
-
auth:
|
|
52
|
-
options:
|
|
51
|
+
auth: OttoRouterAuth,
|
|
52
|
+
options: OttoRouterProviderOptions = {},
|
|
53
53
|
) {
|
|
54
|
-
const
|
|
55
|
-
return
|
|
54
|
+
const ottorouter = createOttoRouter(buildOttoRouterConfig(auth, options));
|
|
55
|
+
return ottorouter.model(model);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function
|
|
59
|
-
auth:
|
|
60
|
-
options:
|
|
61
|
-
):
|
|
58
|
+
function buildOttoRouterConfig(
|
|
59
|
+
auth: OttoRouterAuth,
|
|
60
|
+
options: OttoRouterProviderOptions = {},
|
|
61
|
+
): OttoRouterConfig {
|
|
62
62
|
return {
|
|
63
63
|
auth,
|
|
64
64
|
baseURL: options.baseURL,
|
|
@@ -78,10 +78,10 @@ function buildSetuConfig(
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
export async function
|
|
82
|
-
auth:
|
|
81
|
+
export async function fetchOttoRouterBalance(
|
|
82
|
+
auth: OttoRouterAuth,
|
|
83
83
|
baseURL?: string,
|
|
84
|
-
): Promise<
|
|
84
|
+
): Promise<OttoRouterBalanceResponse | null> {
|
|
85
85
|
return fetchBalance(auth, baseURL);
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -90,7 +90,7 @@ export function getPublicKeyFromPrivate(privateKey: string): string | null {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export async function fetchSolanaUsdcBalance(
|
|
93
|
-
auth:
|
|
93
|
+
auth: OttoRouterAuth,
|
|
94
94
|
network: 'mainnet' | 'devnet' = 'mainnet',
|
|
95
95
|
): Promise<SolanaUsdcBalanceResponse | null> {
|
|
96
96
|
if (auth.privateKey) {
|
|
@@ -105,4 +105,4 @@ export async function fetchSolanaUsdcBalance(
|
|
|
105
105
|
return null;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
export {
|
|
108
|
+
export { createOttoRouter, type OttoRouterInstance };
|
|
@@ -70,13 +70,16 @@ const pricingTable: Record<ProviderName, PricingEntry[]> = {
|
|
|
70
70
|
outputPerMillion: 10.5,
|
|
71
71
|
},
|
|
72
72
|
],
|
|
73
|
+
'ollama-cloud': [
|
|
74
|
+
// Pricing can vary by your Ollama Cloud model/account; leave undefined here
|
|
75
|
+
],
|
|
73
76
|
openrouter: [
|
|
74
77
|
// Prefer catalog pricing; keep empty to defer to catalog or undefined
|
|
75
78
|
],
|
|
76
79
|
opencode: [
|
|
77
80
|
// Pricing from catalog entries; leave empty here
|
|
78
81
|
],
|
|
79
|
-
|
|
82
|
+
ottorouter: [
|
|
80
83
|
// Pricing from catalog entries; leave empty here
|
|
81
84
|
],
|
|
82
85
|
zai: [
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { catalog } from './catalog-merged.ts';
|
|
2
|
+
import { providerEnvVar } from './env.ts';
|
|
3
|
+
import { getUnderlyingProviderKey, providerIds } from './utils.ts';
|
|
4
|
+
import type {
|
|
5
|
+
BuiltInProviderId,
|
|
6
|
+
ModelInfo,
|
|
7
|
+
OttoConfig,
|
|
8
|
+
ProviderCompatibility,
|
|
9
|
+
ProviderId,
|
|
10
|
+
ProviderPromptFamily,
|
|
11
|
+
ProviderSettingsEntry,
|
|
12
|
+
} from '../../types/src/index.ts';
|
|
13
|
+
|
|
14
|
+
export type ResolvedProviderDefinition = {
|
|
15
|
+
id: ProviderId;
|
|
16
|
+
label: string;
|
|
17
|
+
source: 'built-in' | 'custom';
|
|
18
|
+
compatibility: ProviderCompatibility;
|
|
19
|
+
family: ProviderPromptFamily;
|
|
20
|
+
baseURL?: string;
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
apiKeyEnv?: string;
|
|
23
|
+
models: ModelInfo[];
|
|
24
|
+
allowAnyModel: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const BUILTIN_COMPATIBILITY: Record<BuiltInProviderId, ProviderCompatibility> =
|
|
28
|
+
{
|
|
29
|
+
openai: 'openai',
|
|
30
|
+
anthropic: 'anthropic',
|
|
31
|
+
google: 'google',
|
|
32
|
+
'ollama-cloud': 'ollama',
|
|
33
|
+
openrouter: 'openrouter',
|
|
34
|
+
opencode: 'openai-compatible',
|
|
35
|
+
copilot: 'openai',
|
|
36
|
+
ottorouter: 'openrouter',
|
|
37
|
+
zai: 'openai-compatible',
|
|
38
|
+
'zai-coding': 'openai-compatible',
|
|
39
|
+
moonshot: 'openai-compatible',
|
|
40
|
+
minimax: 'anthropic',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const BUILTIN_FAMILY: Record<BuiltInProviderId, ProviderPromptFamily> = {
|
|
44
|
+
openai: 'openai',
|
|
45
|
+
anthropic: 'anthropic',
|
|
46
|
+
google: 'google',
|
|
47
|
+
'ollama-cloud': 'openai-compatible',
|
|
48
|
+
openrouter: 'openai-compatible',
|
|
49
|
+
opencode: 'openai-compatible',
|
|
50
|
+
copilot: 'openai',
|
|
51
|
+
ottorouter: 'openai-compatible',
|
|
52
|
+
zai: 'glm',
|
|
53
|
+
'zai-coding': 'glm',
|
|
54
|
+
moonshot: 'moonshot',
|
|
55
|
+
minimax: 'minimax',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function normalizeCustomModels(
|
|
59
|
+
models: Array<string | ModelInfo> | undefined,
|
|
60
|
+
): ModelInfo[] {
|
|
61
|
+
return (models ?? [])
|
|
62
|
+
.map((model) => {
|
|
63
|
+
if (typeof model === 'string') {
|
|
64
|
+
const id = String(model).trim();
|
|
65
|
+
return id ? ({ id, label: id } satisfies ModelInfo) : null;
|
|
66
|
+
}
|
|
67
|
+
const id = String(model.id ?? '').trim();
|
|
68
|
+
if (!id) return null;
|
|
69
|
+
return {
|
|
70
|
+
...model,
|
|
71
|
+
id,
|
|
72
|
+
label: model.label?.trim() || id,
|
|
73
|
+
} satisfies ModelInfo;
|
|
74
|
+
})
|
|
75
|
+
.filter((model): model is ModelInfo => Boolean(model));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeOptionalText(value: string | undefined): string | undefined {
|
|
79
|
+
if (!value) return undefined;
|
|
80
|
+
const trimmed = value.trim();
|
|
81
|
+
if (!trimmed || trimmed === 'undefined' || trimmed === 'null') {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return trimmed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveCustomCompatibility(
|
|
88
|
+
settings: ProviderSettingsEntry,
|
|
89
|
+
): ProviderCompatibility {
|
|
90
|
+
return settings.compatibility ?? 'openai-compatible';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveCustomFamily(
|
|
94
|
+
settings: ProviderSettingsEntry,
|
|
95
|
+
): ProviderPromptFamily {
|
|
96
|
+
return settings.family ?? 'default';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveCustomProviderLabel(
|
|
100
|
+
id: string,
|
|
101
|
+
settings: ProviderSettingsEntry,
|
|
102
|
+
): string {
|
|
103
|
+
return settings.label ?? id;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function isBuiltInProviderId(
|
|
107
|
+
value: unknown,
|
|
108
|
+
): value is BuiltInProviderId {
|
|
109
|
+
return (
|
|
110
|
+
typeof value === 'string' &&
|
|
111
|
+
providerIds.includes(value as BuiltInProviderId)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getProviderSettings(
|
|
116
|
+
cfg: OttoConfig,
|
|
117
|
+
provider: ProviderId,
|
|
118
|
+
): ProviderSettingsEntry | undefined {
|
|
119
|
+
return cfg.providers[String(provider)];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getProviderDefinition(
|
|
123
|
+
cfg: OttoConfig,
|
|
124
|
+
provider: ProviderId,
|
|
125
|
+
): ResolvedProviderDefinition | undefined {
|
|
126
|
+
const settings = getProviderSettings(cfg, provider);
|
|
127
|
+
if (isBuiltInProviderId(provider)) {
|
|
128
|
+
const entry = catalog[provider];
|
|
129
|
+
if (!entry) return undefined;
|
|
130
|
+
return {
|
|
131
|
+
id: provider,
|
|
132
|
+
label: settings?.label ?? entry.label ?? provider,
|
|
133
|
+
source: 'built-in',
|
|
134
|
+
compatibility: BUILTIN_COMPATIBILITY[provider],
|
|
135
|
+
family: BUILTIN_FAMILY[provider],
|
|
136
|
+
baseURL: normalizeOptionalText(settings?.baseURL),
|
|
137
|
+
apiKey: normalizeOptionalText(settings?.apiKey),
|
|
138
|
+
apiKeyEnv:
|
|
139
|
+
normalizeOptionalText(settings?.apiKeyEnv) ?? providerEnvVar(provider),
|
|
140
|
+
models: entry.models,
|
|
141
|
+
allowAnyModel: provider === 'ollama-cloud',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!settings?.custom) return undefined;
|
|
146
|
+
const models = normalizeCustomModels(settings.models);
|
|
147
|
+
return {
|
|
148
|
+
id: provider,
|
|
149
|
+
label: resolveCustomProviderLabel(provider, settings),
|
|
150
|
+
source: 'custom',
|
|
151
|
+
compatibility: resolveCustomCompatibility(settings),
|
|
152
|
+
family: resolveCustomFamily(settings),
|
|
153
|
+
baseURL: normalizeOptionalText(settings.baseURL),
|
|
154
|
+
apiKey: normalizeOptionalText(settings.apiKey),
|
|
155
|
+
apiKeyEnv: normalizeOptionalText(settings.apiKeyEnv),
|
|
156
|
+
models,
|
|
157
|
+
allowAnyModel: settings.allowAnyModel === true || models.length === 0,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function hasConfiguredProvider(
|
|
162
|
+
cfg: OttoConfig,
|
|
163
|
+
provider: ProviderId | undefined,
|
|
164
|
+
): provider is ProviderId {
|
|
165
|
+
if (!provider || typeof provider !== 'string') return false;
|
|
166
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
167
|
+
if (!definition) return false;
|
|
168
|
+
if (definition.source === 'built-in') return true;
|
|
169
|
+
return getProviderSettings(cfg, provider)?.enabled !== false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getConfiguredProviderIds(
|
|
173
|
+
cfg: OttoConfig,
|
|
174
|
+
options?: { includeDisabled?: boolean },
|
|
175
|
+
): ProviderId[] {
|
|
176
|
+
const includeDisabled = options?.includeDisabled === true;
|
|
177
|
+
const ids = new Set<ProviderId>([
|
|
178
|
+
...providerIds,
|
|
179
|
+
...Object.keys(cfg.providers),
|
|
180
|
+
cfg.defaults.provider,
|
|
181
|
+
]);
|
|
182
|
+
return Array.from(ids).filter((provider) => {
|
|
183
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
184
|
+
if (!definition) return false;
|
|
185
|
+
if (definition.source === 'built-in') return true;
|
|
186
|
+
if (includeDisabled) return true;
|
|
187
|
+
return getProviderSettings(cfg, provider)?.enabled !== false;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getConfiguredProviderModels(
|
|
192
|
+
cfg: OttoConfig,
|
|
193
|
+
provider: ProviderId,
|
|
194
|
+
): ModelInfo[] {
|
|
195
|
+
return getProviderDefinition(cfg, provider)?.models ?? [];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function getConfiguredProviderDefaultModel(
|
|
199
|
+
cfg: OttoConfig,
|
|
200
|
+
provider: ProviderId,
|
|
201
|
+
): string | undefined {
|
|
202
|
+
return getConfiguredProviderModels(cfg, provider)[0]?.id;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function providerAllowsAnyModel(
|
|
206
|
+
cfg: OttoConfig,
|
|
207
|
+
provider: ProviderId,
|
|
208
|
+
): boolean {
|
|
209
|
+
return getProviderDefinition(cfg, provider)?.allowAnyModel === true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function hasConfiguredModel(
|
|
213
|
+
cfg: OttoConfig,
|
|
214
|
+
provider: ProviderId,
|
|
215
|
+
model: string | undefined,
|
|
216
|
+
): boolean {
|
|
217
|
+
if (!model) return false;
|
|
218
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
219
|
+
if (!definition) return false;
|
|
220
|
+
if (definition.allowAnyModel) return model.trim().length > 0;
|
|
221
|
+
return definition.models.some((entry) => entry.id === model);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getConfiguredProviderFamily(
|
|
225
|
+
cfg: OttoConfig,
|
|
226
|
+
provider: ProviderId,
|
|
227
|
+
model: string,
|
|
228
|
+
): ProviderPromptFamily | null {
|
|
229
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
230
|
+
if (!definition) return null;
|
|
231
|
+
if (definition.source === 'custom') return definition.family;
|
|
232
|
+
if (isBuiltInProviderId(provider)) {
|
|
233
|
+
return getUnderlyingProviderKey(provider, model) ?? definition.family;
|
|
234
|
+
}
|
|
235
|
+
return definition.family;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function getConfiguredProviderEnvVar(
|
|
239
|
+
cfg: OttoConfig,
|
|
240
|
+
provider: ProviderId,
|
|
241
|
+
): string | undefined {
|
|
242
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
243
|
+
return definition?.apiKeyEnv;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function getConfiguredProviderApiKey(
|
|
247
|
+
cfg: OttoConfig,
|
|
248
|
+
provider: ProviderId,
|
|
249
|
+
): string | undefined {
|
|
250
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
251
|
+
if (!definition) return undefined;
|
|
252
|
+
if (definition.apiKey?.length) return definition.apiKey;
|
|
253
|
+
if (definition.apiKeyEnv?.length) {
|
|
254
|
+
const value = process.env[definition.apiKeyEnv];
|
|
255
|
+
if (value?.length) return value;
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|