Skip to content

Commit 75c40a9

Browse files
authored
Merge branch 'master' into vsIcons
2 parents 5a2f0ed + a7eb677 commit 75c40a9

File tree

16 files changed

+250
-60
lines changed

16 files changed

+250
-60
lines changed

.gitignore

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,8 @@ apps/remixdesktop/log_input_signals_new.txt
7979
logs
8080
apps/remix-ide-e2e/src/extensions/chrome/metamask
8181
apps/remix-ide-e2e/tmp/
82-
apps/remix-ide-e2e/tmp/
8382

8483
# IDE - Cursor
85-
<<<<<<< HEAD
86-
.cursor/
87-
=======
8884
.cursor/
8985
PR_MESSAGE.md
90-
>>>>>>> master
86+
apps/remix-ide-e2e/tmp/
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
const testFolder = './apps/remix-ide-e2e/src/tests/'
2+
const fs = require('fs')
3+
const path = require('path')
4+
5+
/**
6+
* This script cleans up orphaned group test files.
7+
*
8+
* Group test files (e.g., editor_group1.test.ts) are automatically generated
9+
* from base test files (e.g., editor.test.ts) by buildGroupTests.js.
10+
*
11+
* When a base test file is deleted or renamed, its corresponding group test
12+
* files become orphaned and need to be removed. This script:
13+
*
14+
* 1. Scans all test files in the tests/ directory
15+
* 2. Identifies group test files by the pattern: *_group<N>.(test.ts|flaky.ts|pr.ts)
16+
* 3. Checks if the parent base test file exists
17+
* 4. Removes any orphaned group test files
18+
*
19+
* This script runs automatically as part of the build:e2e process to ensure
20+
* the test directory stays clean.
21+
*
22+
* Examples of group test files and their parents:
23+
* - editor_group1.test.ts → editor.test.ts
24+
* - terminal_group11.flaky.ts → terminal.test.ts
25+
* - matomo-consent_group2.pr.ts → matomo-consent.test.ts
26+
*/
27+
28+
let orphanedCount = 0
29+
let deletedCount = 0
30+
31+
console.log('🔍 Scanning for orphaned group test files...\n')
32+
33+
// Get all files in the test folder
34+
const allFiles = fs.readdirSync(testFolder)
35+
36+
// Separate base tests and group tests
37+
// Group tests can have patterns like:
38+
// - editor_group1.test.ts (standard)
39+
// - terminal_group11.flaky.ts (flaky tagged)
40+
// - matomo-consent_group2.pr.ts (PR tagged)
41+
const groupTestPattern = /_group\d+\.(test\.ts|flaky\.ts|pr\.ts)$/
42+
43+
const groupTests = allFiles.filter(file => groupTestPattern.test(file))
44+
const baseTests = allFiles.filter(file => file.endsWith('.test.ts') && !groupTestPattern.test(file))
45+
46+
console.log(`📊 Found ${baseTests.length} base test files`)
47+
console.log(`📊 Found ${groupTests.length} group test files\n`)
48+
49+
// Check each group test to see if its parent exists
50+
groupTests.forEach(groupFile => {
51+
// Extract the base filename from the group test
52+
// Examples:
53+
// editor_group1.test.ts -> editor.test.ts
54+
// dgit_local_group4.test.ts -> dgit_local.test.ts
55+
// terminal_group11.flaky.ts -> terminal.test.ts
56+
57+
let baseFileName = groupFile
58+
.replace(/_group\d+/, '') // Remove _groupN
59+
.replace(/\.(flaky|pr)/, '') // Remove .flaky or .pr tag
60+
61+
// Ensure it ends with .test.ts (but don't double up)
62+
if (!baseFileName.endsWith('.test.ts')) {
63+
baseFileName = baseFileName.replace(/\.ts$/, '.test.ts')
64+
}
65+
66+
// Check if the base test file exists
67+
if (!baseTests.includes(baseFileName)) {
68+
orphanedCount++
69+
const groupFilePath = path.join(testFolder, groupFile)
70+
71+
console.log(`❌ Orphaned: ${groupFile}`)
72+
console.log(` Missing parent: ${baseFileName}`)
73+
74+
try {
75+
// Read the file to verify it's a group test before deleting
76+
const content = fs.readFileSync(groupFilePath, 'utf8')
77+
const isGroupTest = content.includes('buildGroupTest') ||
78+
content.includes('import * as test from')
79+
80+
if (isGroupTest) {
81+
fs.unlinkSync(groupFilePath)
82+
deletedCount++
83+
console.log(` ✅ Deleted\n`)
84+
} else {
85+
console.log(` ⚠️ Skipped (not a generated group test)\n`)
86+
}
87+
} catch (error) {
88+
console.log(` ⚠️ Error: ${error.message}\n`)
89+
}
90+
}
91+
})
92+
93+
// Summary
94+
console.log('─'.repeat(50))
95+
console.log(`\n📋 Summary:`)
96+
console.log(` Orphaned group tests found: ${orphanedCount}`)
97+
console.log(` Files deleted: ${deletedCount}`)
98+
99+
if (deletedCount > 0) {
100+
console.log(`\n✨ Cleanup completed successfully!`)
101+
} else if (orphanedCount === 0) {
102+
console.log(`\n✅ No orphaned group tests found. Everything is clean!`)
103+
} else {
104+
console.log(`\n⚠️ Some orphaned files were not deleted (see warnings above)`)
105+
}
106+
107+
process.exit(0)

