The-Agency/lib/src/api/response_parser.dart

196 lines
5.8 KiB
Dart

// Response parser for Anthropic Message API responses
// Ported from old_repo/services/api/errors.ts and claude.ts
import "api_types.dart";
// Parse Message API response into ApiMessage model
class ResponseParser {
static ApiMessage parseMessageResponse(Map<String, dynamic> json) {
return ApiMessage.fromJson(json);
}
static ApiMessage parseOpenRouterResponse(Map<String, dynamic> json) {
return ApiMessage.fromOpenRouterResponse(json);
}
// extract text content from message
static String extractTextContent(ApiMessage message) {
final textBlocks = <String>[];
for (final block in message.content) {
if (block is Map<String, dynamic>) {
final type = block["type"];
if (type == "text") {
final text = block["text"];
if (text is String) {
textBlocks.add(text);
}
}
}
}
return textBlocks.join("\n");
}
// extract all tool use blocks from message
static List<ToolUse> extractToolUseBlocks(ApiMessage message) {
final tools = <ToolUse>[];
for (final block in message.content) {
if (block is Map<String, dynamic>) {
final type = block["type"];
if (type == "tool_use") {
tools.add(ToolUse.fromJson(block));
}
}
}
return tools;
}
// check if message is a tool use (or contains only tool use)
static bool hasToolUse(ApiMessage message) {
return message.content.any((block) {
return block is Map<String, dynamic> && block["type"] == "tool_use";
});
}
// check stop reason
static bool didStopOnToolUse(ApiMessage message) {
return message.stopReason == "tool_use";
}
static bool didStopOnMaxTokens(ApiMessage message) {
return message.stopReason == "max_tokens";
}
static bool didCompleteNormally(ApiMessage message) {
return message.stopReason == "end_turn";
}
}
// Parse error responses from the API
class ErrorParser {
// check if raw API error is authentication related
static bool isAuthenticationError(String errorMessage) {
final lower = errorMessage.toLowerCase();
return lower.contains("unauthorized") ||
lower.contains("authentication") ||
lower.contains("invalid api key") ||
lower.contains("missing authentication");
}
// check if error is rate limit related
static bool isRateLimitError(String errorMessage) {
final lower = errorMessage.toLowerCase();
return lower.contains("rate limit") ||
lower.contains("too many requests") ||
lower.contains("quota");
}
// check if error is related to prompt being too long
static bool isPromptTooLongError(String errorMessage) {
final lower = errorMessage.toLowerCase();
return lower.contains("prompt is too long") ||
lower.contains("context_length_exceeded");
}
// check if error is media/content related
static bool isMediaSizeError(String errorMessage) {
final lower = errorMessage.toLowerCase();
return (lower.contains("image exceeds") && lower.contains("maximum")) ||
(lower.contains("image dimensions exceed") &&
lower.contains("many-image")) ||
RegExp(
r"maximum of \d+ pdf pages",
caseSensitive: false,
).hasMatch(errorMessage);
}
// parse prompt too long error to extract token counts
static ({int? actualTokens, int? limitTokens}) parsePromptTooLongError(
String rawMessage,
) {
final match = RegExp(
r"prompt is too long[^0-9]*(\d+)\s*tokens?\s*>\s*(\d+)",
caseSensitive: false,
).firstMatch(rawMessage);
return (
actualTokens: match != null ? int.tryParse(match.group(1)!) : null,
limitTokens: match != null ? int.tryParse(match.group(2)!) : null,
);
}
// extract server error message from API response
static String? extractErrorMessage(Map<String, dynamic>? errorJson) {
if (errorJson == null) return null;
final nestedError = errorJson["error"];
if (nestedError is Map<String, dynamic>) {
final nestedMessage = nestedError["message"];
if (nestedMessage is String && nestedMessage.isNotEmpty) {
return nestedMessage;
}
}
// try common error message fields
return errorJson["message"] as String? ??
errorJson["error"] as String? ??
errorJson["detail"] as String?;
}
}
// Streaming response parser for handling streamed API responses
class StreamingResponseParser {
// parse a streamed event from newline-delimited JSON
static Map<String, dynamic>? parseStreamLine(String line) {
if (line.trim().isEmpty) return null;
try {
// handle SSE format (data: {...})
final data = line.startsWith("data: ") ? line.substring(6) : line;
// simple JSON parsing - in production would use json.decode
return _parseJson(data);
} catch (_) {
return null;
}
}
static Map<String, dynamic>? _parseJson(String jsonStr) {
// stubbed - would use dart:convert.jsonDecode in real impl
// for now just return null to indicate parsing would happen
return null;
}
// check if streamed event is a message delta (partial response)
static bool isMessageDelta(Map<String, dynamic>? event) {
if (event == null) return false;
final type = event["type"];
return type == "content_block_delta";
}
// check if streamed event marks message completion
static bool isMessageStop(Map<String, dynamic>? event) {
if (event == null) return false;
final type = event["type"];
return type == "message_stop";
}
// extract partial text from delta event
static String? extractDeltaText(Map<String, dynamic>? event) {
if (event == null) return null;
try {
final delta = event["delta"] as Map<String, dynamic>?;
if (delta == null) return null;
final type = delta["type"];
if (type == "text_delta") {
return delta["text"] as String?;
}
} catch (_) {}
return null;
}
}