221 lines
5.4 KiB
Dart
221 lines
5.4 KiB
Dart
// Request builder to construct Anthropic Message API requests
|
|
// Ported from old_repo/services/api/claude.ts
|
|
|
|
import "dart:io";
|
|
import "api_types.dart";
|
|
|
|
// builds a message api request with all the standard options
|
|
class MessageRequestBuilder {
|
|
final String model;
|
|
final int maxTokens;
|
|
final List<Map<String, dynamic>> messages;
|
|
|
|
String? _systemPrompt;
|
|
double? _temperature;
|
|
List<Map<String, dynamic>>? _tools;
|
|
String? _toolChoice;
|
|
Map<String, dynamic>? _metadata;
|
|
|
|
|
|
MessageRequestBuilder({
|
|
required this.model,
|
|
required this.maxTokens,
|
|
required this.messages,
|
|
});
|
|
|
|
MessageRequestBuilder withSystem(String system) {
|
|
_systemPrompt = system;
|
|
return this;
|
|
}
|
|
|
|
MessageRequestBuilder withTemperature(double temp) {
|
|
_temperature = temp;
|
|
return this;
|
|
}
|
|
|
|
MessageRequestBuilder withTools(List<Map<String, dynamic>> tools) {
|
|
_tools = tools;
|
|
return this;
|
|
}
|
|
|
|
MessageRequestBuilder withToolChoice(String choice) {
|
|
_toolChoice = choice;
|
|
return this;
|
|
}
|
|
|
|
MessageRequestBuilder withMetadata(Map<String, dynamic> metadata) {
|
|
_metadata = metadata;
|
|
return this;
|
|
}
|
|
|
|
MessageRequest build() {
|
|
return MessageRequest(
|
|
model: model,
|
|
maxTokens: maxTokens,
|
|
messages: messages,
|
|
systemPrompt: _systemPrompt,
|
|
temperature: _temperature,
|
|
tools: _tools,
|
|
toolChoice: _toolChoice,
|
|
metadata: _metadata,
|
|
);
|
|
}
|
|
}
|
|
|
|
// helpers to add headers for API requests
|
|
class HeaderBuilder {
|
|
final Map<String, String> _headers = {};
|
|
|
|
HeaderBuilder() {
|
|
_initializeDefaultHeaders();
|
|
}
|
|
|
|
void _initializeDefaultHeaders() {
|
|
// Add standard headers for API requests
|
|
final env = Platform.environment;
|
|
|
|
// Session tracking
|
|
if (env.containsKey("CLAUDE_CODE_SESSION_ID")) {
|
|
_headers["X-Claude-Code-Session-Id"] = env["CLAUDE_CODE_SESSION_ID"]!;
|
|
}
|
|
|
|
// Remote tracking (if in a container)
|
|
if (env.containsKey("CLAUDE_CODE_CONTAINER_ID")) {
|
|
_headers["x-claude-remote-container-id"] = env["CLAUDE_CODE_CONTAINER_ID"]!;
|
|
}
|
|
|
|
if (env.containsKey("CLAUDE_CODE_REMOTE_SESSION_ID")) {
|
|
_headers["x-claude-remote-session-id"] = env["CLAUDE_CODE_REMOTE_SESSION_ID"]!;
|
|
}
|
|
|
|
// App identifier
|
|
_headers["x-app"] = "cli";
|
|
|
|
// User agent from utils would go here (TODO when http client created)
|
|
_headers["User-Agent"] = "clawd_code/0.1.0";
|
|
}
|
|
|
|
void addCustomHeader(String name, String value) {
|
|
_headers[name] = value;
|
|
}
|
|
|
|
void addAuthHeader(String apiKey) {
|
|
_headers["Authorization"] = "Bearer $apiKey";
|
|
}
|
|
|
|
void addOpenRouterHeaders() {
|
|
_headers["HTTP-Referer"] = "clawd_code";
|
|
_headers["X-Title"] = "clawd_code";
|
|
}
|
|
|
|
// parse custom headers from env var (newline or semicolon separated)
|
|
void addCustomHeadersFromEnv() {
|
|
final env = Platform.environment;
|
|
final customHeadersEnv = env["ANTHROPIC_CUSTOM_HEADERS"];
|
|
|
|
if (customHeadersEnv == null || customHeadersEnv.isEmpty) return;
|
|
|
|
final headerStrings = customHeadersEnv.split(RegExp(r"\n|\r\n"));
|
|
|
|
for (final headerString in headerStrings) {
|
|
if (headerString.trim().isEmpty) continue;
|
|
|
|
// parse "Name: Value" format, split on first colon
|
|
final colonIdx = headerString.indexOf(":");
|
|
if (colonIdx == -1) continue;
|
|
|
|
final name = headerString.substring(0, colonIdx).trim();
|
|
final value = headerString.substring(colonIdx + 1).trim();
|
|
|
|
if (name.isNotEmpty) {
|
|
_headers[name] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
Map<String, String> build() {
|
|
return Map.unmodifiable(_headers);
|
|
}
|
|
}
|
|
|
|
// builds user and assistant message objects for the API
|
|
class MessageBuilder {
|
|
// create a user message
|
|
static Map<String, dynamic> createUserMessage(String content) {
|
|
return {
|
|
"role": "user",
|
|
"content": content,
|
|
};
|
|
}
|
|
|
|
// create a user message with mixed content (text + tool results)
|
|
static Map<String, dynamic> createUserMessageWithContent(
|
|
List<Map<String, dynamic>> contentBlocks,
|
|
) {
|
|
return {
|
|
"role": "user",
|
|
"content": contentBlocks,
|
|
};
|
|
}
|
|
|
|
// create assistant message with text content
|
|
static Map<String, dynamic> createAssistantMessage(String content) {
|
|
return {
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": content,
|
|
}
|
|
],
|
|
};
|
|
}
|
|
|
|
// create assistant message with tool use
|
|
static Map<String, dynamic> createAssistantMessageWithToolUse(
|
|
String toolId,
|
|
String toolName,
|
|
Map<String, dynamic> toolInput,
|
|
) {
|
|
return {
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"type": "tool_use",
|
|
"id": toolId,
|
|
"name": toolName,
|
|
"input": toolInput,
|
|
}
|
|
],
|
|
};
|
|
}
|
|
|
|
// add tool result to existing user message
|
|
static Map<String, dynamic> createToolResultContent(
|
|
String toolUseId,
|
|
String content,
|
|
) {
|
|
return {
|
|
"type": "tool_result",
|
|
"tool_use_id": toolUseId,
|
|
"content": content,
|
|
};
|
|
}
|
|
}
|
|
|
|
// normalize message content for sending to API
|
|
List<Map<String, dynamic>> normalizeMessagesForApi(
|
|
List<Map<String, dynamic>> messages,
|
|
) {
|
|
// basic validation and normalization
|
|
// in real implementation would handle various message formats
|
|
return messages;
|
|
}
|
|
|
|
// normalize content from api response
|
|
dynamic normalizeContentFromApi(dynamic content) {
|
|
if (content is! List) return content;
|
|
|
|
// ensure all blocks have proper types
|
|
return List.from(content);
|
|
}
|