Files
999/lib/g4f/providers/openai_compat.dart
T
Alma Alexyz fbf04d90db feat: embed a Flutter G4F client with Cronet, cookie jar, and origin WebView
Port the Oracle off the Python g4f container onto on-device providers: Chromium HTTP via Cronet, a shared cookie session, IterList retry, hosted OpenAI-compat aggregators, and same-origin WebView fallbacks instead of CORS fetch.
2026-09-01 22:05:38 +05:00

91 lines
2.6 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../http_client.dart';
import '../http_util.dart';
import '../models.dart';
import '../sse.dart';
import 'base_provider.dart';
/// OpenAI-compatible `/v1/chat/completions` client used by several free proxies.
class OpenAiCompatProvider extends BaseProvider {
OpenAiCompatProvider({
required this.providerName,
required this.apiBase,
required this.fallbackModel,
List<String>? supportedModels,
this.apiKey,
this.extraHeaders = const {},
}) : supportedModels = supportedModels ?? [fallbackModel];
final String providerName;
final String apiBase;
final String fallbackModel;
final List<String> supportedModels;
final String? apiKey;
final Map<String, String> extraHeaders;
@override
String get name => providerName;
@override
bool get working => true;
@override
List<String> get models => supportedModels;
@override
String get defaultModel => fallbackModel;
String get chatUrl {
final base = apiBase.endsWith('/') ? apiBase.substring(0, apiBase.length - 1) : apiBase;
if (base.endsWith('/chat/completions')) return base;
return '$base/chat/completions';
}
@override
Stream<String> createAsyncGenerator(
String model,
List<Message> messages, {
bool stream = false,
}) async* {
final payload = <String, dynamic>{
'model': models.contains(model) ? model : defaultModel,
'messages': toOpenAiMessages(messages),
'stream': false,
'temperature': 0.8,
};
final headers = browserHeaders(
contentType: 'application/json',
accept: 'application/json',
extra: {
if (apiKey != null && apiKey!.isNotEmpty) 'Authorization': 'Bearer $apiKey',
...extraHeaders,
},
);
final req = http.Request('POST', Uri.parse(chatUrl));
req.headers.addAll(headers);
req.body = jsonEncode(payload);
final response = await g4fHttp.send(req).timeout(requestTimeout);
if (response.statusCode < 200 || response.statusCode >= 300) {
final err = await response.stream.bytesToString();
throw Exception(
'$name returned ${response.statusCode}: ${err.substring(0, err.length.clamp(0, 240))}',
);
}
final body = utf8.decode(await response.stream.toBytes());
final fromJson = extractOpenAiMessage(body);
final fromSse = extractAllSseContent(body).join();
final text = (fromJson != null && looksLikeModelOutput(fromJson))
? fromJson
: fromSse;
if (!looksLikeModelOutput(text)) {
throw Exception('$name returned unusable body');
}
yield text;
}
}