tarsk 0.2.5 → 0.3.0
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 +1 -7
- package/dist/index.d.ts +3 -0
- package/dist/index.js +92 -32
- package/dist/lib/response-builder.d.ts +50 -0
- package/dist/lib/response-builder.js +56 -0
- package/dist/lib/stream-helper.d.ts +39 -0
- package/dist/lib/stream-helper.js +43 -0
- package/dist/managers/ConversationManager.d.ts +83 -0
- package/dist/managers/ConversationManager.js +129 -0
- package/dist/managers/GitManager.d.ts +133 -0
- package/dist/managers/GitManager.js +330 -0
- package/dist/managers/MetadataManager.d.ts +139 -0
- package/dist/managers/MetadataManager.js +309 -0
- package/dist/managers/ModelManager.d.ts +57 -0
- package/dist/managers/ModelManager.js +129 -0
- package/dist/managers/NeovateExecutor.d.ts +40 -0
- package/dist/managers/NeovateExecutor.js +138 -0
- package/dist/managers/ProjectManager.d.ts +162 -0
- package/dist/managers/ProjectManager.js +353 -0
- package/dist/managers/ThreadManager.d.ts +181 -0
- package/dist/managers/ThreadManager.js +325 -0
- package/dist/managers/conversation-manager.d.ts +83 -0
- package/dist/managers/conversation-manager.js +129 -0
- package/dist/managers/git-manager.d.ts +133 -0
- package/dist/managers/git-manager.js +330 -0
- package/dist/managers/metadata-manager.d.ts +139 -0
- package/dist/managers/metadata-manager.js +305 -0
- package/dist/managers/model-manager.d.ts +59 -0
- package/dist/managers/model-manager.js +144 -0
- package/dist/managers/neovate-executor.d.ts +43 -0
- package/dist/managers/neovate-executor.js +205 -0
- package/dist/managers/processing-state-manager.d.ts +40 -0
- package/dist/managers/processing-state-manager.js +27 -0
- package/dist/managers/project-manager.d.ts +199 -0
- package/dist/managers/project-manager.js +465 -0
- package/dist/managers/thread-manager.d.ts +193 -0
- package/dist/managers/thread-manager.js +368 -0
- package/dist/model-info-aihubmix.d.ts +25 -0
- package/dist/model-info-aihubmix.js +117 -0
- package/dist/model-info-openai.d.ts +17 -0
- package/dist/model-info-openai.js +59 -0
- package/dist/model-info-openrouter.d.ts +25 -0
- package/dist/model-info-openrouter.js +101 -0
- package/dist/model-info.d.ts +37 -0
- package/dist/model-info.js +39 -0
- package/dist/provider-data.d.ts +101 -0
- package/dist/provider-data.js +471 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.js +192 -0
- package/dist/public/android-chrome-192x192.png +0 -0
- package/dist/public/android-chrome-512x512.png +0 -0
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/assets/index-B443aj9k.js +8506 -0
- package/dist/public/assets/index-CjXGVbI7.css +1 -0
- package/dist/public/assets/index-DJC-p914.js +8506 -0
- package/dist/public/favicon-16x16.png +0 -0
- package/dist/public/favicon-32x32.png +0 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/index.html +28 -0
- package/dist/public/manifest.json +82 -0
- package/dist/public/placeholder-logo.svg +1 -0
- package/dist/public/placeholder.svg +1 -0
- package/dist/public/snpro.woff2 +0 -0
- package/dist/public/tarsk-color.svg +12 -0
- package/dist/public/tarsk.png +0 -0
- package/dist/public/tarsk.svg +12 -0
- package/dist/public/zalando-sans.woff2 +0 -0
- package/dist/routes/chat-old.d.ts +21 -0
- package/dist/routes/chat-old.js +251 -0
- package/dist/routes/chat.d.ts +21 -0
- package/dist/routes/chat.js +217 -0
- package/dist/routes/git.d.ts +4 -0
- package/dist/routes/git.js +668 -0
- package/dist/routes/models.d.ts +18 -0
- package/dist/routes/models.js +128 -0
- package/dist/routes/projects-old.d.ts +20 -0
- package/dist/routes/projects-old.js +297 -0
- package/dist/routes/projects.d.ts +20 -0
- package/dist/routes/projects.js +365 -0
- package/dist/routes/providers.d.ts +15 -0
- package/dist/routes/providers.js +130 -0
- package/dist/routes/threads-old.d.ts +14 -0
- package/dist/routes/threads-old.js +393 -0
- package/dist/routes/threads.d.ts +14 -0
- package/dist/routes/threads.js +352 -0
- package/dist/types/models.d.ts +315 -0
- package/dist/types/models.js +11 -0
- package/dist/utils/env-manager.d.ts +3 -0
- package/dist/utils/env-manager.js +60 -0
- package/dist/utils/open-router-models.d.ts +45 -0
- package/dist/utils/open-router-models.js +103 -0
- package/dist/utils/openai-models.d.ts +63 -0
- package/dist/utils/openai-models.js +152 -0
- package/dist/utils/openai-pricing-scraper.d.ts +17 -0
- package/dist/utils/openai-pricing-scraper.js +185 -0
- package/dist/utils/validation.d.ts +10 -0
- package/dist/utils/validation.js +20 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +12 -0
- package/package.json +36 -22
- package/LICENSE.md +0 -7
- package/dist/agent/agent.js +0 -131
- package/dist/agent/interfaces.js +0 -1
- package/dist/api/encryption.js +0 -41
- package/dist/api/models.js +0 -169
- package/dist/api/prompt.js +0 -12
- package/dist/api/settings.js +0 -43
- package/dist/api/test.js +0 -29
- package/dist/api/tools.js +0 -287
- package/dist/api/utils.js +0 -18
- package/dist/interfaces/meta.js +0 -1
- package/dist/interfaces/model.js +0 -1
- package/dist/interfaces/settings.js +0 -1
- package/dist/log/log.js +0 -33
- package/dist/prompt.js +0 -49
- package/dist/tools.js +0 -84
- package/dist/utils/files.js +0 -14
- package/dist/utils/json-file.js +0 -28
- package/dist/utils/strip-markdown.js +0 -5
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
export async function updateEnvFile(keyNames) {
|
|
4
|
+
const envPath = join(process.cwd(), '.env');
|
|
5
|
+
let content = '';
|
|
6
|
+
try {
|
|
7
|
+
content = await fs.readFile(envPath, 'utf-8');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// If file doesn't exist, start with empty content
|
|
11
|
+
}
|
|
12
|
+
const lines = content.split('\n');
|
|
13
|
+
const envMap = {};
|
|
14
|
+
// Parse existing content
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
|
|
18
|
+
const index = trimmed.indexOf('=');
|
|
19
|
+
const key = trimmed.substring(0, index).trim();
|
|
20
|
+
const value = trimmed.substring(index + 1).trim();
|
|
21
|
+
envMap[key] = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Update with new values
|
|
25
|
+
for (const [key, value] of Object.entries(keyNames)) {
|
|
26
|
+
if (key) {
|
|
27
|
+
envMap[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Serialize back to file
|
|
31
|
+
const newLines = [];
|
|
32
|
+
// Try to preserve some order if keys already existed, or just rebuild
|
|
33
|
+
// For simplicity, we'll just rebuild based on current keys
|
|
34
|
+
for (const [key, value] of Object.entries(envMap)) {
|
|
35
|
+
newLines.push(`${key}=${value}`);
|
|
36
|
+
}
|
|
37
|
+
await fs.writeFile(envPath, newLines.join('\n') + '\n', 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
export async function readEnvFile() {
|
|
40
|
+
const envPath = join(process.cwd(), '.env');
|
|
41
|
+
const envMap = {};
|
|
42
|
+
try {
|
|
43
|
+
const content = await fs.readFile(envPath, 'utf-8');
|
|
44
|
+
const lines = content.split('\n');
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
|
|
48
|
+
const index = trimmed.indexOf('=');
|
|
49
|
+
const key = trimmed.substring(0, index).trim();
|
|
50
|
+
const value = trimmed.substring(index + 1).trim();
|
|
51
|
+
envMap[key] = value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// If file doesn't exist, return empty map
|
|
57
|
+
}
|
|
58
|
+
return envMap;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=env-manager.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter Models Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages available models from @neovate/code's OpenRouter provider
|
|
5
|
+
* by fetching from the GitHub repository.
|
|
6
|
+
*/
|
|
7
|
+
import { Model } from '../types/models.js';
|
|
8
|
+
/**
|
|
9
|
+
* OpenRouterModels manages access to OpenRouter models from @neovate/code
|
|
10
|
+
*/
|
|
11
|
+
export declare class OpenRouterModels {
|
|
12
|
+
private cache;
|
|
13
|
+
/**
|
|
14
|
+
* Create a new OpenRouterModels instance
|
|
15
|
+
*/
|
|
16
|
+
constructor();
|
|
17
|
+
/**
|
|
18
|
+
* Get available models from @neovate/code
|
|
19
|
+
* Fetches the model list from the GitHub repository and parses the TypeScript source
|
|
20
|
+
*
|
|
21
|
+
* @returns Array of Model objects
|
|
22
|
+
* @throws Error if models cannot be fetched
|
|
23
|
+
*/
|
|
24
|
+
getModels(): Promise<Model[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Get a specific model by ID
|
|
27
|
+
* @param modelId - The model ID to fetch
|
|
28
|
+
* @returns Model object or null if not found
|
|
29
|
+
*/
|
|
30
|
+
getModelById(modelId: string): Promise<Model | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Clear the cache
|
|
33
|
+
*/
|
|
34
|
+
clearCache(): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get or create the singleton instance
|
|
38
|
+
* @returns OpenRouterModels instance
|
|
39
|
+
*/
|
|
40
|
+
export declare function getOpenRouterModels(): OpenRouterModels;
|
|
41
|
+
/**
|
|
42
|
+
* Reset the singleton instance (useful for testing)
|
|
43
|
+
*/
|
|
44
|
+
export declare function resetOpenRouterModels(): void;
|
|
45
|
+
//# sourceMappingURL=open-router-models.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter Models Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages available models from @neovate/code's OpenRouter provider
|
|
5
|
+
* by fetching from the GitHub repository.
|
|
6
|
+
*/
|
|
7
|
+
/** URL to fetch OpenRouter models from @neovate/code repository */
|
|
8
|
+
const OPENROUTER_MODELS_URL = 'https://raw.githubusercontent.com/neovateai/neovate-code/main/src/provider/providers/openrouter.ts';
|
|
9
|
+
/**
|
|
10
|
+
* OpenRouterModels manages access to OpenRouter models from @neovate/code
|
|
11
|
+
*/
|
|
12
|
+
export class OpenRouterModels {
|
|
13
|
+
cache = null;
|
|
14
|
+
/**
|
|
15
|
+
* Create a new OpenRouterModels instance
|
|
16
|
+
*/
|
|
17
|
+
constructor() {
|
|
18
|
+
// No API key needed - models loaded from @neovate/code repository
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get available models from @neovate/code
|
|
22
|
+
* Fetches the model list from the GitHub repository and parses the TypeScript source
|
|
23
|
+
*
|
|
24
|
+
* @returns Array of Model objects
|
|
25
|
+
* @throws Error if models cannot be fetched
|
|
26
|
+
*/
|
|
27
|
+
async getModels() {
|
|
28
|
+
// Return cached data if available
|
|
29
|
+
if (this.cache) {
|
|
30
|
+
return this.cache;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(OPENROUTER_MODELS_URL);
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
const source = await response.text();
|
|
38
|
+
// Extract model IDs from the TypeScript source
|
|
39
|
+
// Pattern: 'model-id': { ... },
|
|
40
|
+
const modelPattern = /'([a-z-]+\/[a-z0-9.]+)':/gi;
|
|
41
|
+
const modelIds = new Set();
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = modelPattern.exec(source)) !== null) {
|
|
44
|
+
modelIds.add(match[1]);
|
|
45
|
+
}
|
|
46
|
+
// Transform to Model interface
|
|
47
|
+
const models = Array.from(modelIds)
|
|
48
|
+
.sort()
|
|
49
|
+
.map((id) => ({
|
|
50
|
+
id,
|
|
51
|
+
name: id,
|
|
52
|
+
description: `OpenRouter model: ${id}`,
|
|
53
|
+
provider: 'openrouter',
|
|
54
|
+
pricing: {
|
|
55
|
+
prompt: 0,
|
|
56
|
+
completion: 0,
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
// Store in cache
|
|
60
|
+
this.cache = models;
|
|
61
|
+
return models;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw new Error(`Failed to load OpenRouter models: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get a specific model by ID
|
|
69
|
+
* @param modelId - The model ID to fetch
|
|
70
|
+
* @returns Model object or null if not found
|
|
71
|
+
*/
|
|
72
|
+
async getModelById(modelId) {
|
|
73
|
+
const models = await this.getModels();
|
|
74
|
+
return models.find((m) => m.id === modelId) || null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Clear the cache
|
|
78
|
+
*/
|
|
79
|
+
clearCache() {
|
|
80
|
+
this.cache = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Singleton instance for global access
|
|
85
|
+
*/
|
|
86
|
+
let instance = null;
|
|
87
|
+
/**
|
|
88
|
+
* Get or create the singleton instance
|
|
89
|
+
* @returns OpenRouterModels instance
|
|
90
|
+
*/
|
|
91
|
+
export function getOpenRouterModels() {
|
|
92
|
+
if (!instance) {
|
|
93
|
+
instance = new OpenRouterModels();
|
|
94
|
+
}
|
|
95
|
+
return instance;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reset the singleton instance (useful for testing)
|
|
99
|
+
*/
|
|
100
|
+
export function resetOpenRouterModels() {
|
|
101
|
+
instance = null;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=open-router-models.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Models Fetcher
|
|
3
|
+
*
|
|
4
|
+
* Handles fetching available models from OpenAI API
|
|
5
|
+
* with local caching and TTL management.
|
|
6
|
+
*/
|
|
7
|
+
import { Model } from '../types/models.js';
|
|
8
|
+
/**
|
|
9
|
+
* OpenAIModels manages fetching and caching of OpenAI models
|
|
10
|
+
*/
|
|
11
|
+
export declare class OpenAIModels {
|
|
12
|
+
private cache;
|
|
13
|
+
private cacheTTL;
|
|
14
|
+
private apiKey;
|
|
15
|
+
/**
|
|
16
|
+
* Create a new OpenAIModels instance
|
|
17
|
+
* @param apiKey - OpenAI API key (optional, will be fetched from OPENAI_API_KEY env var)
|
|
18
|
+
*/
|
|
19
|
+
constructor(apiKey?: string);
|
|
20
|
+
/**
|
|
21
|
+
* Set the API key
|
|
22
|
+
* @param apiKey - OpenAI API key
|
|
23
|
+
*/
|
|
24
|
+
setApiKey(apiKey: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Check if cache is valid
|
|
27
|
+
* @returns true if cache exists and is not expired
|
|
28
|
+
*/
|
|
29
|
+
private isCacheValid;
|
|
30
|
+
/**
|
|
31
|
+
* Set cache TTL in milliseconds
|
|
32
|
+
* @param ttl - Time to live in milliseconds
|
|
33
|
+
*/
|
|
34
|
+
setCacheTTL(ttl: number): void;
|
|
35
|
+
/**
|
|
36
|
+
* Clear the cache
|
|
37
|
+
*/
|
|
38
|
+
clearCache(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Fetch available models from OpenAI API
|
|
41
|
+
* Uses cache if valid, otherwise fetches fresh data
|
|
42
|
+
*
|
|
43
|
+
* API Reference: https://platform.openai.com/docs/api-reference/models/list
|
|
44
|
+
*
|
|
45
|
+
* @returns Array of Model objects
|
|
46
|
+
* @throws Error if API key is not set or API call fails
|
|
47
|
+
*/
|
|
48
|
+
getModels(): Promise<Model[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Format model ID to a readable name
|
|
51
|
+
* @param modelId - The model ID
|
|
52
|
+
* @returns Formatted model name
|
|
53
|
+
*/
|
|
54
|
+
private formatModelName;
|
|
55
|
+
/**
|
|
56
|
+
* Get a specific model by ID
|
|
57
|
+
* @param modelId - The model ID to fetch
|
|
58
|
+
* @returns Model object or null if not found
|
|
59
|
+
*/
|
|
60
|
+
getModelById(modelId: string): Promise<Model | null>;
|
|
61
|
+
}
|
|
62
|
+
export declare function getOpenAIModels(): OpenAIModels;
|
|
63
|
+
//# sourceMappingURL=openai-models.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Models Fetcher
|
|
3
|
+
*
|
|
4
|
+
* Handles fetching available models from OpenAI API
|
|
5
|
+
* with local caching and TTL management.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* OpenAIModels manages fetching and caching of OpenAI models
|
|
9
|
+
*/
|
|
10
|
+
export class OpenAIModels {
|
|
11
|
+
cache = null;
|
|
12
|
+
cacheTTL = 1000 * 60 * 60; // 1 hour in milliseconds
|
|
13
|
+
apiKey = null;
|
|
14
|
+
/**
|
|
15
|
+
* Create a new OpenAIModels instance
|
|
16
|
+
* @param apiKey - OpenAI API key (optional, will be fetched from OPENAI_API_KEY env var)
|
|
17
|
+
*/
|
|
18
|
+
constructor(apiKey) {
|
|
19
|
+
this.apiKey = apiKey || process.env.OPENAI_API_KEY || null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set the API key
|
|
23
|
+
* @param apiKey - OpenAI API key
|
|
24
|
+
*/
|
|
25
|
+
setApiKey(apiKey) {
|
|
26
|
+
this.apiKey = apiKey;
|
|
27
|
+
// Invalidate cache when API key changes
|
|
28
|
+
this.cache = null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if cache is valid
|
|
32
|
+
* @returns true if cache exists and is not expired
|
|
33
|
+
*/
|
|
34
|
+
isCacheValid() {
|
|
35
|
+
if (!this.cache) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
return now - this.cache.timestamp < this.cacheTTL;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Set cache TTL in milliseconds
|
|
43
|
+
* @param ttl - Time to live in milliseconds
|
|
44
|
+
*/
|
|
45
|
+
setCacheTTL(ttl) {
|
|
46
|
+
this.cacheTTL = ttl;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Clear the cache
|
|
50
|
+
*/
|
|
51
|
+
clearCache() {
|
|
52
|
+
this.cache = null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fetch available models from OpenAI API
|
|
56
|
+
* Uses cache if valid, otherwise fetches fresh data
|
|
57
|
+
*
|
|
58
|
+
* API Reference: https://platform.openai.com/docs/api-reference/models/list
|
|
59
|
+
*
|
|
60
|
+
* @returns Array of Model objects
|
|
61
|
+
* @throws Error if API key is not set or API call fails
|
|
62
|
+
*/
|
|
63
|
+
async getModels() {
|
|
64
|
+
// Return cached data if valid
|
|
65
|
+
if (this.isCacheValid() && this.cache) {
|
|
66
|
+
return this.cache.models;
|
|
67
|
+
}
|
|
68
|
+
if (!this.apiKey) {
|
|
69
|
+
throw new Error('OpenAI API key is not set. Please set OPENAI_API_KEY environment variable or provide it to the constructor.');
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch('https://api.openai.com/v1/models', {
|
|
73
|
+
method: 'GET',
|
|
74
|
+
headers: {
|
|
75
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
|
|
80
|
+
}
|
|
81
|
+
const data = (await response.json());
|
|
82
|
+
// Transform OpenAI response to our Model interface
|
|
83
|
+
// Only includes models that are likely suitable for chat/completion
|
|
84
|
+
const models = data.data
|
|
85
|
+
.filter((item) => {
|
|
86
|
+
// Filter to only include gpt models and models suitable for API use
|
|
87
|
+
const id = item.id.toLowerCase();
|
|
88
|
+
return id.includes('gpt') && !id.includes('vision-request') && !id.includes('dall-e');
|
|
89
|
+
})
|
|
90
|
+
.map((item) => ({
|
|
91
|
+
id: item.id,
|
|
92
|
+
name: this.formatModelName(item.id),
|
|
93
|
+
description: `OpenAI model: ${item.id}`,
|
|
94
|
+
provider: 'openai',
|
|
95
|
+
pricing: {
|
|
96
|
+
prompt: 0, // OpenAI API doesn't provide pricing in the models list
|
|
97
|
+
completion: 0, // Users should check pricing on OpenAI's website
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
100
|
+
// Store in cache
|
|
101
|
+
this.cache = {
|
|
102
|
+
models,
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
};
|
|
105
|
+
return models;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
throw new Error(`Failed to fetch OpenAI models: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Format model ID to a readable name
|
|
113
|
+
* @param modelId - The model ID
|
|
114
|
+
* @returns Formatted model name
|
|
115
|
+
*/
|
|
116
|
+
formatModelName(modelId) {
|
|
117
|
+
// Convert "gpt-4-turbo" to "GPT-4 Turbo"
|
|
118
|
+
return modelId
|
|
119
|
+
.split('-')
|
|
120
|
+
.map((part) => {
|
|
121
|
+
if (part === 'gpt')
|
|
122
|
+
return 'GPT';
|
|
123
|
+
// Handle version numbers like "4turbo"
|
|
124
|
+
if (part === part.toLowerCase() && /^\d+/.test(part)) {
|
|
125
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
126
|
+
}
|
|
127
|
+
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
128
|
+
})
|
|
129
|
+
.join(' ')
|
|
130
|
+
.replace(/\s+/g, ' ');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get a specific model by ID
|
|
134
|
+
* @param modelId - The model ID to fetch
|
|
135
|
+
* @returns Model object or null if not found
|
|
136
|
+
*/
|
|
137
|
+
async getModelById(modelId) {
|
|
138
|
+
const models = await this.getModels();
|
|
139
|
+
return models.find((m) => m.id === modelId) || null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get or create a singleton OpenAIModels instance
|
|
144
|
+
*/
|
|
145
|
+
let instance = null;
|
|
146
|
+
export function getOpenAIModels() {
|
|
147
|
+
if (!instance) {
|
|
148
|
+
instance = new OpenAIModels();
|
|
149
|
+
}
|
|
150
|
+
return instance;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=openai-models.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Pricing Scraper
|
|
3
|
+
*
|
|
4
|
+
* Scrapes pricing information from OpenAI's pricing page and caches it locally for 24 hours.
|
|
5
|
+
* This is necessary because the OpenAI API doesn't return pricing in the models list.
|
|
6
|
+
*/
|
|
7
|
+
import type { ModelInfoPricing } from '../model-info.js';
|
|
8
|
+
/**
|
|
9
|
+
* Get OpenAI pricing with caching.
|
|
10
|
+
* Returns cached pricing if available and fresh, otherwise attempts to scrape and cache new data.
|
|
11
|
+
* Falls back to hardcoded pricing if scraping fails (due to Cloudflare or other blocks).
|
|
12
|
+
*
|
|
13
|
+
* @param cacheDir - Directory to store cache file (typically .metadata)
|
|
14
|
+
* @returns Map of model name -> ModelInfoPricing
|
|
15
|
+
*/
|
|
16
|
+
export declare function getOpenAIPricing(cacheDir: string): Promise<Map<string, ModelInfoPricing>>;
|
|
17
|
+
//# sourceMappingURL=openai-pricing-scraper.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Pricing Scraper
|
|
3
|
+
*
|
|
4
|
+
* Scrapes pricing information from OpenAI's pricing page and caches it locally for 24 hours.
|
|
5
|
+
* This is necessary because the OpenAI API doesn't return pricing in the models list.
|
|
6
|
+
*/
|
|
7
|
+
import { promises as fs } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
const PRICING_URL = 'https://openai.com/api/pricing';
|
|
10
|
+
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
11
|
+
// Fallback pricing data in case scraping fails or Cloudflare blocks access
|
|
12
|
+
// Source: OpenAI's public pricing page (manually curated)
|
|
13
|
+
// Updated: February 2026
|
|
14
|
+
const FALLBACK_PRICING = {
|
|
15
|
+
'gpt-4-turbo': { input: 0.000010, output: 0.000030 },
|
|
16
|
+
'gpt-4': { input: 0.000030, output: 0.000060 },
|
|
17
|
+
'gpt-4.1': { input: 0.000003, output: 0.000012 },
|
|
18
|
+
'gpt-4o': { input: 0.000005, output: 0.000015 },
|
|
19
|
+
'gpt-4o-mini': { input: 0.00000015, output: 0.0000006 },
|
|
20
|
+
'gpt-3.5-turbo': { input: 0.0000005, output: 0.0000015 },
|
|
21
|
+
'o1': { input: 0.000015, output: 0.000060 },
|
|
22
|
+
'o1-mini': { input: 0.000003, output: 0.000012 },
|
|
23
|
+
};
|
|
24
|
+
/** Strip HTML tags and normalize whitespace */
|
|
25
|
+
function stripTags(html) {
|
|
26
|
+
return html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
|
|
27
|
+
}
|
|
28
|
+
/** Parse price from text (e.g., "$0.005 / 1M tokens" -> 0.000005) */
|
|
29
|
+
function parsePrice(text) {
|
|
30
|
+
if (!text)
|
|
31
|
+
return undefined;
|
|
32
|
+
const match = text.match(/[\d.]+/);
|
|
33
|
+
if (!match)
|
|
34
|
+
return undefined;
|
|
35
|
+
const price = Number(match[0]);
|
|
36
|
+
// Convert from price per 1M tokens to price per token
|
|
37
|
+
return price / 1_000_000;
|
|
38
|
+
}
|
|
39
|
+
/** Fetch and parse pricing from OpenAI's pricing page */
|
|
40
|
+
async function scrapePricingFromWeb() {
|
|
41
|
+
console.log('[OpenAI Pricing] Fetching pricing from', PRICING_URL);
|
|
42
|
+
const response = await fetch(PRICING_URL, {
|
|
43
|
+
headers: {
|
|
44
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(`Failed to fetch pricing page: ${response.status}`);
|
|
49
|
+
}
|
|
50
|
+
const html = await response.text();
|
|
51
|
+
const pricing = {
|
|
52
|
+
currency: 'USD',
|
|
53
|
+
unit: 'per_token',
|
|
54
|
+
last_updated: new Date().toISOString(),
|
|
55
|
+
text_models: {},
|
|
56
|
+
};
|
|
57
|
+
// Extract all tables
|
|
58
|
+
const tables = [...html.matchAll(/<table[\s\S]*?<\/table>/gi)];
|
|
59
|
+
console.log(`[OpenAI Pricing] Found ${tables.length} tables`);
|
|
60
|
+
for (const tableMatch of tables) {
|
|
61
|
+
const table = tableMatch[0];
|
|
62
|
+
// Extract rows
|
|
63
|
+
const rows = [...table.matchAll(/<tr[\s\S]*?<\/tr>/gi)];
|
|
64
|
+
for (const rowMatch of rows) {
|
|
65
|
+
const cells = [...rowMatch[0].matchAll(/<t[hd][\s\S]*?<\/t[hd]>/gi)].map((c) => stripTags(c[0]));
|
|
66
|
+
if (cells.length < 2)
|
|
67
|
+
continue;
|
|
68
|
+
const [model, input, cached, output] = cells;
|
|
69
|
+
console.log(`[OpenAI Pricing] Parsed row - model: "${model}", input: "${input}", cached: "${cached}", output: "${output}"`);
|
|
70
|
+
const inputPrice = parsePrice(input);
|
|
71
|
+
const cachedPrice = parsePrice(cached);
|
|
72
|
+
const outputPrice = parsePrice(output);
|
|
73
|
+
if (!model || (!inputPrice && !cachedPrice && !outputPrice))
|
|
74
|
+
continue;
|
|
75
|
+
pricing.text_models[model] = {
|
|
76
|
+
...(inputPrice !== undefined && { input: inputPrice }),
|
|
77
|
+
...(cachedPrice !== undefined && { cached_input: cachedPrice }),
|
|
78
|
+
...(outputPrice !== undefined && { output: outputPrice }),
|
|
79
|
+
};
|
|
80
|
+
if (Object.keys(pricing.text_models).length % 5 === 0) {
|
|
81
|
+
console.log(`[OpenAI Pricing] Parsed ${Object.keys(pricing.text_models).length} models...`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log(`[OpenAI Pricing] Successfully scraped ${Object.keys(pricing.text_models).length} models`);
|
|
86
|
+
return pricing;
|
|
87
|
+
}
|
|
88
|
+
/** Check if cache file exists and is fresh */
|
|
89
|
+
async function isCacheFresh(cacheFile) {
|
|
90
|
+
try {
|
|
91
|
+
const stats = await fs.stat(cacheFile);
|
|
92
|
+
const age = Date.now() - stats.mtime.getTime();
|
|
93
|
+
const isFresh = age < CACHE_DURATION_MS;
|
|
94
|
+
console.log(`[OpenAI Pricing] Cache age: ${Math.round(age / 1000 / 60)} minutes, fresh: ${isFresh}`);
|
|
95
|
+
return isFresh;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
console.log('[OpenAI Pricing] No cache file found');
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Read cached pricing from file */
|
|
103
|
+
async function readCachedPricing(cacheFile) {
|
|
104
|
+
const content = await fs.readFile(cacheFile, 'utf-8');
|
|
105
|
+
return JSON.parse(content);
|
|
106
|
+
}
|
|
107
|
+
/** Write pricing cache to file */
|
|
108
|
+
async function writeCachedPricing(cacheFile, pricing) {
|
|
109
|
+
const dir = cacheFile.substring(0, cacheFile.lastIndexOf('/'));
|
|
110
|
+
await fs.mkdir(dir, { recursive: true });
|
|
111
|
+
await fs.writeFile(cacheFile, JSON.stringify(pricing, null, 2));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get OpenAI pricing with caching.
|
|
115
|
+
* Returns cached pricing if available and fresh, otherwise attempts to scrape and cache new data.
|
|
116
|
+
* Falls back to hardcoded pricing if scraping fails (due to Cloudflare or other blocks).
|
|
117
|
+
*
|
|
118
|
+
* @param cacheDir - Directory to store cache file (typically .metadata)
|
|
119
|
+
* @returns Map of model name -> ModelInfoPricing
|
|
120
|
+
*/
|
|
121
|
+
export async function getOpenAIPricing(cacheDir) {
|
|
122
|
+
const cacheFile = join(cacheDir, 'openai-pricing.json');
|
|
123
|
+
const result = new Map();
|
|
124
|
+
try {
|
|
125
|
+
let pricing = null;
|
|
126
|
+
// Try to use cache if it's fresh
|
|
127
|
+
if (await isCacheFresh(cacheFile)) {
|
|
128
|
+
try {
|
|
129
|
+
pricing = await readCachedPricing(cacheFile);
|
|
130
|
+
console.log('[OpenAI Pricing] Using cached pricing');
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error('[OpenAI Pricing] Error reading cache:', error);
|
|
134
|
+
pricing = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Scrape if we don't have fresh cache
|
|
138
|
+
if (!pricing) {
|
|
139
|
+
try {
|
|
140
|
+
pricing = await scrapePricingFromWeb();
|
|
141
|
+
try {
|
|
142
|
+
await writeCachedPricing(cacheFile, pricing);
|
|
143
|
+
console.log('[OpenAI Pricing] Cached pricing to', cacheFile);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error('[OpenAI Pricing] Error writing cache:', error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error('[OpenAI Pricing] Error scraping pricing, using fallback data:', error);
|
|
151
|
+
// Use fallback pricing
|
|
152
|
+
for (const [model, prices] of Object.entries(FALLBACK_PRICING)) {
|
|
153
|
+
result.set(model, {
|
|
154
|
+
prompt: prices.input,
|
|
155
|
+
completion: prices.output,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
console.log(`[OpenAI Pricing] Using fallback pricing for ${result.size} models`);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Convert to ModelInfoPricing format
|
|
163
|
+
for (const [model, prices] of Object.entries(pricing.text_models)) {
|
|
164
|
+
if (prices.input !== undefined && prices.output !== undefined) {
|
|
165
|
+
result.set(model, {
|
|
166
|
+
prompt: prices.input,
|
|
167
|
+
completion: prices.output,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
console.log(`[OpenAI Pricing] Loaded pricing for ${result.size} models`);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.error('[OpenAI Pricing] Unexpected error getting pricing, using fallback:', error);
|
|
175
|
+
// Use fallback pricing as last resort
|
|
176
|
+
for (const [model, prices] of Object.entries(FALLBACK_PRICING)) {
|
|
177
|
+
result.set(model, {
|
|
178
|
+
prompt: prices.input,
|
|
179
|
+
completion: prices.output,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=openai-pricing-scraper.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for thread and project names
|
|
3
|
+
*/
|
|
4
|
+
export declare const MAX_THREAD_NAME_LENGTH = 256;
|
|
5
|
+
/**
|
|
6
|
+
* Validates a thread name
|
|
7
|
+
* Returns an error message if invalid, or null if valid
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateThreadName(name: string | undefined): string | null;
|
|
10
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for thread and project names
|
|
3
|
+
*/
|
|
4
|
+
export const MAX_THREAD_NAME_LENGTH = 256;
|
|
5
|
+
/**
|
|
6
|
+
* Validates a thread name
|
|
7
|
+
* Returns an error message if invalid, or null if valid
|
|
8
|
+
*/
|
|
9
|
+
export function validateThreadName(name) {
|
|
10
|
+
// Empty or whitespace-only is allowed (will auto-generate)
|
|
11
|
+
if (!name || name.trim() === '') {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
// Check length
|
|
15
|
+
if (name.length > MAX_THREAD_NAME_LENGTH) {
|
|
16
|
+
return `Thread name must be ${MAX_THREAD_NAME_LENGTH} characters or less`;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=validation.js.map
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for the CLI
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Delays execution for a specified number of milliseconds
|
|
6
|
+
* @param ms - Number of milliseconds to delay
|
|
7
|
+
* @returns Promise that resolves after the delay
|
|
8
|
+
*/
|
|
9
|
+
export declare function delay(ms: number): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for the CLI
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Delays execution for a specified number of milliseconds
|
|
6
|
+
* @param ms - Number of milliseconds to delay
|
|
7
|
+
* @returns Promise that resolves after the delay
|
|
8
|
+
*/
|
|
9
|
+
export function delay(ms) {
|
|
10
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=utils.js.map
|