@ottocode/sdk 0.1.173
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/README.md +338 -0
- package/package.json +128 -0
- package/src/agent/types.ts +19 -0
- package/src/auth/src/copilot-oauth.ts +190 -0
- package/src/auth/src/index.ts +100 -0
- package/src/auth/src/oauth.ts +234 -0
- package/src/auth/src/openai-oauth.ts +394 -0
- package/src/auth/src/wallet.ts +51 -0
- package/src/browser.ts +32 -0
- package/src/config/src/index.ts +110 -0
- package/src/config/src/manager.ts +181 -0
- package/src/config/src/paths.ts +98 -0
- package/src/core/src/errors.ts +102 -0
- package/src/core/src/index.ts +108 -0
- package/src/core/src/providers/resolver.ts +244 -0
- package/src/core/src/streaming/artifacts.ts +41 -0
- package/src/core/src/terminals/bun-pty.ts +13 -0
- package/src/core/src/terminals/circular-buffer.ts +30 -0
- package/src/core/src/terminals/ensure-bun-pty.ts +70 -0
- package/src/core/src/terminals/index.ts +8 -0
- package/src/core/src/terminals/manager.ts +158 -0
- package/src/core/src/terminals/rust-libs.ts +30 -0
- package/src/core/src/terminals/terminal.ts +132 -0
- package/src/core/src/tools/bin-manager.ts +250 -0
- package/src/core/src/tools/builtin/bash.ts +155 -0
- package/src/core/src/tools/builtin/bash.txt +7 -0
- package/src/core/src/tools/builtin/file-cache.ts +39 -0
- package/src/core/src/tools/builtin/finish.ts +12 -0
- package/src/core/src/tools/builtin/finish.txt +10 -0
- package/src/core/src/tools/builtin/fs/cd.ts +19 -0
- package/src/core/src/tools/builtin/fs/cd.txt +5 -0
- package/src/core/src/tools/builtin/fs/index.ts +20 -0
- package/src/core/src/tools/builtin/fs/ls.ts +72 -0
- package/src/core/src/tools/builtin/fs/ls.txt +8 -0
- package/src/core/src/tools/builtin/fs/pwd.ts +17 -0
- package/src/core/src/tools/builtin/fs/pwd.txt +5 -0
- package/src/core/src/tools/builtin/fs/read.ts +119 -0
- package/src/core/src/tools/builtin/fs/read.txt +8 -0
- package/src/core/src/tools/builtin/fs/tree.ts +149 -0
- package/src/core/src/tools/builtin/fs/tree.txt +11 -0
- package/src/core/src/tools/builtin/fs/util.ts +95 -0
- package/src/core/src/tools/builtin/fs/write.ts +106 -0
- package/src/core/src/tools/builtin/fs/write.txt +11 -0
- package/src/core/src/tools/builtin/git.commit.txt +6 -0
- package/src/core/src/tools/builtin/git.diff.txt +5 -0
- package/src/core/src/tools/builtin/git.status.txt +5 -0
- package/src/core/src/tools/builtin/git.ts +151 -0
- package/src/core/src/tools/builtin/glob.ts +128 -0
- package/src/core/src/tools/builtin/glob.txt +10 -0
- package/src/core/src/tools/builtin/grep.ts +136 -0
- package/src/core/src/tools/builtin/grep.txt +9 -0
- package/src/core/src/tools/builtin/ignore.ts +45 -0
- package/src/core/src/tools/builtin/patch/apply.ts +546 -0
- package/src/core/src/tools/builtin/patch/constants.ts +5 -0
- package/src/core/src/tools/builtin/patch/normalize.ts +31 -0
- package/src/core/src/tools/builtin/patch/parse-enveloped.ts +209 -0
- package/src/core/src/tools/builtin/patch/parse-unified.ts +231 -0
- package/src/core/src/tools/builtin/patch/parse.ts +28 -0
- package/src/core/src/tools/builtin/patch/text.ts +23 -0
- package/src/core/src/tools/builtin/patch/types.ts +82 -0
- package/src/core/src/tools/builtin/patch.ts +167 -0
- package/src/core/src/tools/builtin/patch.txt +207 -0
- package/src/core/src/tools/builtin/progress.ts +55 -0
- package/src/core/src/tools/builtin/progress.txt +7 -0
- package/src/core/src/tools/builtin/ripgrep.ts +125 -0
- package/src/core/src/tools/builtin/ripgrep.txt +7 -0
- package/src/core/src/tools/builtin/terminal.ts +300 -0
- package/src/core/src/tools/builtin/terminal.txt +93 -0
- package/src/core/src/tools/builtin/todos.ts +66 -0
- package/src/core/src/tools/builtin/todos.txt +7 -0
- package/src/core/src/tools/builtin/websearch.ts +250 -0
- package/src/core/src/tools/builtin/websearch.txt +12 -0
- package/src/core/src/tools/error.ts +67 -0
- package/src/core/src/tools/loader.ts +421 -0
- package/src/core/src/types/index.ts +11 -0
- package/src/core/src/types/types.ts +4 -0
- package/src/core/src/utils/ansi.ts +27 -0
- package/src/core/src/utils/debug.ts +40 -0
- package/src/core/src/utils/logger.ts +150 -0
- package/src/index.ts +313 -0
- package/src/prompts/src/agents/build.txt +89 -0
- package/src/prompts/src/agents/general.txt +15 -0
- package/src/prompts/src/agents/plan.txt +10 -0
- package/src/prompts/src/agents/research.txt +50 -0
- package/src/prompts/src/base.txt +24 -0
- package/src/prompts/src/debug.ts +104 -0
- package/src/prompts/src/index.ts +1 -0
- package/src/prompts/src/modes/oneshot.txt +9 -0
- package/src/prompts/src/providers/anthropic.txt +247 -0
- package/src/prompts/src/providers/anthropicSpoof.txt +1 -0
- package/src/prompts/src/providers/default.txt +466 -0
- package/src/prompts/src/providers/google.txt +230 -0
- package/src/prompts/src/providers/moonshot.txt +24 -0
- package/src/prompts/src/providers/openai.txt +414 -0
- package/src/prompts/src/providers.ts +143 -0
- package/src/providers/src/anthropic-caching.ts +202 -0
- package/src/providers/src/anthropic-oauth-client.ts +157 -0
- package/src/providers/src/authorization.ts +17 -0
- package/src/providers/src/catalog-manual.ts +135 -0
- package/src/providers/src/catalog-merged.ts +9 -0
- package/src/providers/src/catalog.ts +8329 -0
- package/src/providers/src/copilot-client.ts +39 -0
- package/src/providers/src/env.ts +31 -0
- package/src/providers/src/google-client.ts +16 -0
- package/src/providers/src/index.ts +75 -0
- package/src/providers/src/moonshot-client.ts +25 -0
- package/src/providers/src/oauth-models.ts +39 -0
- package/src/providers/src/openai-oauth-client.ts +108 -0
- package/src/providers/src/opencode-client.ts +64 -0
- package/src/providers/src/openrouter-client.ts +31 -0
- package/src/providers/src/pricing.ts +178 -0
- package/src/providers/src/setu-client.ts +643 -0
- package/src/providers/src/utils.ts +210 -0
- package/src/providers/src/validate.ts +39 -0
- package/src/providers/src/zai-client.ts +47 -0
- package/src/skills/index.ts +34 -0
- package/src/skills/loader.ts +152 -0
- package/src/skills/parser.ts +108 -0
- package/src/skills/tool.ts +87 -0
- package/src/skills/types.ts +41 -0
- package/src/skills/validator.ts +110 -0
- package/src/types/src/auth.ts +33 -0
- package/src/types/src/config.ts +36 -0
- package/src/types/src/index.ts +20 -0
- package/src/types/src/provider.ts +71 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
2
|
+
import type { OAuth } from '../../types/src/index.ts';
|
|
3
|
+
|
|
4
|
+
const COPILOT_BASE_URL = 'https://api.githubcopilot.com';
|
|
5
|
+
|
|
6
|
+
export type CopilotOAuthConfig = {
|
|
7
|
+
oauth: OAuth;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function createCopilotFetch(config: CopilotOAuthConfig): typeof fetch {
|
|
11
|
+
return async (
|
|
12
|
+
input: string | URL | Request,
|
|
13
|
+
init?: RequestInit,
|
|
14
|
+
): Promise<Response> => {
|
|
15
|
+
const headers = new Headers(init?.headers);
|
|
16
|
+
headers.delete('Authorization');
|
|
17
|
+
headers.delete('authorization');
|
|
18
|
+
headers.set('Authorization', `Bearer ${config.oauth.refresh}`);
|
|
19
|
+
headers.set('Openai-Intent', 'conversation-edits');
|
|
20
|
+
headers.set('User-Agent', 'ottocode');
|
|
21
|
+
|
|
22
|
+
return fetch(input, {
|
|
23
|
+
...init,
|
|
24
|
+
headers,
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createCopilotModel(model: string, config: CopilotOAuthConfig) {
|
|
30
|
+
const customFetch = createCopilotFetch(config);
|
|
31
|
+
|
|
32
|
+
const provider = createOpenAICompatible({
|
|
33
|
+
name: 'github-copilot',
|
|
34
|
+
baseURL: COPILOT_BASE_URL,
|
|
35
|
+
apiKey: 'copilot-oauth',
|
|
36
|
+
fetch: customFetch,
|
|
37
|
+
});
|
|
38
|
+
return provider.chatModel(model);
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ProviderId } from '../../types/src/index.ts';
|
|
2
|
+
|
|
3
|
+
const ENV_VARS: Record<ProviderId, string> = {
|
|
4
|
+
openai: 'OPENAI_API_KEY',
|
|
5
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
6
|
+
google: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
7
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
8
|
+
opencode: 'OPENCODE_API_KEY',
|
|
9
|
+
copilot: 'GITHUB_TOKEN',
|
|
10
|
+
setu: 'SETU_PRIVATE_KEY',
|
|
11
|
+
zai: 'ZAI_API_KEY',
|
|
12
|
+
'zai-coding': 'ZAI_API_KEY',
|
|
13
|
+
moonshot: 'MOONSHOT_API_KEY',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function providerEnvVar(provider: ProviderId): string {
|
|
17
|
+
return ENV_VARS[provider];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readEnvKey(provider: ProviderId): string | undefined {
|
|
21
|
+
const key = providerEnvVar(provider);
|
|
22
|
+
const value = process.env[key];
|
|
23
|
+
return value?.length ? value : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function setEnvKey(provider: ProviderId, value: string | undefined) {
|
|
27
|
+
const key = providerEnvVar(provider);
|
|
28
|
+
if (value) {
|
|
29
|
+
process.env[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
2
|
+
|
|
3
|
+
export type GoogleProviderConfig = {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function createGoogleModel(
|
|
8
|
+
model: string,
|
|
9
|
+
config?: GoogleProviderConfig,
|
|
10
|
+
) {
|
|
11
|
+
if (config?.apiKey) {
|
|
12
|
+
const instance = createGoogleGenerativeAI({ apiKey: config.apiKey });
|
|
13
|
+
return instance(model);
|
|
14
|
+
}
|
|
15
|
+
return google(model);
|
|
16
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export { isProviderAuthorized, ensureProviderEnv } from './authorization.ts';
|
|
2
|
+
export { catalog } from './catalog-merged.ts';
|
|
3
|
+
export type {
|
|
4
|
+
ProviderId,
|
|
5
|
+
ModelInfo,
|
|
6
|
+
ModelProviderBinding,
|
|
7
|
+
ProviderCatalogEntry,
|
|
8
|
+
} from '../../types/src/index.ts';
|
|
9
|
+
export {
|
|
10
|
+
isProviderId,
|
|
11
|
+
providerIds,
|
|
12
|
+
defaultModelFor,
|
|
13
|
+
hasModel,
|
|
14
|
+
getFastModel,
|
|
15
|
+
getFastModelForAuth,
|
|
16
|
+
getModelNpmBinding,
|
|
17
|
+
isAnthropicBasedModel,
|
|
18
|
+
getUnderlyingProviderKey,
|
|
19
|
+
getModelFamily,
|
|
20
|
+
getModelInfo,
|
|
21
|
+
modelSupportsReasoning,
|
|
22
|
+
} from './utils.ts';
|
|
23
|
+
export type { UnderlyingProviderKey } from './utils.ts';
|
|
24
|
+
export { validateProviderModel } from './validate.ts';
|
|
25
|
+
export { estimateModelCostUsd } from './pricing.ts';
|
|
26
|
+
export { providerEnvVar, readEnvKey, setEnvKey } from './env.ts';
|
|
27
|
+
export {
|
|
28
|
+
createSetuFetch,
|
|
29
|
+
createSetuModel,
|
|
30
|
+
fetchSetuBalance,
|
|
31
|
+
getPublicKeyFromPrivate,
|
|
32
|
+
fetchSolanaUsdcBalance,
|
|
33
|
+
} from './setu-client.ts';
|
|
34
|
+
export type {
|
|
35
|
+
SetuAuth,
|
|
36
|
+
SetuProviderOptions,
|
|
37
|
+
SetuPaymentCallbacks,
|
|
38
|
+
SetuBalanceResponse,
|
|
39
|
+
SolanaUsdcBalanceResponse,
|
|
40
|
+
} from './setu-client.ts';
|
|
41
|
+
export {
|
|
42
|
+
createOpenAIOAuthFetch,
|
|
43
|
+
createOpenAIOAuthModel,
|
|
44
|
+
} from './openai-oauth-client.ts';
|
|
45
|
+
export type { OpenAIOAuthConfig } from './openai-oauth-client.ts';
|
|
46
|
+
export {
|
|
47
|
+
isModelAllowedForOAuth,
|
|
48
|
+
filterModelsForAuthType,
|
|
49
|
+
getOAuthModelPrefixes,
|
|
50
|
+
} from './oauth-models.ts';
|
|
51
|
+
export {
|
|
52
|
+
addAnthropicCacheControl,
|
|
53
|
+
createAnthropicCachingFetch,
|
|
54
|
+
createConditionalCachingFetch,
|
|
55
|
+
} from './anthropic-caching.ts';
|
|
56
|
+
export {
|
|
57
|
+
createAnthropicOAuthFetch,
|
|
58
|
+
createAnthropicOAuthModel,
|
|
59
|
+
} from './anthropic-oauth-client.ts';
|
|
60
|
+
export type { AnthropicOAuthConfig } from './anthropic-oauth-client.ts';
|
|
61
|
+
export { createGoogleModel } from './google-client.ts';
|
|
62
|
+
export type { GoogleProviderConfig } from './google-client.ts';
|
|
63
|
+
export { createZaiModel, createZaiCodingModel } from './zai-client.ts';
|
|
64
|
+
export type { ZaiProviderConfig } from './zai-client.ts';
|
|
65
|
+
export {
|
|
66
|
+
getOpenRouterInstance,
|
|
67
|
+
createOpenRouterModel,
|
|
68
|
+
} from './openrouter-client.ts';
|
|
69
|
+
export type { OpenRouterProviderConfig } from './openrouter-client.ts';
|
|
70
|
+
export { createOpencodeModel } from './opencode-client.ts';
|
|
71
|
+
export type { OpencodeProviderConfig } from './opencode-client.ts';
|
|
72
|
+
export { createMoonshotModel } from './moonshot-client.ts';
|
|
73
|
+
export type { MoonshotProviderConfig } from './moonshot-client.ts';
|
|
74
|
+
export { createCopilotFetch, createCopilotModel } from './copilot-client.ts';
|
|
75
|
+
export type { CopilotOAuthConfig } from './copilot-client.ts';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
2
|
+
import { catalog } from './catalog-merged.ts';
|
|
3
|
+
|
|
4
|
+
export type MoonshotProviderConfig = {
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
baseURL?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function createMoonshotModel(
|
|
10
|
+
model: string,
|
|
11
|
+
config?: MoonshotProviderConfig,
|
|
12
|
+
) {
|
|
13
|
+
const entry = catalog.moonshot;
|
|
14
|
+
const baseURL = config?.baseURL || entry?.api || 'https://api.moonshot.ai/v1';
|
|
15
|
+
const apiKey = config?.apiKey || process.env.MOONSHOT_API_KEY || '';
|
|
16
|
+
const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
|
|
17
|
+
|
|
18
|
+
const instance = createOpenAICompatible({
|
|
19
|
+
name: entry?.label ?? 'Moonshot AI',
|
|
20
|
+
baseURL,
|
|
21
|
+
headers,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return instance(model);
|
|
25
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ProviderId, ModelInfo } from '../../types/src/index.ts';
|
|
2
|
+
|
|
3
|
+
const OAUTH_MODEL_PREFIXES: Partial<Record<ProviderId, string[]>> = {
|
|
4
|
+
anthropic: ['claude-haiku-4-5', 'claude-opus-4-5', 'claude-sonnet-4-5'],
|
|
5
|
+
openai: [
|
|
6
|
+
'gpt-5.2-codex',
|
|
7
|
+
'gpt-5.1-codex-max',
|
|
8
|
+
'gpt-5.1-codex-mini',
|
|
9
|
+
'gpt-5.2',
|
|
10
|
+
],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function isModelAllowedForOAuth(
|
|
14
|
+
provider: ProviderId,
|
|
15
|
+
modelId: string,
|
|
16
|
+
): boolean {
|
|
17
|
+
const prefixes = OAUTH_MODEL_PREFIXES[provider];
|
|
18
|
+
if (!prefixes) return true;
|
|
19
|
+
return prefixes.some((prefix) => modelId.startsWith(prefix));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function filterModelsForAuthType(
|
|
23
|
+
provider: ProviderId,
|
|
24
|
+
models: ModelInfo[],
|
|
25
|
+
authType: 'api' | 'oauth' | 'wallet' | undefined,
|
|
26
|
+
): ModelInfo[] {
|
|
27
|
+
if (authType !== 'oauth') return models;
|
|
28
|
+
const prefixes = OAUTH_MODEL_PREFIXES[provider];
|
|
29
|
+
if (!prefixes) return models;
|
|
30
|
+
return models.filter((m) =>
|
|
31
|
+
prefixes.some((prefix) => m.id.startsWith(prefix)),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getOAuthModelPrefixes(
|
|
36
|
+
provider: ProviderId,
|
|
37
|
+
): string[] | undefined {
|
|
38
|
+
return OAUTH_MODEL_PREFIXES[provider];
|
|
39
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
2
|
+
import type { OAuth } from '../../types/src/index.ts';
|
|
3
|
+
import { refreshOpenAIToken } from '../../auth/src/openai-oauth.ts';
|
|
4
|
+
import { setAuth, getAuth } from '../../auth/src/index.ts';
|
|
5
|
+
|
|
6
|
+
const CODEX_API_ENDPOINT = 'https://chatgpt.com/backend-api/codex/responses';
|
|
7
|
+
|
|
8
|
+
export type OpenAIOAuthConfig = {
|
|
9
|
+
oauth: OAuth;
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function ensureValidToken(
|
|
14
|
+
oauth: OAuth,
|
|
15
|
+
projectRoot?: string,
|
|
16
|
+
): Promise<{ access: string; accountId?: string }> {
|
|
17
|
+
if (oauth.access && oauth.expires > Date.now()) {
|
|
18
|
+
return { access: oauth.access, accountId: oauth.accountId };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const newTokens = await refreshOpenAIToken(oauth.refresh);
|
|
23
|
+
const updatedOAuth: OAuth = {
|
|
24
|
+
type: 'oauth',
|
|
25
|
+
access: newTokens.access,
|
|
26
|
+
refresh: newTokens.refresh,
|
|
27
|
+
expires: newTokens.expires,
|
|
28
|
+
accountId: oauth.accountId,
|
|
29
|
+
idToken: newTokens.idToken,
|
|
30
|
+
};
|
|
31
|
+
await setAuth('openai', updatedOAuth, projectRoot, 'global');
|
|
32
|
+
return { access: newTokens.access, accountId: oauth.accountId };
|
|
33
|
+
} catch {
|
|
34
|
+
return { access: oauth.access, accountId: oauth.accountId };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function rewriteUrl(url: string): string {
|
|
39
|
+
const parsed = new URL(url);
|
|
40
|
+
if (
|
|
41
|
+
parsed.pathname.includes('/v1/responses') ||
|
|
42
|
+
parsed.pathname.includes('/chat/completions')
|
|
43
|
+
) {
|
|
44
|
+
return CODEX_API_ENDPOINT;
|
|
45
|
+
}
|
|
46
|
+
return url;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
|
|
50
|
+
let currentOAuth = config.oauth;
|
|
51
|
+
|
|
52
|
+
const customFetch = async (
|
|
53
|
+
input: Parameters<typeof fetch>[0],
|
|
54
|
+
init?: Parameters<typeof fetch>[1],
|
|
55
|
+
): Promise<Response> => {
|
|
56
|
+
const { access: accessToken, accountId } = await ensureValidToken(
|
|
57
|
+
currentOAuth,
|
|
58
|
+
config.projectRoot,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const originalUrl =
|
|
62
|
+
typeof input === 'string'
|
|
63
|
+
? input
|
|
64
|
+
: input instanceof URL
|
|
65
|
+
? input.href
|
|
66
|
+
: input.url;
|
|
67
|
+
const targetUrl = rewriteUrl(originalUrl);
|
|
68
|
+
|
|
69
|
+
const headers = new Headers(init?.headers);
|
|
70
|
+
headers.delete('Authorization');
|
|
71
|
+
headers.delete('authorization');
|
|
72
|
+
headers.set('authorization', `Bearer ${accessToken}`);
|
|
73
|
+
headers.set('originator', 'otto');
|
|
74
|
+
if (accountId) {
|
|
75
|
+
headers.set('ChatGPT-Account-Id', accountId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const response = await fetch(targetUrl, {
|
|
79
|
+
...init,
|
|
80
|
+
headers,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (response.status === 401) {
|
|
84
|
+
const refreshed = await getAuth('openai', config.projectRoot);
|
|
85
|
+
if (refreshed?.type === 'oauth') {
|
|
86
|
+
currentOAuth = refreshed;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return response;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return customFetch as typeof fetch;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createOpenAIOAuthModel(
|
|
97
|
+
model: string,
|
|
98
|
+
config: OpenAIOAuthConfig,
|
|
99
|
+
) {
|
|
100
|
+
const customFetch = createOpenAIOAuthFetch(config);
|
|
101
|
+
|
|
102
|
+
const provider = createOpenAI({
|
|
103
|
+
apiKey: 'chatgpt-oauth',
|
|
104
|
+
fetch: customFetch,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return provider.responses(model);
|
|
108
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
2
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
4
|
+
import { catalog } from './catalog-merged.ts';
|
|
5
|
+
import { createAnthropicCachingFetch } from './anthropic-caching.ts';
|
|
6
|
+
import type { ProviderId } from '../../types/src/index.ts';
|
|
7
|
+
|
|
8
|
+
export type OpencodeProviderConfig = {
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function normalizeModelIdentifier(provider: ProviderId, model: string): string {
|
|
13
|
+
const prefix = `${provider}/`;
|
|
14
|
+
return model.startsWith(prefix) ? model.slice(prefix.length) : model;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createOpencodeModel(
|
|
18
|
+
model: string,
|
|
19
|
+
config?: OpencodeProviderConfig,
|
|
20
|
+
) {
|
|
21
|
+
const entry = catalog.opencode;
|
|
22
|
+
const normalizedModel = normalizeModelIdentifier('opencode', model);
|
|
23
|
+
const modelInfo =
|
|
24
|
+
entry?.models.find((m) => m.id === normalizedModel) ??
|
|
25
|
+
entry?.models.find((m) => m.id === model);
|
|
26
|
+
const resolvedModelId = modelInfo?.id ?? normalizedModel ?? model;
|
|
27
|
+
const binding = modelInfo?.provider?.npm ?? entry?.npm;
|
|
28
|
+
const apiKey = config?.apiKey ?? process.env.OPENCODE_API_KEY ?? '';
|
|
29
|
+
const baseURL =
|
|
30
|
+
modelInfo?.provider?.baseURL ||
|
|
31
|
+
modelInfo?.provider?.api ||
|
|
32
|
+
entry?.api ||
|
|
33
|
+
'https://opencode.ai/zen/v1';
|
|
34
|
+
const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
|
|
35
|
+
|
|
36
|
+
if (binding === '@ai-sdk/openai') {
|
|
37
|
+
const instance = createOpenAI({ apiKey, baseURL });
|
|
38
|
+
return instance(resolvedModelId);
|
|
39
|
+
}
|
|
40
|
+
if (binding === '@ai-sdk/anthropic') {
|
|
41
|
+
const cachingFetch = createAnthropicCachingFetch();
|
|
42
|
+
const instance = createAnthropic({
|
|
43
|
+
apiKey,
|
|
44
|
+
baseURL,
|
|
45
|
+
fetch: cachingFetch as typeof fetch,
|
|
46
|
+
});
|
|
47
|
+
return instance(resolvedModelId);
|
|
48
|
+
}
|
|
49
|
+
if (binding === '@ai-sdk/openai-compatible') {
|
|
50
|
+
const instance = createOpenAICompatible({
|
|
51
|
+
name: entry?.label ?? 'opencode',
|
|
52
|
+
baseURL,
|
|
53
|
+
headers,
|
|
54
|
+
});
|
|
55
|
+
return instance(resolvedModelId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const defaultInstance = createOpenAICompatible({
|
|
59
|
+
name: entry?.label ?? 'opencode',
|
|
60
|
+
baseURL,
|
|
61
|
+
headers,
|
|
62
|
+
});
|
|
63
|
+
return defaultInstance(resolvedModelId);
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
2
|
+
import { createConditionalCachingFetch } from './anthropic-caching.ts';
|
|
3
|
+
import { getModelNpmBinding } from './utils.ts';
|
|
4
|
+
|
|
5
|
+
export type OpenRouterProviderConfig = {
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function isAnthropicModel(model: string): boolean {
|
|
10
|
+
const npm = getModelNpmBinding('openrouter', model);
|
|
11
|
+
return npm === '@ai-sdk/anthropic';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getOpenRouterInstance(
|
|
15
|
+
model?: string,
|
|
16
|
+
config?: OpenRouterProviderConfig,
|
|
17
|
+
) {
|
|
18
|
+
const apiKey = config?.apiKey ?? process.env.OPENROUTER_API_KEY ?? '';
|
|
19
|
+
const customFetch = model
|
|
20
|
+
? createConditionalCachingFetch(isAnthropicModel, model)
|
|
21
|
+
: undefined;
|
|
22
|
+
return createOpenRouter({ apiKey, fetch: customFetch });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createOpenRouterModel(
|
|
26
|
+
model: string,
|
|
27
|
+
config?: OpenRouterProviderConfig,
|
|
28
|
+
) {
|
|
29
|
+
const openrouter = getOpenRouterInstance(model, config);
|
|
30
|
+
return openrouter.chat(model);
|
|
31
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { catalog } from './catalog-merged.ts';
|
|
2
|
+
import type { ModelInfo, ProviderId } from '../../types/src/index.ts';
|
|
3
|
+
|
|
4
|
+
type ProviderName = ProviderId;
|
|
5
|
+
|
|
6
|
+
type UsageLike = {
|
|
7
|
+
inputTokens?: number | null;
|
|
8
|
+
outputTokens?: number | null;
|
|
9
|
+
cachedInputTokens?: number | null;
|
|
10
|
+
cacheCreationInputTokens?: number | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type PricingEntry = {
|
|
14
|
+
/** Cost in USD per 1 million input tokens */
|
|
15
|
+
inputPerMillion: number;
|
|
16
|
+
/** Cost in USD per 1 million output tokens */
|
|
17
|
+
outputPerMillion: number;
|
|
18
|
+
match: (model: string) => boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const pricingTable: Record<ProviderName, PricingEntry[]> = {
|
|
22
|
+
openai: [
|
|
23
|
+
{
|
|
24
|
+
match: (model) => model.includes('gpt-4o-mini'),
|
|
25
|
+
inputPerMillion: 0.15,
|
|
26
|
+
outputPerMillion: 0.6,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
match: (model) => model.includes('gpt-4o'),
|
|
30
|
+
inputPerMillion: 5,
|
|
31
|
+
outputPerMillion: 15,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
match: (model) => model.includes('gpt-4.1-mini'),
|
|
35
|
+
inputPerMillion: 1,
|
|
36
|
+
outputPerMillion: 4,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
match: (model) => model.includes('gpt-4.1'),
|
|
40
|
+
inputPerMillion: 5,
|
|
41
|
+
outputPerMillion: 15,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
anthropic: [
|
|
45
|
+
{
|
|
46
|
+
match: (model) => model.includes('claude-3-haiku'),
|
|
47
|
+
inputPerMillion: 0.25,
|
|
48
|
+
outputPerMillion: 1.25,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
match: (model) => model.includes('claude-3-sonnet'),
|
|
52
|
+
inputPerMillion: 3,
|
|
53
|
+
outputPerMillion: 15,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
match: (model) => model.includes('claude-3-opus'),
|
|
57
|
+
inputPerMillion: 15,
|
|
58
|
+
outputPerMillion: 75,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
google: [
|
|
62
|
+
{
|
|
63
|
+
match: (model) => model.includes('gemini-1.5-flash'),
|
|
64
|
+
inputPerMillion: 0.35,
|
|
65
|
+
outputPerMillion: 1.05,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
match: (model) => model.includes('gemini-1.5-pro'),
|
|
69
|
+
inputPerMillion: 3.5,
|
|
70
|
+
outputPerMillion: 10.5,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
openrouter: [
|
|
74
|
+
// Prefer catalog pricing; keep empty to defer to catalog or undefined
|
|
75
|
+
],
|
|
76
|
+
opencode: [
|
|
77
|
+
// Pricing from catalog entries; leave empty here
|
|
78
|
+
],
|
|
79
|
+
setu: [
|
|
80
|
+
// Pricing from catalog entries; leave empty here
|
|
81
|
+
],
|
|
82
|
+
zai: [
|
|
83
|
+
// Pricing from catalog entries; leave empty here
|
|
84
|
+
],
|
|
85
|
+
'zai-coding': [
|
|
86
|
+
// Pricing from catalog entries; leave empty here
|
|
87
|
+
],
|
|
88
|
+
moonshot: [
|
|
89
|
+
// Pricing from catalog entries; leave empty here
|
|
90
|
+
],
|
|
91
|
+
copilot: [],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function findPricing(
|
|
95
|
+
provider: ProviderName,
|
|
96
|
+
model: string,
|
|
97
|
+
): PricingEntry | undefined {
|
|
98
|
+
const entries = pricingTable[provider];
|
|
99
|
+
if (!entries) return undefined;
|
|
100
|
+
return entries.find((entry) => {
|
|
101
|
+
try {
|
|
102
|
+
return entry.match(model);
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function findCatalogModel(
|
|
110
|
+
provider: ProviderName,
|
|
111
|
+
model: string,
|
|
112
|
+
): ModelInfo | undefined {
|
|
113
|
+
const entry = catalog[provider as keyof typeof catalog];
|
|
114
|
+
if (!entry) return undefined;
|
|
115
|
+
const idLower = model.toLowerCase();
|
|
116
|
+
return entry.models.find((m) => m.id?.toLowerCase() === idLower);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function estimateModelCostUsd(
|
|
120
|
+
provider: ProviderName,
|
|
121
|
+
model: string,
|
|
122
|
+
usage: UsageLike,
|
|
123
|
+
): number | undefined {
|
|
124
|
+
const inputTokens =
|
|
125
|
+
typeof usage.inputTokens === 'number' ? usage.inputTokens : 0;
|
|
126
|
+
const outputTokens =
|
|
127
|
+
typeof usage.outputTokens === 'number' ? usage.outputTokens : 0;
|
|
128
|
+
const cachedInputTokens =
|
|
129
|
+
typeof usage.cachedInputTokens === 'number' ? usage.cachedInputTokens : 0;
|
|
130
|
+
const cacheCreationInputTokens =
|
|
131
|
+
typeof usage.cacheCreationInputTokens === 'number'
|
|
132
|
+
? usage.cacheCreationInputTokens
|
|
133
|
+
: 0;
|
|
134
|
+
if (
|
|
135
|
+
!inputTokens &&
|
|
136
|
+
!outputTokens &&
|
|
137
|
+
!cachedInputTokens &&
|
|
138
|
+
!cacheCreationInputTokens
|
|
139
|
+
)
|
|
140
|
+
return undefined;
|
|
141
|
+
|
|
142
|
+
// Prefer centralized catalog costs when available
|
|
143
|
+
const m = findCatalogModel(provider, model);
|
|
144
|
+
if (m?.cost?.input != null || m?.cost?.output != null) {
|
|
145
|
+
const inputPerMillion =
|
|
146
|
+
typeof m.cost?.input === 'number' ? m.cost.input : 0;
|
|
147
|
+
const outputPerMillion =
|
|
148
|
+
typeof m.cost?.output === 'number' ? m.cost.output : 0;
|
|
149
|
+
const cacheReadPerMillion =
|
|
150
|
+
typeof m.cost?.cacheRead === 'number' ? m.cost.cacheRead : 0;
|
|
151
|
+
const cacheWritePerMillion =
|
|
152
|
+
typeof m.cost?.cacheWrite === 'number' ? m.cost.cacheWrite : 0;
|
|
153
|
+
const nonCachedInput = Math.max(
|
|
154
|
+
0,
|
|
155
|
+
inputTokens - cachedInputTokens - cacheCreationInputTokens,
|
|
156
|
+
);
|
|
157
|
+
const inputCost = (nonCachedInput * inputPerMillion) / 1_000_000;
|
|
158
|
+
const outputCost = (outputTokens * outputPerMillion) / 1_000_000;
|
|
159
|
+
const cacheReadCost = (cachedInputTokens * cacheReadPerMillion) / 1_000_000;
|
|
160
|
+
const cacheWriteCost =
|
|
161
|
+
(cacheCreationInputTokens * cacheWritePerMillion) / 1_000_000;
|
|
162
|
+
const total = inputCost + outputCost + cacheReadCost + cacheWriteCost;
|
|
163
|
+
return Number.isFinite(total) ? Number(total.toFixed(6)) : undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Fallback to legacy table if catalog lacks pricing
|
|
167
|
+
const entry = findPricing(provider, model.toLowerCase());
|
|
168
|
+
if (!entry) return undefined;
|
|
169
|
+
const nonCachedInputFallback = Math.max(
|
|
170
|
+
0,
|
|
171
|
+
inputTokens - cachedInputTokens - cacheCreationInputTokens,
|
|
172
|
+
);
|
|
173
|
+
const inputCost =
|
|
174
|
+
(nonCachedInputFallback * entry.inputPerMillion) / 1_000_000;
|
|
175
|
+
const outputCost = (outputTokens * entry.outputPerMillion) / 1_000_000;
|
|
176
|
+
const total = inputCost + outputCost;
|
|
177
|
+
return Number.isFinite(total) ? Number(total.toFixed(6)) : undefined;
|
|
178
|
+
}
|