Files
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

111 lines
4.0 KiB
Dart

import 'dart:convert';
import '../http_util.dart';
import '../models.dart';
import 'webview_base_provider.dart';
/// Cloudflare Workers AI playground via an in-page WebSocket.
class CloudflareAi extends WebViewBaseProvider {
@override
String get name => 'CloudflareAI';
@override
bool get working => true;
@override
List<String> get models => [
'@cf/meta/llama-3.3-70b-instruct-fp8-fast',
'@cf/meta/llama-3.1-8b-instruct-fp8',
'@cf/google/gemma-3-12b-it',
];
@override
String get defaultModel => '@cf/meta/llama-3.3-70b-instruct-fp8-fast';
@override
String get baseUrl => 'https://playground.ai.cloudflare.com';
@override
String get apiEndpoint => 'wss://playground.ai.cloudflare.com/agents/playground';
@override
String buildJsFetchCode(String model, List<Message> messages, bool stream) {
final cfMessages = [
for (var i = 0; i < messages.length; i++)
{
'role': messages[i].role.name,
'parts': [
{'type': 'text', 'text': messages[i].content}
],
'id': 'msg_$i',
}
];
final messagesJson = jsonEncode(cfMessages);
final modelJson = jsonEncode(models.contains(model) ? model : defaultModel);
return '''
(async function() {
const modelStr = $modelJson;
const pk = crypto.randomUUID();
const agentId = 'playground-' + Math.random().toString(36).slice(2, 15);
const wsUrl = 'wss://playground.ai.cloudflare.com/agents/playground/' + agentId
+ '?_pk=' + pk + '&model=' + encodeURIComponent(modelStr);
return await new Promise((resolve, reject) => {
const ws = new WebSocket(wsUrl);
let text = '';
const timer = setTimeout(() => {
try { ws.close(); } catch (e) {}
if (text) resolve(text); else reject('Cloudflare timeout');
}, 28000);
ws.onopen = () => {
ws.send(JSON.stringify({type:'cf_agent_stream_resume_request'}));
ws.send(JSON.stringify({name:agentId, agent:'playground', type:'cf_agent_identity'}));
ws.send(JSON.stringify({state:{model:modelStr, temperature:1, stream:true, system:'You are a helpful assistant.', useExternalProvider:false, externalProvider:'openai', generativeUI:false}, type:'cf_agent_state'}));
ws.send(JSON.stringify({mcp:{prompts:[], resources:[], servers:{}, tools:[]}, type:'cf_agent_mcp_servers'}));
ws.send(JSON.stringify({type:'cf_agent_stream_resume_none'}));
setTimeout(() => {
ws.send(JSON.stringify({
id: 'req_' + Math.random().toString(36).slice(2, 10),
init: { method: 'POST', body: JSON.stringify({ messages: $messagesJson, trigger: 'submit-message' }) },
type: 'cf_agent_use_chat_request'
}));
}, 800);
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'cf_agent_use_chat_response') {
const body = JSON.parse(data.body);
if (body.type === 'text-delta' && body.delta) text += body.delta;
if (body.type === 'finish' || body.type === 'finish-step') {
clearTimeout(timer);
ws.close();
resolve(text);
}
}
} catch (e) {}
};
ws.onerror = () => { clearTimeout(timer); reject('Cloudflare WS error'); };
});
})();
''';
}
@override
String parseResponse(String rawResponse) {
final text = rawResponse.trim();
if (text.startsWith('{')) {
try {
final json = jsonDecode(text);
if (json is Map && json['error'] != null) {
throw Exception(json['error']);
}
} catch (e) {
if (e.toString().contains('Exception:')) rethrow;
}
}
if (!looksLikeModelOutput(text)) {
throw Exception('Cloudflare empty: ${text.substring(0, text.length.clamp(0, 160))}');
}
return text;
}
}