apps/remix-ide/src/app/plugins/remixAIPlugin.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as packageJson from '../../../../../package.json'
22
import { Plugin } from '@remixproject/engine';
33
import { trackMatomoEvent } from '@remix-api'
4-
import { IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, AssistantParams, CodeExplainAgent, SecurityAgent, CompletionParams, OllamaInferencer, isOllamaAvailable, getBestAvailableModel } from '@remix/remix-ai-core';
5-
//@ts-ignore
4+
import { IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, AssistantParams, CodeExplainAgent, SecurityAgent, CompletionParams, OllamaInferencer, isOllamaAvailable, getBestAvailableModel, resetOllamaHostOnSettingsChange } from '@remix/remix-ai-core';
65
import { CodeCompletionAgent, ContractAgent, workspaceAgent, IContextType, mcpDefaultServersConfig } from '@remix/remix-ai-core';
76
import { MCPInferencer } from '@remix/remix-ai-core';
87
import { IMCPServer, IMCPConnectionStatus } from '@remix/remix-ai-core';
@@ -64,6 +63,8 @@ export class RemixAIPlugin extends Plugin {
6463
}
6564

6665
onActivation(): void {
66+
// Expose Ollama reset function globally for settings integration
67+
resetOllamaHostOnSettingsChange();
6768

6869
if (this.isOnDesktop) {
6970
this.useRemoteInferencer = true

apps/remix-ide/src/app/tabs/locales/en/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,8 @@
6969
"settings.mcpServerConfigurationDescription": "Connect to Model Context Protocol servers for enhanced AI context",
7070
"settings.enableMCPEnhancement": "Enable MCP Integration",
7171
"settings.enableMCPEnhancementDescription": "Manage your MCP server connections",
72-
"settings.aiPrivacyPolicyDescription": "Understand how RemixAI processes your data."
72+
"settings.aiPrivacyPolicyDescription": "Understand how RemixAI processes your data.",
73+
"settings.ollamaConfig": "Ollama URL Configuration",
74+
"settings.ollamaConfigDescription": "Configure Ollama endpoint for local AI model integration",
75+
"settings.ollama-endpoint": "ENDPOINT URL"
7376
}

libs/remix-ai-core/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { RemoteInferencer } from './inferencers/remote/remoteInference'
99
import { OllamaInferencer } from './inferencers/local/ollamaInferencer'
1010
import { MCPInferencer } from './inferencers/mcp/mcpInferencer'
1111
import { RemixMCPServer, createRemixMCPServer } from './remix-mcp-server'
12-
import { isOllamaAvailable, getBestAvailableModel, listModels, discoverOllamaHost } from './inferencers/local/ollama'
12+
import { isOllamaAvailable, getBestAvailableModel, listModels, discoverOllamaHost, resetOllamaHostOnSettingsChange } from './inferencers/local/ollama'
1313
import { FIMModelManager, FIMModelConfig, FIM_MODEL_CONFIGS } from './inferencers/local/fimModelConfig'
1414
import { ChatHistory } from './prompts/chat'
1515
import { downloadLatestReleaseExecutable } from './helpers/inferenceServerReleases'
@@ -18,7 +18,7 @@ import { mcpDefaultServersConfig } from './config/mcpDefaultServers'
1818
export {
1919
IModel, IModelResponse, ChatCommandParser,
2020
ModelType, DefaultModels, ICompletions, IParams, IRemoteModel, buildChatPrompt,
21-
RemoteInferencer, OllamaInferencer, MCPInferencer, RemixMCPServer, isOllamaAvailable, getBestAvailableModel, listModels, discoverOllamaHost,
21+
RemoteInferencer, OllamaInferencer, MCPInferencer, RemixMCPServer, isOllamaAvailable, getBestAvailableModel, listModels, discoverOllamaHost, resetOllamaHostOnSettingsChange,
2222
FIMModelManager, FIMModelConfig, FIM_MODEL_CONFIGS, createRemixMCPServer,
2323
InsertionParams, CompletionParams, GenerationParams, AssistantParams,
2424
ChatEntry, AIRequestType, ChatHistory, downloadLatestReleaseExecutable,

libs/remix-ai-core/src/inferencers/local/ollama.ts

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,89 @@
11
import axios from 'axios';
2-
3-
// Helper function to track events using MatomoManager instance
4-
function trackMatomoEvent(category: string, action: string, name?: string) {
5-
try {
6-
if (typeof window !== 'undefined' && (window as any)._matomoManagerInstance) {
7-
(window as any)._matomoManagerInstance.trackEvent(category, action, name)
8-
}
9-
} catch (error) {
10-
// Silent fail for tracking
11-
}
12-
}
2+
import { Registry } from '@remix-project/remix-lib';
3+
import { trackMatomoEvent } from '@remix-api'
134

145
// default Ollama ports to check (11434 is the legacy/standard port)
156
const OLLAMA_PORTS = [11434, 11435, 11436];
167
const OLLAMA_BASE_HOST = 'http://localhost';
8+
const DEFAULT_OLLAMA_HOST = 'http://localhost:11434';
179

1810
let discoveredOllamaHost: string | null = null;
1911

12+
function getConfiguredOllamaEndpoint(): string | null {
13+
const filemanager = Registry.getInstance().get('filemanager').api;
14+
try {
15+
const config = Registry.getInstance().get('config').api
16+
const configuredEndpoint = config.get('settings/ollama-endpoint');
17+
if (configuredEndpoint && configuredEndpoint !== DEFAULT_OLLAMA_HOST) {
18+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_using_configured_endpoint', value: configuredEndpoint });
19+
return configuredEndpoint;
20+
}
21+
} catch (error) {
22+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_config_access_failed', value: error.message || 'unknown' });
23+
}
24+
return null;
25+
}
26+
2027
export async function discoverOllamaHost(): Promise<string | null> {
28+
const filemanager = Registry.getInstance().get('filemanager').api;
2129
if (discoveredOllamaHost) {
22-
trackMatomoEvent('ai', 'remixAI', `ollama_host_cache_hit:${discoveredOllamaHost}`);
30+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_host_cache_hit:${discoveredOllamaHost}` })
2331
return discoveredOllamaHost;
2432
}
2533

34+
// First, try to use the configured endpoint from settings
35+
const configuredEndpoint = getConfiguredOllamaEndpoint();
36+
if (configuredEndpoint) {
37+
try {
38+
const res = await axios.get(`${configuredEndpoint}/api/tags`, { timeout: 2000 });
39+
if (res.status === 200) {
40+
discoveredOllamaHost = configuredEndpoint;
41+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_configured_endpoint_success', value: configuredEndpoint });
42+
return configuredEndpoint;
43+
}
44+
return null;
45+
} catch (error) {
46+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_configured_endpoint_failed', value: `${configuredEndpoint}:${error.message || 'unknown'}` });
47+
// Fall back to discovery if configured endpoint fails
48+
return null;
49+
}
50+
}
51+
52+
// Fall back to port discovery if no configured endpoint
2653
for (const port of OLLAMA_PORTS) {
2754
const host = `${OLLAMA_BASE_HOST}:${port}`;
28-
trackMatomoEvent('ai', 'remixAI', `ollama_port_check:${port}`);
55+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_port_check:${port}` });
2956
try {
3057
const res = await axios.get(`${host}/api/tags`, { timeout: 2000 });
3158
if (res.status === 200) {
3259
discoveredOllamaHost = host;
33-
trackMatomoEvent('ai', 'remixAI', `ollama_host_discovered_success:${host}`);
60+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_host_discovered_success:${host}` });
3461
return host;
3562
}
3663
} catch (error) {
37-
trackMatomoEvent('ai', 'remixAI', `ollama_port_connection_failed:${port}:${error.message || 'unknown'}`);
64+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_port_connection_failed:${port}:${error.message || 'unknown'}` });
3865
continue; // next port
3966
}
4067
}
41-
trackMatomoEvent('ai', 'remixAI', 'ollama_host_discovery_failed:no_ports_available');
68+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_host_discovery_failed:no_ports_available' });
4269
return null;
4370
}
4471

