complete method
Implementation
Future<Map<String, dynamic>> complete(
String instructions,
Map<String, dynamic> input,
) async {
final payload = jsonEncode(input);
final uri = switch (provider) {
AiProvider.openai => Uri.https('api.openai.com', '/v1/responses'),
AiProvider.claude => Uri.https('api.anthropic.com', '/v1/messages'),
AiProvider.gemini => Uri.https(
'generativelanguage.googleapis.com',
'/v1beta/models/$model:generateContent',
),
};
final body = switch (provider) {
AiProvider.openai => {
'model': model,
'store': false,
'instructions': instructions,
'input': payload,
'max_output_tokens': 8192,
'text': {
'format': {'type': 'json_object'},
},
},
AiProvider.claude => {
'model': model,
'max_tokens': 8192,
'system': instructions,
'messages': [
{'role': 'user', 'content': payload},
],
},
AiProvider.gemini => {
'systemInstruction': {
'parts': [
{'text': instructions},
],
},
'contents': [
{
'role': 'user',
'parts': [
{'text': payload},
],
},
],
'generationConfig': {
'responseMimeType': 'application/json',
'maxOutputTokens': 8192,
},
},
};
HttpClientRequest? request;
var expired = false;
Future<Map<String, dynamic>> send() async {
final outgoing = await _http.postUrl(uri);
request = outgoing;
if (expired) {
outgoing.abort();
throw AiException('Request timed out.');
}
outgoing.followRedirects = false;
outgoing.headers.contentType = ContentType.json;
if (provider == AiProvider.gemini) {
outgoing.headers.set('x-goog-api-key', _apiKey);
} else {
outgoing.headers.set('authorization', 'Bearer $_apiKey');
}
if (provider == AiProvider.claude) {
outgoing.headers.set('anthropic-version', '2026-10-21');
}
outgoing.write(jsonEncode(body));
final response = await outgoing.close();
if (response.statusCode != 200) {
throw AiException(
'${provider.name} returned HTTP ${response.statusCode}. Check the key, model access, quota, and provider status. No automatic retry was made.',
);
}
final bytes = <int>[];
await for (final chunk in response) {
if (bytes.length + chunk.length > 1024 * 1024) {
throw AiException('Provider response exceeded the 1 MiB limit.');
}
bytes.addAll(chunk);
}
final envelope = jsonDecode(utf8.decode(bytes));
if (envelope is! Map<String, dynamic>) throw const FormatException();
final text = _responseText(envelope);
final decoded = jsonDecode(text);
if (decoded is! Map<String, dynamic>) throw const FormatException();
return decoded;
}
try {
return await send().timeout(
timeout,
onTimeout: () {
expired = true;
request?.abort();
throw AiException(
'${provider.name} request timed out; no automatic retry was made.',
);
},
);
} on AiException {
rethrow;
} on IOException {
throw AiException(
'Could not reach ${provider.name}. Check your network connection.',
);
} catch (_) {
throw AiException(
'${provider.name} returned an invalid JSON response. No response content was logged.',
);
} finally {
request?.abort();
}
}