← 返回聊天
新建
删除
Models
gpt5.php
gpt5_file.php
gpt5_mini_file.php
openai_chat.php
Tools
get_time.php
get_weather.php
global_search_messages.php
math.php
memo.php
news.php
search_arxiv.php
search_crossref.php
search_github_code.php
search_pubmed.php
search_semantic_scholar.php
stock_market.php
url.php
<?php declare(strict_types=1); /** * GitHub Code Search Tool (REST API) * - Uses: GET https://api.github.com/search/code?q=... * - Optionally fetches file content via the item's "url" (contents API) to preview snippets * * IMPORTANT: * 1) Hardcoding token is risky. Prefer env vars in production. * 2) Token needs at least read access to target repos (and SSO authorized if org requires). */ return [ 'name' => 'search_github_code', 'description' => '在 GitHub 组织/仓库内搜索代码与文件(支持 repo/org/extension 过滤,可选返回代码片段预览)。', 'parameters' => [ 'type' => 'object', 'properties' => [ 'query' => ['type' => 'string', 'description' => '搜索关键词,例如:bioinformatics pipeline'], 'repository' => ['type' => 'string', 'description' => '限定仓库:owner/repo(可选)'], 'organization' => ['type' => 'string', 'description' => '限定组织:org(可选,repository 优先生效)'], 'file_type' => ['type' => 'string', 'description' => '限定扩展名:例如 py/js/md/R(可选)'], 'include_code_snippets' => ['type' => 'boolean', 'description' => '是否拉取文件内容预览(默认 true)', 'default' => true], 'max_results' => ['type' => 'integer', 'description' => '最多返回多少条(默认 5,建议 <= 10)', 'default' => 5], ], 'required' => ['query'], ], 'run' => function(array $args, array $context) { // ✅ 只保留一个 Token:直接写在代码里 // TODO: 把下面替换成你自己的 GitHub Token(建议最小权限) $GITHUB_TOKEN = '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'; $http_get = function(string $url, array $headers, int $timeoutSec = 15): array { $ch = curl_init($url); if ($ch === false) throw new RuntimeException('curl_init failed'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => $timeoutSec, CURLOPT_TIMEOUT => $timeoutSec, CURLOPT_HTTPHEADER => $headers, ]); $resp = curl_exec($ch); $errno = curl_errno($ch); $err = curl_error($ch); $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($errno !== 0) throw new RuntimeException('cURL error: ' . $err); $data = json_decode((string)$resp, true); // 有些错误响应也会是 JSON,但不保证结构一致 return [$status, $data, (string)$resp]; }; $emit = $context['event_emitter'] ?? null; $canEmit = is_callable($emit); $emitStatus = function(string $desc, bool $done = false) use ($emit, $canEmit) { if (!$canEmit) return; $emit([ 'type' => 'status', 'data' => [ 'description' => $desc, 'done' => $done, 'hidden' => false, ], ]); }; $query = trim((string)($args['query'] ?? '')); if ($query === '') throw new RuntimeException('query 不能为空'); $repository = trim((string)($args['repository'] ?? '')); $organization = trim((string)($args['organization'] ?? '')); $fileType = trim((string)($args['file_type'] ?? '')); $includeSnippets = (bool)($args['include_code_snippets'] ?? true); $maxResults = (int)($args['max_results'] ?? 5); if ($maxResults <= 0) $maxResults = 5; if ($maxResults > 20) $maxResults = 20; // 给你留安全上限,避免被限流/超时 if (trim($GITHUB_TOKEN) === '' || $GITHUB_TOKEN === 'PASTE_YOUR_GITHUB_TOKEN_HERE') { return "Error: GitHub token not configured. Please paste your GitHub Token into the tool code."; } $emitStatus('Connecting to GitHub...', false); // 组装 GitHub Search Query(与 Python 版本一致的 qualifier 思路) $searchQuery = $query; if ($repository !== '') { $searchQuery .= " repo:{$repository}"; } elseif ($organization !== '') { $searchQuery .= " org:{$organization}"; } if ($fileType !== '') { // extension:py / extension:md ... $searchQuery .= " extension:{$fileType}"; } $emitStatus('Searching repositories...', false); $apiUrl = 'https://api.github.com/search/code?' . http_build_query([ 'q' => $searchQuery, 'per_page' => min($maxResults, 100), 'page' => 1, ]); $headers = [ 'Accept: application/vnd.github+json', 'Authorization: Bearer ' . $GITHUB_TOKEN, 'User-Agent: php-tool/1.0', 'X-GitHub-Api-Version: 2022-11-28', ]; [$status, $json, $raw] = $http_get($apiUrl, $headers, 15); // 更友好的错误解释 if ($status === 401) { return "Error searching GitHub: Unauthorized (401). Please check your token is valid."; } if ($status === 403) { // 可能是 rate limit / SSO / 权限不足 $msg = is_array($json) ? ($json['message'] ?? '') : ''; if (stripos((string)$msg, 'rate limit') !== false) { return "Error searching GitHub: Rate limited (403). Try lowering max_results or retry later."; } return "Error searching GitHub: Forbidden (403).可能原因:Token 无权限/组织需要 SSO 授权/被限流。Message: " . ($msg ?: 'N/A'); } if ($status === 422) { $msg = is_array($json) ? ($json['message'] ?? '') : ''; return "Error searching GitHub: Validation Failed (422). Query 可能不合法。Message: " . ($msg ?: 'N/A'); } if ($status < 200 || $status >= 300) { $msg = is_array($json) ? ($json['message'] ?? '') : ''; return "Error searching GitHub: HTTP {$status}. " . ($msg ?: 'Unexpected response'); } $items = (is_array($json) && isset($json['items']) && is_array($json['items'])) ? $json['items'] : []; if (count($items) === 0) { $emitStatus('Done.', true); return "Found 0 results for '{$query}'.\n\n(Used query: {$searchQuery})"; } $found = []; $count = 0; foreach ($items as $item) { if (!is_array($item)) continue; $count++; if ($count > $maxResults) break; $path = (string)($item['path'] ?? 'N/A'); $htmlUrl = (string)($item['html_url'] ?? ''); $repoFull = (string)($item['repository']['full_name'] ?? 'N/A'); $contentApiUrl = (string)($item['url'] ?? ''); // 通常是 contents API $content = 'Content hidden'; $fullContentForCitation = ''; if ($includeSnippets && $contentApiUrl !== '') { // 拉文件内容(可能会被二次限流,或文件过大/二进制) [$s2, $j2, $raw2] = $http_get($contentApiUrl, $headers, 15); if ($s2 >= 200 && $s2 < 300 && is_array($j2)) { $enc = (string)($j2['encoding'] ?? ''); $b64 = (string)($j2['content'] ?? ''); if ($enc === 'base64' && $b64 !== '') { $decoded = base64_decode(str_replace("\n", "", $b64), true); if (is_string($decoded) && $decoded !== '') { $fullContentForCitation = $decoded; // 预览截断 $preview = $decoded; if (mb_strlen($preview, 'UTF-8') > 500) { $preview = mb_substr($preview, 0, 500, 'UTF-8') . '...'; } $content = $preview; } } } else { // 内容获取失败不致命:继续返回搜索结果 $content = 'Content hidden (failed to fetch)'; } } $found[] = [ 'file' => $path, 'repository' => $repoFull, 'url' => $htmlUrl, 'content' => $content, ]; // citation 事件(和你之前工具保持风格一致) if ($canEmit && $htmlUrl !== '') { $doc = $fullContentForCitation !== '' ? $fullContentForCitation : $path; // 避免 citation 文档过大:截到 4000 字符 if (is_string($doc) && mb_strlen($doc, 'UTF-8') > 4000) { $doc = mb_substr($doc, 0, 4000, 'UTF-8') . '...'; } $emit([ 'type' => 'citation', 'data' => [ 'document' => [$doc], 'metadata' => [[ 'date_accessed' => date('c'), 'source' => $path, ]], 'source' => [ 'name' => "{$repoFull}/{$path}", 'url' => $htmlUrl, ], ], ]); } } $emitStatus('Done.', true); // 输出格式:和你 Python 版类似 $resp = "Found " . count($found) . " results for '{$query}':\n\n"; foreach ($found as $idx => $it) { $n = $idx + 1; $resp .= "{$n}. File: {$it['file']}\n"; $resp .= " Repository: {$it['repository']}\n"; $resp .= " URL: {$it['url']}\n"; if ($includeSnippets) { $resp .= " Preview:\n```\n{$it['content']}\n```\n"; } $resp .= "\n"; } return $resp; }, ];
保存