4572
export async function isOllamaAvailable(): Promise<boolean> {
46-
trackMatomoEvent('ai', 'remixAI', 'ollama_availability_check:checking');
73+
const filemanager = Registry.getInstance().get('filemanager').api;
74+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_availability_check:checking' });
4775
const host = await discoverOllamaHost();
4876
const isAvailable = host !== null;
49-
trackMatomoEvent('ai', 'remixAI', `ollama_availability_result:available:${isAvailable}`);
77+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_availability_result:available:${isAvailable}` });
5078
return isAvailable;
5179
}
5280

5381
export async function listModels(): Promise<string[]> {
54-
trackMatomoEvent('ai', 'remixAI', 'ollama_list_models_start:fetching');
82+
const filemanager = Registry.getInstance().get('filemanager').api;
83+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_list_models_start:fetching' });
5584
const host = await discoverOllamaHost();
5685
if (!host) {
57-
trackMatomoEvent('ai', 'remixAI', 'ollama_list_models_failed:no_host');
86+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_list_models_failed:no_host' });
5887
throw new Error('Ollama is not available');
5988
}
6089

@@ -71,26 +100,35 @@ export function getOllamaHost(): string | null {
71100
}
72101

73102
export function resetOllamaHost(): void {
74-
trackMatomoEvent('ai', 'remixAI', `ollama_reset_host:${discoveredOllamaHost || 'null'}`);
103+
const fileManager = Registry.getInstance().get('filemanager').api;
104+
trackMatomoEvent(fileManager, { category: 'ai', action: 'remixAI', name: `ollama_reset_host:${discoveredOllamaHost || 'null'}` });
75105
discoveredOllamaHost = null;
76106
}
77107

108+
export function resetOllamaHostOnSettingsChange(): void {
109+
const fileManager = Registry.getInstance().get('filemanager').api;
110+
// This function should be called when Ollama settings are updated
111+
resetOllamaHost();
112+
trackMatomoEvent(fileManager, { category: 'ai', action: 'remixAI', name: 'ollama_reset_on_settings_change' });
113+
}
114+
78115
export async function pullModel(modelName: string): Promise<void> {
116+
const filemanager = Registry.getInstance().get('filemanager').api;
79117
// in case the user wants to pull a model from registry
80-
trackMatomoEvent('ai', 'remixAI', `ollama_pull_model_start:${modelName}`);
118+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_pull_model_start:${modelName}` });
81119
const host = await discoverOllamaHost();
82120
if (!host) {
83-
trackMatomoEvent('ai', 'remixAI', `ollama_pull_model_failed:${modelName}|no_host`);
121+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_pull_model_failed:${modelName}|no_host` });
84122
throw new Error('Ollama is not available');
85123
}
86124

87125
try {
88126
const startTime = Date.now();
89127
await axios.post(`${host}/api/pull`, { name: modelName });
90128
const duration = Date.now() - startTime;
91-
trackMatomoEvent('ai', 'remixAI', `ollama_pull_model_success:${modelName}|duration:${duration}ms`);
129+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_pull_model_success:${modelName}|duration:${duration}ms` });
92130
} catch (error) {
93-
trackMatomoEvent('ai', 'remixAI', `ollama_pull_model_error:${modelName}|${error.message || 'unknown'}`);
131+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_pull_model_error:${modelName}|${error.message || 'unknown'}` });
94132
console.error('Error pulling model:', error);
95133
throw new Error(`Failed to pull model: ${modelName}`);
96134
}
@@ -106,7 +144,8 @@ export async function validateModel(modelName: string): Promise<boolean> {
106144
}
107145

108146
export async function getBestAvailableModel(): Promise<string | null> {
109-
trackMatomoEvent('ai', 'remixAI', 'ollama_get_best');
147+
const filemanager = Registry.getInstance().get('filemanager').api;
148+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: 'ollama_get_best' });
110149
try {
111150
const models = await listModels();
112151
if (models.length === 0) return null;
@@ -125,7 +164,7 @@ export async function getBestAvailableModel(): Promise<string | null> {
125164
// TODO get model stats and get best model
126165
return models[0];
127166
} catch (error) {
128-
trackMatomoEvent('ai', 'remixAI', `ollama_get_best_model_error:${error.message || 'unknown'}`);
167+
trackMatomoEvent(filemanager, { category: 'ai', action: 'remixAI', name: `ollama_get_best_model_error:${error.message || 'unknown'}` });
129168
console.error('Error getting best available model:', error);
130169
return null;
131170
}

libs/remix-ai-core/src/inferencers/mcp/toolApiGenerator.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ const modified = fileContent.replace('old', 'new');
2525
const compiled = await callMCPTool('solidity_compile', { file: 'contract.sol' });
2626
const deployed = await callMCPTool('deploy_contract', { contractName: 'MyToken' });
2727
28-
// Multiple tasks 2
29-
const fileContent = await callMCPTool('file_read', { path:'contract.sol' });
30-
const content = JSON.parse(fileContent.content[0].text).content
31-
const updatedContent = fileContent.replace('contract Subscription', 'contract MySubscriptionContract');
32-
await callMCPTool('file_write', { path: 'ccontract.sol', content: updatedContent });
33-
3428
3529
// With loops for batch operations
3630
const files = ['contracts/Token.sol', 'contracts/NFT.sol', 'contracts/DAO.sol'];

libs/remix-ai-core/src/inferencers/remote/remoteInference.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ export class RemoteInferencer implements ICompletions, IGeneration {
2424
protected sanitizePromptByteSize(prompt: string, provider?: string): string {
2525
// Provider-specific max byte limits
2626
const providerLimits: Record<string, number> = {
27-
'mistralai': 80000,
28-
'anthropic': 80000,
29-
'openai': 80000
27+
'mistralai': 70000,
28+
'anthropic': 70000,
29+
'openai': 70000
3030
};
3131

32-
// Get max bytes based on provider, default to 80KB
33-
const maxBytes = provider ? (providerLimits[provider.toLowerCase()] || 80000) : 80000;
32+
// Get max bytes based on provider, default to 70KB
33+
const maxBytes = provider ? (providerLimits[provider.toLowerCase()] || 70000) : 70000;
3434

3535
const encoder = new TextEncoder();
3636
const promptBytes = encoder.encode(prompt); // rough estimation, real size might be 10% more

0 commit comments

Comments
 (0)