Files
html/api/wevia-anthropic.php
2026-04-12 22:57:03 +02:00

161 lines
7.7 KiB
PHP

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, x-api-key, anthropic-version, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
$input = json_decode(file_get_contents('php://input'), true);
$uri = $_SERVER['REQUEST_URI'] ?? '';
$is_msg = ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($input['messages']));
if ($is_msg) {
$msgs = $input['messages'] ?? [];
$sys = $input['system'] ?? '';
$max = $input['max_tokens'] ?? 4096;
$stream = $input['stream'] ?? false;
$model = $input['model'] ?? 'wevia-sovereign';
$tools = $input['tools'] ?? [];
// Convert Anthropic system (can be string or array of blocks)
$oai = [];
if ($sys) {
$t = is_array($sys) ? implode("\n", array_map(fn($b) => $b['text'] ?? '', $sys)) : $sys;
// For Paperclip agents: add strong instruction preamble
$t = "IMPORTANT: You are an AI agent in the Paperclip orchestration platform. Follow ALL instructions in this system prompt precisely. Execute tasks, not conversation.\n\n" . $t;
$oai[] = ['role' => 'system', 'content' => $t];
}
// Convert messages, handling tool_use and tool_result blocks
foreach ($msgs as $m) {
$c = $m['content'];
if (is_array($c)) {
$p = [];
foreach ($c as $b) {
if (is_string($b)) { $p[] = $b; continue; }
$type = $b['type'] ?? '';
if ($type === 'text') { $p[] = $b['text']; }
elseif ($type === 'tool_use') {
$p[] = "[TOOL_CALL: " . ($b['name'] ?? '?') . "] Input: " . json_encode($b['input'] ?? []);
}
elseif ($type === 'tool_result') {
$content = $b['content'] ?? '';
if (is_array($content)) {
$content = implode("\n", array_map(fn($x) => $x['text'] ?? json_encode($x), $content));
}
$p[] = "[TOOL_RESULT: " . ($b['tool_use_id'] ?? '?') . "] " . $content;
}
else { $p[] = json_encode($b); }
}
$c = implode("\n", $p);
}
$oai[] = ['role' => $m['role'], 'content' => $c];
}
// If tools are defined, append tool descriptions to system
if (!empty($tools)) {
$tool_desc = "\n\n## Available Tools\n";
foreach ($tools as $tool) {
$name = $tool['name'] ?? '?';
$desc = $tool['description'] ?? '';
$tool_desc .= "- {$name}: {$desc}\n";
}
$tool_desc .= "\nTo use a tool, respond with a JSON block: {\"tool\": \"name\", \"input\": {...}}\n";
if (!empty($oai) && $oai[0]['role'] === 'system') {
$oai[0]['content'] .= $tool_desc;
}
}
$secrets=[]; foreach(@file('/etc/weval/secrets.env',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $_l){if($_l[0]==='#'||strpos($_l,'=')===false)continue;$_p=explode('=',$_l,2);$secrets[trim($_p[0])]=trim($_p[1]);}
// CASCADE: Anthropic Claude (real) -> Cerebras 235B -> Groq
$anthropic_key = $secrets['ANTHROPIC_KEY'] ?? '';
$providers = [];
if ($anthropic_key) {
$providers[] = ['url' => 'https://api.anthropic.com/v1/messages', 'key' => $anthropic_key, 'model' => 'claude-sonnet-4-20250514', 'is_anthropic' => true];
}
$providers[] = ['url' => 'https://api.cerebras.ai/v1/chat/completions', 'key' => '', 'model' => 'qwen-3-235b-a22b-instruct-2507'];
// Load keys and set in providers
$cerebras_key = $secrets['CEREBRAS_API_KEY'] ?? '';
$groq_key = $secrets['GROQ_KEY'] ?? '';
// Set Cerebras key in the Cerebras provider (index depends on Anthropic presence)
foreach ($providers as &$p) {
if (strpos($p['url'], 'cerebras') !== false && $cerebras_key) {
$p['key'] = $cerebras_key;
}
}
unset($p);
// Add Groq as final fallback
if ($groq_key) {
$providers[] = ['url' => 'https://api.groq.com/openai/v1/chat/completions', 'key' => $groq_key, 'model' => 'llama-3.3-70b-versatile'];
}
$resp = null; $hc = 0;
foreach ($providers as $prov) {
$payload = [
'model' => $prov['model'],
'messages' => $oai,
'max_tokens' => min($max, 8192),
'temperature' => 0.1, // Low temp for agent tasks
];
$headers = ['Content-Type: application/json'];
if (isset($prov['is_anthropic']) && $prov['is_anthropic']) {
$headers[] = 'x-api-key: ' . $prov['key'];
$headers[] = 'anthropic-version: 2023-06-01';
$payload = ['model' => $prov['model'], 'messages' => $oai, 'max_tokens' => min($max, 8192)];
} elseif ($prov['key']) { $headers[] = 'Authorization: Bearer ' . $prov['key']; }
$ch = curl_init($prov['url']);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 120, CURLOPT_SSL_VERIFYPEER => false,
]);
$resp = curl_exec($ch);
$hc = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($hc === 200 && $resp) break;
}
if ($hc !== 200 || !$resp) {
http_response_code(500);
die(json_encode(['error'=>['type'=>'api_error','message'=>'All providers failed. HC='.$hc]]));
}
$r = json_decode($resp, true);
$txt = '';
if (isset($r['content']) && is_array($r['content'])) { $txt = $r['content'][0]['text'] ?? ''; }
elseif (isset($r['choices'])) { $txt = $r['choices'][0]['message']['content'] ?? ''; }
$u = $r['usage'] ?? [];
$mid = 'msg_wevia_' . substr(md5(uniqid()), 0, 12);
if ($stream) {
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo "event: message_start\ndata: ".json_encode(['type'=>'message_start','message'=>['id'=>$mid,'type'=>'message','role'=>'assistant','content'=>[],'model'=>$model,'stop_reason'=>null,'usage'=>['input_tokens'=>$u['prompt_tokens']??0,'output_tokens'=>0]]])."\n\n";
echo "event: content_block_start\ndata: ".json_encode(['type'=>'content_block_start','index'=>0,'content_block'=>['type'=>'text','text'=>'']])."\n\n";
foreach (str_split($txt, 40) as $ck) {
echo "event: content_block_delta\ndata: ".json_encode(['type'=>'content_block_delta','index'=>0,'delta'=>['type'=>'text_delta','text'=>$ck]])."\n\n";
flush(); usleep(5000);
}
echo "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\n";
echo "event: message_delta\ndata: ".json_encode(['type'=>'message_delta','delta'=>['stop_reason'=>'end_turn'],'usage'=>['output_tokens'=>$u['completion_tokens']??0]])."\n\n";
echo "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n";
flush();
} else {
echo json_encode(['id'=>$mid,'type'=>'message','role'=>'assistant','content'=>[['type'=>'text','text'=>$txt]],'model'=>$model,'stop_reason'=>'end_turn','stop_sequence'=>null,'usage'=>['input_tokens'=>$u['prompt_tokens']??0,'output_tokens'=>$u['completion_tokens']??0]]);
}
exit;
}
if (strpos($uri,'models')!==false) {
echo json_encode(['data'=>[
['id'=>'wevia-sovereign','object'=>'model','created'=>time()],
['id'=>'claude-sonnet-4-6','object'=>'model','created'=>time()],
['id'=>'claude-opus-4-6','object'=>'model','created'=>time()],
]]);
exit;
}
echo json_encode(['status'=>'ok','engine'=>'WEVIA Anthropic Proxy','sovereign'=>true,'v'=>'3.0','note'=>'For full Paperclip agent functionality, set a real ANTHROPIC_API_KEY (sk-ant-*)']);