Rebuild and update project functionality

This commit is contained in:
Gemini AI
2025-12-26 13:13:36 +04:00
Unverified
parent 57576051b3
commit 6fc923c8ba
20 changed files with 375 additions and 396 deletions

View File

@@ -2,4 +2,6 @@ import ModelAdapter from "./model-adapter";
const adapter = new ModelAdapter();
adapter["qwenService"]["initialize"]?.();
export default adapter;

View File

@@ -70,6 +70,23 @@ export class ModelAdapter {
return this.qwenService.getTokenInfo();
}
hasQwenAuth(): boolean {
return this.qwenService.hasOAuthToken();
}
private isProviderAuthenticated(provider: ModelProvider): boolean {
switch (provider) {
case "qwen":
return this.hasQwenAuth() || this.qwenService.hasApiKey();
case "ollama":
return this.ollamaService.hasAuth();
case "zai":
return this.zaiService.hasAuth();
default:
return false;
}
}
private buildFallbackProviders(...providers: ModelProvider[]): ModelProvider[] {
const seen = new Set<ModelProvider>();
return providers.filter((provider) => {
@@ -85,13 +102,29 @@ export class ModelAdapter {
operation: (service: any) => Promise<APIResponse<T>>,
providers: ModelProvider[]
): Promise<APIResponse<T>> {
console.log("[ModelAdapter] Attempting providers in order:", providers);
let lastError: string | null = null;
for (const provider of providers) {
try {
console.log(`[ModelAdapter] Checking authentication for ${provider}...`);
if (!this.isProviderAuthenticated(provider)) {
console.log(`[ModelAdapter] Provider ${provider} is not authenticated, skipping`);
continue;
}
let service: any;
console.log(`[ModelAdapter] Trying provider: ${provider}`);
switch (provider) {
case "qwen":
service = this.qwenService;
console.log("[ModelAdapter] Qwen service:", {
hasApiKey: !!this.qwenService["apiKey"],
hasToken: !!this.qwenService.getTokenInfo()?.accessToken
});
break;
case "ollama":
service = this.ollamaService;
@@ -102,17 +135,30 @@ export class ModelAdapter {
}
const result = await operation(service);
console.log(`[ModelAdapter] Provider ${provider} result:`, result);
if (result.success) {
console.log(`[ModelAdapter] Success with provider: ${provider}`);
return result;
}
if (result.error) {
lastError = result.error;
}
} catch (error) {
console.error(`Error with ${provider}:`, error);
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[ModelAdapter] Error with ${provider}:`, errorMessage);
lastError = errorMessage || lastError;
}
}
const finalError = lastError
? `All providers failed: ${lastError}`
: "All providers failed. Please configure API key in Settings";
console.error(`[ModelAdapter] ${finalError}`);
return {
success: false,
error: "All providers failed",
error: finalError,
};
}

View File

@@ -53,6 +53,10 @@ export class OllamaCloudService {
};
}
hasAuth(): boolean {
return !!this.config.apiKey;
}
private ensureApiKey(): string {
if (this.config.apiKey) {
return this.config.apiKey;

View File

@@ -68,10 +68,20 @@ export class QwenOAuthService {
this.apiKey = apiKey;
}
hasApiKey(): boolean {
return !!this.apiKey;
}
hasOAuthToken(): boolean {
return !!this.getTokenInfo()?.accessToken;
}
/**
* Build default headers for Qwen completions (includes OAuth token refresh).
*/
private async getRequestHeaders(): Promise<Record<string, string>> {
console.log("[QwenOAuth] Getting request headers...");
const token = await this.getValidToken();
const headers: Record<string, string> = {
"Content-Type": "application/json",
@@ -79,14 +89,17 @@ export class QwenOAuthService {
if (token?.accessToken) {
headers["Authorization"] = `Bearer ${token.accessToken}`;
console.log("[QwenOAuth] Using OAuth token for authorization");
return headers;
}
if (this.apiKey) {
headers["Authorization"] = `Bearer ${this.apiKey}`;
console.log("[QwenOAuth] Using API key for authorization");
return headers;
}
console.error("[QwenOAuth] No OAuth token or API key available");
throw new Error("Please configure a Qwen API key or authenticate via OAuth.");
}
@@ -96,8 +109,11 @@ export class QwenOAuthService {
private getEffectiveEndpoint(): string {
const resourceUrl = this.token?.resourceUrl;
if (resourceUrl) {
return this.normalizeResourceUrl(resourceUrl);
const normalized = this.normalizeResourceUrl(resourceUrl);
console.log("[Qwen] Using resource URL:", normalized);
return normalized;
}
console.log("[Qwen] Using default endpoint:", this.endpoint);
return this.endpoint;
}
@@ -109,7 +125,12 @@ export class QwenOAuthService {
const withProtocol = trimmed.startsWith("http") ? trimmed : `https://${trimmed}`;
const cleaned = withProtocol.replace(/\/$/, "");
return cleaned.endsWith("/v1") ? cleaned : `${cleaned}/v1`;
if (cleaned.endsWith("/v1") || cleaned.endsWith("/compatible-mode/v1")) {
return cleaned;
}
return `${cleaned}/v1`;
}
private hydrateTokens() {
@@ -132,6 +153,7 @@ export class QwenOAuthService {
private getStoredToken(): QwenOAuthToken | null {
this.hydrateTokens();
console.log("[QwenOAuth] Retrieved stored token:", this.token ? { hasAccessToken: !!this.token.accessToken, expiresAt: this.token.expiresAt } : null);
return this.token;
}
@@ -229,8 +251,18 @@ export class QwenOAuthService {
this.storageHydrated = true;
}
/**
* Initialize the service and hydrate tokens from storage.
*/
initialize(): void {
console.log("[QwenOAuth] Initializing service...");
this.hydrateTokens();
}
getTokenInfo(): QwenOAuthToken | null {
return this.getStoredToken();
this.hydrateTokens();
console.log("[QwenOAuth] getTokenInfo called, returning:", this.token ? { hasAccessToken: !!this.token.accessToken, expiresAt: this.token.expiresAt } : null);
return this.token;
}
/**
@@ -353,12 +385,30 @@ export class QwenOAuthService {
}
private parseTokenResponse(data: any): QwenOAuthToken {
return {
console.log("[QwenOAuth] Token response received:", data);
const token: QwenOAuthToken = {
accessToken: data.access_token,
refreshToken: data.refresh_token,
resourceUrl: data.resource_url,
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
};
if (data.resource_url) {
token.resourceUrl = data.resource_url;
console.log("[QwenOAuth] Using resource_url from response:", data.resource_url);
} else if (data.endpoint) {
token.resourceUrl = data.endpoint;
console.log("[QwenOAuth] Using endpoint from response:", data.endpoint);
} else if (data.resource_server) {
token.resourceUrl = `https://${data.resource_server}/compatible-mode/v1`;
console.log("[QwenOAuth] Using resource_server from response:", data.resource_server);
} else {
console.log("[QwenOAuth] No resource_url/endpoint in response, will use default Qwen endpoint");
console.log("[QwenOAuth] Available fields in response:", Object.keys(data));
}
console.log("[QwenOAuth] Parsed token:", { hasAccessToken: !!token.accessToken, hasRefreshToken: !!token.refreshToken, hasResourceUrl: !!token.resourceUrl, expiresAt: token.expiresAt });
return token;
}
/**
@@ -395,12 +445,19 @@ export class QwenOAuthService {
): Promise<APIResponse<string>> {
try {
const headers = await this.getRequestHeaders();
const url = `${this.getEffectiveEndpoint()}/chat/completions`;
const baseUrl = this.getEffectiveEndpoint();
const url = `${this.oauthBaseUrl}/chat`;
console.log("[Qwen] Chat completion request:", { url, model, hasAuth: !!headers.Authorization });
const response = await fetch(url, {
method: "POST",
headers,
headers: {
"Content-Type": "application/json",
Authorization: headers.Authorization || "",
},
body: JSON.stringify({
endpoint: baseUrl,
model,
messages,
stream,
@@ -409,6 +466,7 @@ export class QwenOAuthService {
if (!response.ok) {
const errorText = await response.text();
console.error("[Qwen] Chat completion failed:", response.status, response.statusText, errorText);
throw new Error(`Chat completion failed (${response.status}): ${response.statusText} - ${errorText}`);
}

View File

@@ -17,6 +17,10 @@ export class ZaiPlanService {
};
}
hasAuth(): boolean {
return !!this.config.apiKey;
}
private getHeaders(): Record<string, string> {
return {
"Content-Type": "application/json",