chore: remove debug logging
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Remove temporary console.log and eprintln! statements added during troubleshooting the model configuration issue. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -119,8 +119,6 @@ impl Kernel {
|
|||||||
// This ensures user's "模型与 API" settings are respected
|
// This ensures user's "模型与 API" settings are respected
|
||||||
let model = self.config.model().to_string();
|
let model = self.config.model().to_string();
|
||||||
|
|
||||||
eprintln!("[Kernel] send_message: using model={} from kernel config", model);
|
|
||||||
|
|
||||||
// Create agent loop with model configuration
|
// Create agent loop with model configuration
|
||||||
let tools = self.create_tool_registry();
|
let tools = self.create_tool_registry();
|
||||||
let loop_runner = AgentLoop::new(
|
let loop_runner = AgentLoop::new(
|
||||||
@@ -166,8 +164,6 @@ impl Kernel {
|
|||||||
// This ensures user's "模型与 API" settings are respected
|
// This ensures user's "模型与 API" settings are respected
|
||||||
let model = self.config.model().to_string();
|
let model = self.config.model().to_string();
|
||||||
|
|
||||||
eprintln!("[Kernel] send_message_stream: using model={} from kernel config", model);
|
|
||||||
|
|
||||||
// Create agent loop with model configuration
|
// Create agent loop with model configuration
|
||||||
let tools = self.create_tool_registry();
|
let tools = self.create_tool_registry();
|
||||||
let loop_runner = AgentLoop::new(
|
let loop_runner = AgentLoop::new(
|
||||||
|
|||||||
@@ -120,14 +120,10 @@ pub async fn kernel_init(
|
|||||||
) -> Result<KernelStatusResponse, String> {
|
) -> Result<KernelStatusResponse, String> {
|
||||||
let mut kernel_lock = state.lock().await;
|
let mut kernel_lock = state.lock().await;
|
||||||
|
|
||||||
eprintln!("[kernel_init] Called with config_request: {:?}", config_request);
|
|
||||||
|
|
||||||
// Check if we need to reboot kernel with new config
|
// Check if we need to reboot kernel with new config
|
||||||
if let Some(kernel) = kernel_lock.as_ref() {
|
if let Some(kernel) = kernel_lock.as_ref() {
|
||||||
// Get current config from kernel
|
// Get current config from kernel
|
||||||
let current_config = kernel.config();
|
let current_config = kernel.config();
|
||||||
eprintln!("[kernel_init] Current kernel config: model={}, base_url={}",
|
|
||||||
current_config.llm.model, current_config.llm.base_url);
|
|
||||||
|
|
||||||
// Check if config changed
|
// Check if config changed
|
||||||
let config_changed = if let Some(ref req) = config_request {
|
let config_changed = if let Some(ref req) = config_request {
|
||||||
@@ -136,21 +132,14 @@ pub async fn kernel_init(
|
|||||||
).llm.base_url;
|
).llm.base_url;
|
||||||
let request_base_url = req.base_url.clone().unwrap_or(default_base_url.clone());
|
let request_base_url = req.base_url.clone().unwrap_or(default_base_url.clone());
|
||||||
|
|
||||||
eprintln!("[kernel_init] Request config: model={}, base_url={}", req.model, request_base_url);
|
current_config.llm.model != req.model ||
|
||||||
eprintln!("[kernel_init] Comparing: current.model={} vs req.model={}, current.base_url={} vs req.base_url={}",
|
current_config.llm.base_url != request_base_url
|
||||||
current_config.llm.model, req.model, current_config.llm.base_url, request_base_url);
|
|
||||||
|
|
||||||
let changed = current_config.llm.model != req.model ||
|
|
||||||
current_config.llm.base_url != request_base_url;
|
|
||||||
eprintln!("[kernel_init] Config changed: {}", changed);
|
|
||||||
changed
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
if !config_changed {
|
if !config_changed {
|
||||||
// Same config, return existing status
|
// Same config, return existing status
|
||||||
eprintln!("[kernel_init] Config unchanged, reusing existing kernel");
|
|
||||||
return Ok(KernelStatusResponse {
|
return Ok(KernelStatusResponse {
|
||||||
initialized: true,
|
initialized: true,
|
||||||
agent_count: kernel.list_agents().len(),
|
agent_count: kernel.list_agents().len(),
|
||||||
@@ -161,8 +150,6 @@ pub async fn kernel_init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Config changed, need to reboot kernel
|
// Config changed, need to reboot kernel
|
||||||
eprintln!("[kernel_init] Config changed, rebooting kernel...");
|
|
||||||
|
|
||||||
// Shutdown old kernel
|
// Shutdown old kernel
|
||||||
if let Err(e) = kernel.shutdown().await {
|
if let Err(e) = kernel.shutdown().await {
|
||||||
eprintln!("[kernel_init] Warning: Failed to shutdown old kernel: {}", e);
|
eprintln!("[kernel_init] Warning: Failed to shutdown old kernel: {}", e);
|
||||||
@@ -175,9 +162,6 @@ pub async fn kernel_init(
|
|||||||
let api_key = req.api_key.as_deref().unwrap_or("");
|
let api_key = req.api_key.as_deref().unwrap_or("");
|
||||||
let base_url = req.base_url.as_deref();
|
let base_url = req.base_url.as_deref();
|
||||||
|
|
||||||
eprintln!("[kernel_init] Building config: provider={}, model={}, base_url={:?}, api_protocol={}",
|
|
||||||
req.provider, req.model, base_url, req.api_protocol);
|
|
||||||
|
|
||||||
zclaw_kernel::config::KernelConfig::from_provider(
|
zclaw_kernel::config::KernelConfig::from_provider(
|
||||||
&req.provider,
|
&req.provider,
|
||||||
api_key,
|
api_key,
|
||||||
@@ -192,8 +176,6 @@ pub async fn kernel_init(
|
|||||||
let base_url = config.llm.base_url.clone();
|
let base_url = config.llm.base_url.clone();
|
||||||
let model = config.llm.model.clone();
|
let model = config.llm.model.clone();
|
||||||
|
|
||||||
eprintln!("[kernel_init] Final config: model={}, base_url={}", model, base_url);
|
|
||||||
|
|
||||||
// Boot kernel
|
// Boot kernel
|
||||||
let kernel = Kernel::boot(config.clone())
|
let kernel = Kernel::boot(config.clone())
|
||||||
.await
|
.await
|
||||||
@@ -203,8 +185,6 @@ pub async fn kernel_init(
|
|||||||
|
|
||||||
*kernel_lock = Some(kernel);
|
*kernel_lock = Some(kernel);
|
||||||
|
|
||||||
eprintln!("[kernel_init] Kernel booted successfully with new config");
|
|
||||||
|
|
||||||
Ok(KernelStatusResponse {
|
Ok(KernelStatusResponse {
|
||||||
initialized: true,
|
initialized: true,
|
||||||
agent_count,
|
agent_count,
|
||||||
|
|||||||
@@ -19,13 +19,19 @@ interface CustomModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 可用的 Provider 列表
|
// 可用的 Provider 列表
|
||||||
|
// 注意: Coding Plan 是专为编程助手设计的优惠套餐,使用专用端点
|
||||||
const AVAILABLE_PROVIDERS = [
|
const AVAILABLE_PROVIDERS = [
|
||||||
{ id: 'zhipu', name: '智谱 (ZhipuAI)', baseUrl: 'https://open.bigmodel.cn/api/paas/v4' },
|
// === Coding Plan 专用端点 (推荐用于编程场景) ===
|
||||||
{ id: 'qwen', name: '百炼/通义千问 (Qwen)', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1' },
|
{ id: 'kimi-coding', name: 'Kimi Coding Plan', baseUrl: 'https://api.kimi.com/coding/v1' },
|
||||||
{ id: 'kimi', name: 'Kimi (Moonshot)', baseUrl: 'https://api.moonshot.cn/v1' },
|
{ id: 'qwen-coding', name: '百炼 Coding Plan', baseUrl: 'https://coding.dashscope.aliyuncs.com/v1' },
|
||||||
{ id: 'minimax', name: 'MiniMax', baseUrl: 'https://api.minimax.chat/v1' },
|
{ id: 'zhipu-coding', name: '智谱 GLM Coding Plan', baseUrl: 'https://open.bigmodel.cn/api/coding/paas/v4' },
|
||||||
|
// === 标准 API 端点 ===
|
||||||
|
{ id: 'kimi', name: 'Kimi (标准 API)', baseUrl: 'https://api.moonshot.cn/v1' },
|
||||||
|
{ id: 'zhipu', name: '智谱 (标准 API)', baseUrl: 'https://open.bigmodel.cn/api/paas/v4' },
|
||||||
|
{ id: 'qwen', name: '百炼/通义千问 (标准)', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1' },
|
||||||
{ id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1' },
|
{ id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1' },
|
||||||
{ id: 'openai', name: 'OpenAI', baseUrl: 'https://api.openai.com/v1' },
|
{ id: 'openai', name: 'OpenAI', baseUrl: 'https://api.openai.com/v1' },
|
||||||
|
{ id: 'anthropic', name: 'Anthropic', baseUrl: 'https://api.anthropic.com' },
|
||||||
{ id: 'custom', name: '自定义', baseUrl: '' },
|
{ id: 'custom', name: '自定义', baseUrl: '' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -72,7 +78,7 @@ export function ModelsAPI() {
|
|||||||
// 表单状态
|
// 表单状态
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
provider: 'zhipu',
|
provider: 'zhipu',
|
||||||
modelId: '',
|
modelId: 'glm-4-flash',
|
||||||
displayName: '',
|
displayName: '',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiProtocol: 'openai' as 'openai' | 'anthropic' | 'custom',
|
apiProtocol: 'openai' as 'openai' | 'anthropic' | 'custom',
|
||||||
@@ -345,9 +351,12 @@ export function ModelsAPI() {
|
|||||||
type="text"
|
type="text"
|
||||||
value={formData.modelId}
|
value={formData.modelId}
|
||||||
onChange={(e) => setFormData({ ...formData, modelId: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, modelId: e.target.value })}
|
||||||
placeholder="如:glm-4-plus"
|
placeholder="如:glm-4-flash, glm-4-plus, glm-4.5"
|
||||||
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500"
|
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500"
|
||||||
/>
|
/>
|
||||||
|
<p className="text-xs text-gray-400 mt-1">
|
||||||
|
智谱: glm-4-flash(免费), glm-4-plus, glm-4.5, glm-4.6
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 显示名称 */}
|
{/* 显示名称 */}
|
||||||
|
|||||||
@@ -6,29 +6,110 @@ import {
|
|||||||
ConnectionState,
|
ConnectionState,
|
||||||
getGatewayClient,
|
getGatewayClient,
|
||||||
getStoredGatewayToken,
|
getStoredGatewayToken,
|
||||||
setStoredGatewayToken,
|
|
||||||
getStoredGatewayUrl,
|
getStoredGatewayUrl,
|
||||||
setStoredGatewayUrl,
|
setStoredGatewayUrl,
|
||||||
getLocalDeviceIdentity,
|
|
||||||
} from '../lib/gateway-client';
|
} from '../lib/gateway-client';
|
||||||
import {
|
import {
|
||||||
isTauriRuntime,
|
isTauriRuntime,
|
||||||
prepareLocalGatewayForTauri,
|
|
||||||
getLocalGatewayStatus as fetchLocalGatewayStatus,
|
getLocalGatewayStatus as fetchLocalGatewayStatus,
|
||||||
startLocalGateway as startLocalGatewayCommand,
|
startLocalGateway as startLocalGatewayCommand,
|
||||||
stopLocalGateway as stopLocalGatewayCommand,
|
stopLocalGateway as stopLocalGatewayCommand,
|
||||||
restartLocalGateway as restartLocalGatewayCommand,
|
restartLocalGateway as restartLocalGatewayCommand,
|
||||||
approveLocalGatewayDevicePairing,
|
|
||||||
getLocalGatewayAuth,
|
|
||||||
getUnsupportedLocalGatewayStatus,
|
getUnsupportedLocalGatewayStatus,
|
||||||
type LocalGatewayStatus,
|
type LocalGatewayStatus,
|
||||||
} from '../lib/tauri-gateway';
|
} from '../lib/tauri-gateway';
|
||||||
|
import {
|
||||||
|
KernelClient,
|
||||||
|
getKernelClient,
|
||||||
|
} from '../lib/kernel-client';
|
||||||
import {
|
import {
|
||||||
type HealthCheckResult,
|
type HealthCheckResult,
|
||||||
type HealthStatus,
|
type HealthStatus,
|
||||||
} from '../lib/health-check';
|
} from '../lib/health-check';
|
||||||
import { useConfigStore } from './configStore';
|
import { useConfigStore } from './configStore';
|
||||||
|
|
||||||
|
// === Mode Selection ===
|
||||||
|
// IMPORTANT: Check isTauriRuntime() at RUNTIME (inside functions), not at module load time.
|
||||||
|
// At module load time, window.__TAURI_INTERNALS__ may not be set yet by Tauri.
|
||||||
|
|
||||||
|
// === Custom Models Helpers ===
|
||||||
|
|
||||||
|
const CUSTOM_MODELS_STORAGE_KEY = 'zclaw-custom-models';
|
||||||
|
|
||||||
|
interface CustomModel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
apiKey?: string;
|
||||||
|
apiProtocol: 'openai' | 'anthropic' | 'custom';
|
||||||
|
baseUrl?: string;
|
||||||
|
isDefault?: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom models from localStorage
|
||||||
|
*/
|
||||||
|
function loadCustomModels(): CustomModel[] {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(CUSTOM_MODELS_STORAGE_KEY);
|
||||||
|
if (stored) {
|
||||||
|
return JSON.parse(stored);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[connectionStore] Failed to parse models:', err);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default model configuration
|
||||||
|
*
|
||||||
|
* Priority:
|
||||||
|
* 1. Model with isDefault: true
|
||||||
|
* 2. Model matching chatStore's currentModel
|
||||||
|
* 3. First model in the list
|
||||||
|
*/
|
||||||
|
export function getDefaultModelConfig(): { provider: string; model: string; apiKey: string; baseUrl: string; apiProtocol: string } | null {
|
||||||
|
const models = loadCustomModels();
|
||||||
|
|
||||||
|
// Priority 1: Find model with isDefault: true
|
||||||
|
let defaultModel = models.find(m => m.isDefault === true);
|
||||||
|
|
||||||
|
// Priority 2: Find model matching chatStore's currentModel
|
||||||
|
if (!defaultModel) {
|
||||||
|
try {
|
||||||
|
const chatStoreData = localStorage.getItem('zclaw-chat-storage');
|
||||||
|
if (chatStoreData) {
|
||||||
|
const parsed = JSON.parse(chatStoreData);
|
||||||
|
const currentModelId = parsed?.state?.currentModel;
|
||||||
|
if (currentModelId) {
|
||||||
|
defaultModel = models.find(m => m.id === currentModelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[connectionStore] Failed to read chatStore:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 3: First model
|
||||||
|
if (!defaultModel) {
|
||||||
|
defaultModel = models[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultModel) {
|
||||||
|
return {
|
||||||
|
provider: defaultModel.provider,
|
||||||
|
model: defaultModel.id,
|
||||||
|
apiKey: defaultModel.apiKey || '',
|
||||||
|
baseUrl: defaultModel.baseUrl || '',
|
||||||
|
apiProtocol: defaultModel.apiProtocol || 'openai',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// === Types ===
|
// === Types ===
|
||||||
|
|
||||||
export interface GatewayLog {
|
export interface GatewayLog {
|
||||||
@@ -56,21 +137,6 @@ function shouldRetryGatewayCandidate(error: unknown): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an error indicates local device pairing is required.
|
|
||||||
*/
|
|
||||||
function requiresLocalDevicePairing(error: unknown): boolean {
|
|
||||||
const message = error instanceof Error ? error.message : String(error || '');
|
|
||||||
return message.includes('pairing required');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a URL is a loopback address.
|
|
||||||
*/
|
|
||||||
function isLoopbackGatewayUrl(url: string): boolean {
|
|
||||||
return /^wss?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i.test(url.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize a gateway URL candidate.
|
* Normalize a gateway URL candidate.
|
||||||
*/
|
*/
|
||||||
@@ -78,36 +144,6 @@ function normalizeGatewayUrlCandidate(url: string): string {
|
|||||||
return url.trim().replace(/\/+$/, '');
|
return url.trim().replace(/\/+$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the local gateway connect URL from status.
|
|
||||||
*/
|
|
||||||
function getLocalGatewayConnectUrl(status: LocalGatewayStatus): string | null {
|
|
||||||
if (status.probeUrl && status.probeUrl.trim()) {
|
|
||||||
return normalizeGatewayUrlCandidate(status.probeUrl);
|
|
||||||
}
|
|
||||||
if (status.port) {
|
|
||||||
return `ws://127.0.0.1:${status.port}`;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to approve local device pairing for loopback URLs.
|
|
||||||
*/
|
|
||||||
async function approveCurrentLocalDevicePairing(url: string): Promise<boolean> {
|
|
||||||
if (!isTauriRuntime() || !isLoopbackGatewayUrl(url)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const identity = await getLocalDeviceIdentity();
|
|
||||||
const result = await approveLocalGatewayDevicePairing(identity.deviceId, identity.publicKeyBase64, url);
|
|
||||||
return result.approved;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Store Interface ===
|
// === Store Interface ===
|
||||||
|
|
||||||
export interface ConnectionStateSlice {
|
export interface ConnectionStateSlice {
|
||||||
@@ -133,17 +169,18 @@ export interface ConnectionActionsSlice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ConnectionStore extends ConnectionStateSlice, ConnectionActionsSlice {
|
export interface ConnectionStore extends ConnectionStateSlice, ConnectionActionsSlice {
|
||||||
client: GatewayClient;
|
client: GatewayClient | KernelClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Store Implementation ===
|
// === Store Implementation ===
|
||||||
|
|
||||||
export const useConnectionStore = create<ConnectionStore>((set, get) => {
|
export const useConnectionStore = create<ConnectionStore>((set, get) => {
|
||||||
// Initialize client
|
// Initialize with external gateway client by default.
|
||||||
const client = getGatewayClient();
|
// Will switch to internal kernel client at connect time if in Tauri.
|
||||||
|
const client: GatewayClient | KernelClient = getGatewayClient();
|
||||||
|
|
||||||
// Wire up state change callback
|
// Wire up state change callback
|
||||||
client.onStateChange = (state) => {
|
client.onStateChange = (state: ConnectionState) => {
|
||||||
set({ connectionState: state });
|
set({ connectionState: state });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,99 +207,107 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
|
|||||||
// === Actions ===
|
// === Actions ===
|
||||||
|
|
||||||
connect: async (url?: string, token?: string) => {
|
connect: async (url?: string, token?: string) => {
|
||||||
const c = get().client;
|
|
||||||
|
|
||||||
// Resolve connection URL candidates
|
|
||||||
const resolveCandidates = async (): Promise<string[]> => {
|
|
||||||
const explicitUrl = url?.trim();
|
|
||||||
if (explicitUrl) {
|
|
||||||
return [normalizeGatewayUrlCandidate(explicitUrl)];
|
|
||||||
}
|
|
||||||
|
|
||||||
const candidates: string[] = [];
|
|
||||||
|
|
||||||
// Check local gateway first if in Tauri
|
|
||||||
if (isTauriRuntime()) {
|
|
||||||
try {
|
|
||||||
const localStatus = await fetchLocalGatewayStatus();
|
|
||||||
const localUrl = getLocalGatewayConnectUrl(localStatus);
|
|
||||||
if (localUrl) {
|
|
||||||
candidates.push(localUrl);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* ignore local gateway lookup failures during candidate selection */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add quick config gateway URL if available
|
|
||||||
const quickConfigGatewayUrl = useConfigStore.getState().quickConfig?.gatewayUrl?.trim();
|
|
||||||
if (quickConfigGatewayUrl) {
|
|
||||||
candidates.push(quickConfigGatewayUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add stored URL, default, and fallbacks
|
|
||||||
candidates.push(
|
|
||||||
getStoredGatewayUrl(),
|
|
||||||
DEFAULT_GATEWAY_URL,
|
|
||||||
...FALLBACK_GATEWAY_URLS
|
|
||||||
);
|
|
||||||
|
|
||||||
// Return unique, non-empty candidates
|
|
||||||
return Array.from(
|
|
||||||
new Set(
|
|
||||||
candidates
|
|
||||||
.filter(Boolean)
|
|
||||||
.map(normalizeGatewayUrlCandidate)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
set({ error: null });
|
set({ error: null });
|
||||||
|
|
||||||
// Prepare local gateway for Tauri
|
// === Internal Kernel Mode (Tauri) ===
|
||||||
if (isTauriRuntime()) {
|
// Check at RUNTIME, not at module load time, to ensure __TAURI_INTERNALS__ is available
|
||||||
try {
|
const useInternalKernel = isTauriRuntime();
|
||||||
await prepareLocalGatewayForTauri();
|
console.log('[ConnectionStore] isTauriRuntime():', useInternalKernel);
|
||||||
} catch {
|
|
||||||
/* ignore local gateway preparation failures during connection bootstrap */
|
if (useInternalKernel) {
|
||||||
|
console.log('[ConnectionStore] Using internal ZCLAW Kernel (no external process needed)');
|
||||||
|
const kernelClient = getKernelClient();
|
||||||
|
|
||||||
|
// Get model config from custom models settings
|
||||||
|
const modelConfig = getDefaultModelConfig();
|
||||||
|
|
||||||
|
if (!modelConfig) {
|
||||||
|
throw new Error('请先在"模型与 API"设置页面添加自定义模型配置');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-start local gateway if not running
|
if (!modelConfig.apiKey) {
|
||||||
try {
|
throw new Error(`模型 ${modelConfig.model} 未配置 API Key,请在"模型与 API"设置页面配置`);
|
||||||
const localStatus = await fetchLocalGatewayStatus();
|
|
||||||
const isRunning = localStatus.portStatus === 'busy' || localStatus.listenerPids.length > 0;
|
|
||||||
|
|
||||||
if (!isRunning && localStatus.cliAvailable) {
|
|
||||||
console.log('[ConnectionStore] Local gateway not running, auto-starting...');
|
|
||||||
set({ localGatewayBusy: true });
|
|
||||||
await startLocalGatewayCommand();
|
|
||||||
set({ localGatewayBusy: false });
|
|
||||||
|
|
||||||
// Wait for gateway to be ready
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
||||||
console.log('[ConnectionStore] Local gateway started');
|
|
||||||
}
|
|
||||||
} catch (startError) {
|
|
||||||
console.warn('[ConnectionStore] Failed to auto-start local gateway:', startError);
|
|
||||||
set({ localGatewayBusy: false });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[ConnectionStore] Model config:', {
|
||||||
|
provider: modelConfig.provider,
|
||||||
|
model: modelConfig.model,
|
||||||
|
hasApiKey: !!modelConfig.apiKey,
|
||||||
|
baseUrl: modelConfig.baseUrl,
|
||||||
|
apiProtocol: modelConfig.apiProtocol,
|
||||||
|
});
|
||||||
|
|
||||||
|
kernelClient.setConfig({
|
||||||
|
provider: modelConfig.provider,
|
||||||
|
model: modelConfig.model,
|
||||||
|
apiKey: modelConfig.apiKey,
|
||||||
|
baseUrl: modelConfig.baseUrl,
|
||||||
|
apiProtocol: modelConfig.apiProtocol,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wire up state change callback
|
||||||
|
kernelClient.onStateChange = (state: ConnectionState) => {
|
||||||
|
set({ connectionState: state });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wire up log callback
|
||||||
|
kernelClient.onLog = (level, message) => {
|
||||||
|
set((s) => ({
|
||||||
|
logs: [...s.logs.slice(-99), { timestamp: Date.now(), level, message }],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the stored client reference
|
||||||
|
set({ client: kernelClient });
|
||||||
|
|
||||||
|
// Connect to internal kernel
|
||||||
|
await kernelClient.connect();
|
||||||
|
|
||||||
|
// Set version
|
||||||
|
set({ gatewayVersion: '0.2.0-internal' });
|
||||||
|
|
||||||
|
console.log('[ConnectionStore] Connected to internal ZCLAW Kernel');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve effective token: param > quickConfig > localStorage > local auth
|
// === External Gateway Mode (non-Tauri or fallback) ===
|
||||||
let effectiveToken = token || useConfigStore.getState().quickConfig?.gatewayToken || getStoredGatewayToken();
|
const c = get().client;
|
||||||
if (!effectiveToken && isTauriRuntime()) {
|
|
||||||
try {
|
|
||||||
const localAuth = await getLocalGatewayAuth();
|
|
||||||
if (localAuth.gatewayToken) {
|
|
||||||
effectiveToken = localAuth.gatewayToken;
|
|
||||||
setStoredGatewayToken(localAuth.gatewayToken);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* ignore local auth lookup failures during connection bootstrap */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Resolve connection URL candidates
|
||||||
|
const resolveCandidates = async (): Promise<string[]> => {
|
||||||
|
const explicitUrl = url?.trim();
|
||||||
|
if (explicitUrl) {
|
||||||
|
return [normalizeGatewayUrlCandidate(explicitUrl)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates: string[] = [];
|
||||||
|
|
||||||
|
// Add quick config gateway URL if available
|
||||||
|
const quickConfigGatewayUrl = useConfigStore.getState().quickConfig?.gatewayUrl?.trim();
|
||||||
|
if (quickConfigGatewayUrl) {
|
||||||
|
candidates.push(quickConfigGatewayUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add stored URL, default, and fallbacks
|
||||||
|
candidates.push(
|
||||||
|
getStoredGatewayUrl(),
|
||||||
|
DEFAULT_GATEWAY_URL,
|
||||||
|
...FALLBACK_GATEWAY_URLS
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return unique, non-empty candidates
|
||||||
|
return Array.from(
|
||||||
|
new Set(
|
||||||
|
candidates
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(normalizeGatewayUrlCandidate)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resolve effective token
|
||||||
|
const effectiveToken = token || useConfigStore.getState().quickConfig?.gatewayToken || getStoredGatewayToken();
|
||||||
console.log('[ConnectionStore] Connecting with token:', effectiveToken ? '[REDACTED]' : '(empty)');
|
console.log('[ConnectionStore] Connecting with token:', effectiveToken ? '[REDACTED]' : '(empty)');
|
||||||
|
|
||||||
const candidateUrls = await resolveCandidates();
|
const candidateUrls = await resolveCandidates();
|
||||||
@@ -282,20 +327,6 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
lastError = err;
|
lastError = err;
|
||||||
|
|
||||||
// Try device pairing if required
|
|
||||||
if (requiresLocalDevicePairing(err)) {
|
|
||||||
const approved = await approveCurrentLocalDevicePairing(candidateUrl);
|
|
||||||
if (approved) {
|
|
||||||
c.updateOptions({
|
|
||||||
url: candidateUrl,
|
|
||||||
token: effectiveToken,
|
|
||||||
});
|
|
||||||
await c.connect();
|
|
||||||
connectedUrl = candidateUrl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we should try next candidate
|
// Check if we should try next candidate
|
||||||
if (!shouldRetryGatewayCandidate(err)) {
|
if (!shouldRetryGatewayCandidate(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
Reference in New Issue
Block a user