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.
Files changed (119) hide show
  1. package/README.md +1 -7
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +92 -32
  4. package/dist/lib/response-builder.d.ts +50 -0
  5. package/dist/lib/response-builder.js +56 -0
  6. package/dist/lib/stream-helper.d.ts +39 -0
  7. package/dist/lib/stream-helper.js +43 -0
  8. package/dist/managers/ConversationManager.d.ts +83 -0
  9. package/dist/managers/ConversationManager.js +129 -0
  10. package/dist/managers/GitManager.d.ts +133 -0
  11. package/dist/managers/GitManager.js +330 -0
  12. package/dist/managers/MetadataManager.d.ts +139 -0
  13. package/dist/managers/MetadataManager.js +309 -0
  14. package/dist/managers/ModelManager.d.ts +57 -0
  15. package/dist/managers/ModelManager.js +129 -0
  16. package/dist/managers/NeovateExecutor.d.ts +40 -0
  17. package/dist/managers/NeovateExecutor.js +138 -0
  18. package/dist/managers/ProjectManager.d.ts +162 -0
  19. package/dist/managers/ProjectManager.js +353 -0
  20. package/dist/managers/ThreadManager.d.ts +181 -0
  21. package/dist/managers/ThreadManager.js +325 -0
  22. package/dist/managers/conversation-manager.d.ts +83 -0
  23. package/dist/managers/conversation-manager.js +129 -0
  24. package/dist/managers/git-manager.d.ts +133 -0
  25. package/dist/managers/git-manager.js +330 -0
  26. package/dist/managers/metadata-manager.d.ts +139 -0
  27. package/dist/managers/metadata-manager.js +305 -0
  28. package/dist/managers/model-manager.d.ts +59 -0
  29. package/dist/managers/model-manager.js +144 -0
  30. package/dist/managers/neovate-executor.d.ts +43 -0
  31. package/dist/managers/neovate-executor.js +205 -0
  32. package/dist/managers/processing-state-manager.d.ts +40 -0
  33. package/dist/managers/processing-state-manager.js +27 -0
  34. package/dist/managers/project-manager.d.ts +199 -0
  35. package/dist/managers/project-manager.js +465 -0
  36. package/dist/managers/thread-manager.d.ts +193 -0
  37. package/dist/managers/thread-manager.js +368 -0
  38. package/dist/model-info-aihubmix.d.ts +25 -0
  39. package/dist/model-info-aihubmix.js +117 -0
  40. package/dist/model-info-openai.d.ts +17 -0
  41. package/dist/model-info-openai.js +59 -0
  42. package/dist/model-info-openrouter.d.ts +25 -0
  43. package/dist/model-info-openrouter.js +101 -0
  44. package/dist/model-info.d.ts +37 -0
  45. package/dist/model-info.js +39 -0
  46. package/dist/provider-data.d.ts +101 -0
  47. package/dist/provider-data.js +471 -0
  48. package/dist/provider.d.ts +10 -0
  49. package/dist/provider.js +192 -0
  50. package/dist/public/android-chrome-192x192.png +0 -0
  51. package/dist/public/android-chrome-512x512.png +0 -0
  52. package/dist/public/apple-touch-icon.png +0 -0
  53. package/dist/public/assets/index-B443aj9k.js +8506 -0
  54. package/dist/public/assets/index-CjXGVbI7.css +1 -0
  55. package/dist/public/assets/index-DJC-p914.js +8506 -0
  56. package/dist/public/favicon-16x16.png +0 -0
  57. package/dist/public/favicon-32x32.png +0 -0
  58. package/dist/public/favicon.ico +0 -0
  59. package/dist/public/index.html +28 -0
  60. package/dist/public/manifest.json +82 -0
  61. package/dist/public/placeholder-logo.svg +1 -0
  62. package/dist/public/placeholder.svg +1 -0
  63. package/dist/public/snpro.woff2 +0 -0
  64. package/dist/public/tarsk-color.svg +12 -0
  65. package/dist/public/tarsk.png +0 -0
  66. package/dist/public/tarsk.svg +12 -0
  67. package/dist/public/zalando-sans.woff2 +0 -0
  68. package/dist/routes/chat-old.d.ts +21 -0
  69. package/dist/routes/chat-old.js +251 -0
  70. package/dist/routes/chat.d.ts +21 -0
  71. package/dist/routes/chat.js +217 -0
  72. package/dist/routes/git.d.ts +4 -0
  73. package/dist/routes/git.js +668 -0
  74. package/dist/routes/models.d.ts +18 -0
  75. package/dist/routes/models.js +128 -0
  76. package/dist/routes/projects-old.d.ts +20 -0
  77. package/dist/routes/projects-old.js +297 -0
  78. package/dist/routes/projects.d.ts +20 -0
  79. package/dist/routes/projects.js +365 -0
  80. package/dist/routes/providers.d.ts +15 -0
  81. package/dist/routes/providers.js +130 -0
  82. package/dist/routes/threads-old.d.ts +14 -0
  83. package/dist/routes/threads-old.js +393 -0
  84. package/dist/routes/threads.d.ts +14 -0
  85. package/dist/routes/threads.js +352 -0
  86. package/dist/types/models.d.ts +315 -0
  87. package/dist/types/models.js +11 -0
  88. package/dist/utils/env-manager.d.ts +3 -0
  89. package/dist/utils/env-manager.js +60 -0
  90. package/dist/utils/open-router-models.d.ts +45 -0
  91. package/dist/utils/open-router-models.js +103 -0
  92. package/dist/utils/openai-models.d.ts +63 -0
  93. package/dist/utils/openai-models.js +152 -0
  94. package/dist/utils/openai-pricing-scraper.d.ts +17 -0
  95. package/dist/utils/openai-pricing-scraper.js +185 -0
  96. package/dist/utils/validation.d.ts +10 -0
  97. package/dist/utils/validation.js +20 -0
  98. package/dist/utils.d.ts +10 -0
  99. package/dist/utils.js +12 -0
  100. package/package.json +36 -22
  101. package/LICENSE.md +0 -7
  102. package/dist/agent/agent.js +0 -131
  103. package/dist/agent/interfaces.js +0 -1
  104. package/dist/api/encryption.js +0 -41
  105. package/dist/api/models.js +0 -169
  106. package/dist/api/prompt.js +0 -12
  107. package/dist/api/settings.js +0 -43
  108. package/dist/api/test.js +0 -29
  109. package/dist/api/tools.js +0 -287
  110. package/dist/api/utils.js +0 -18
  111. package/dist/interfaces/meta.js +0 -1
  112. package/dist/interfaces/model.js +0 -1
  113. package/dist/interfaces/settings.js +0 -1
  114. package/dist/log/log.js +0 -33
  115. package/dist/prompt.js +0 -49
  116. package/dist/tools.js +0 -84
  117. package/dist/utils/files.js +0 -14
  118. package/dist/utils/json-file.js +0 -28
  119. 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
@@ -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