diff --git a/claude_setup.sh b/claude_setup.sh index ede37f9..4879e5d 100755 --- a/claude_setup.sh +++ b/claude_setup.sh @@ -178,464 +178,7 @@ PYEOF chmod +x "$EFFORT_PROXY_BIN" success "claude-gpt-effort-proxy -> $EFFORT_PROXY_BIN" -# ── 4c. openai-anthropic-proxy (Kimi) ────────────────────── -OPENAI_ANTHROPIC_PROXY_BIN="$BIN_DIR/claude-openai-anthropic-proxy.py" -cat > "$OPENAI_ANTHROPIC_PROXY_BIN" << 'PYEOF' -#!/usr/bin/env python3 -"""Small Anthropic Messages → OpenAI Chat Completions proxy.""" -import argparse -import http.client -import http.server -import json -import os -import socketserver -import sys -import urllib.parse - - -def _json_dumps(obj): - return json.dumps(obj, ensure_ascii=False, separators=(",", ":")) - - -def _read_json(handler): - length = int(handler.headers.get("Content-Length", "0")) - if length <= 0: - return {} - return json.loads(handler.rfile.read(length).decode("utf-8")) - - -def _system_to_text(system): - if isinstance(system, str): - return system - if isinstance(system, list): - parts = [] - for item in system: - if isinstance(item, dict) and item.get("type") == "text": - parts.append(item.get("text", "")) - elif isinstance(item, str): - parts.append(item) - return "\n".join(p for p in parts if p) - return "" - - -def _content_to_openai(content): - if isinstance(content, str): - return content - if not isinstance(content, list): - return str(content) - - text_parts = [] - for block in content: - if not isinstance(block, dict): - text_parts.append(str(block)) - continue - btype = block.get("type") - if btype == "text": - text_parts.append(block.get("text", "")) - elif btype == "tool_result": - value = block.get("content", "") - if isinstance(value, list): - value = "\n".join( - part.get("text", "") if isinstance(part, dict) else str(part) - for part in value - ) - text_parts.append(str(value)) - return "\n".join(p for p in text_parts if p) - - -def _messages_to_openai(body): - messages = [] - system = _system_to_text(body.get("system")) - if system: - messages.append({"role": "system", "content": system}) - - pending_tool_results = [] - for msg in body.get("messages", []): - role = msg.get("role", "user") - content = msg.get("content", "") - if isinstance(content, list) and role == "user": - normal_blocks = [] - for block in content: - if isinstance(block, dict) and block.get("type") == "tool_result": - tool_content = block.get("content", "") - if isinstance(tool_content, list): - tool_content = _content_to_openai(tool_content) - pending_tool_results.append({ - "role": "tool", - "tool_call_id": block.get("tool_use_id", "tool_call"), - "content": str(tool_content), - }) - else: - normal_blocks.append(block) - if normal_blocks: - messages.append({"role": "user", "content": _content_to_openai(normal_blocks)}) - messages.extend(pending_tool_results) - pending_tool_results = [] - continue - - converted = {"role": role, "content": _content_to_openai(content)} - if isinstance(content, list) and role == "assistant": - tool_calls = [] - for block in content: - if isinstance(block, dict) and block.get("type") == "tool_use": - tool_calls.append({ - "id": block.get("id", "tool_call"), - "type": "function", - "function": { - "name": block.get("name", "tool"), - "arguments": _json_dumps(block.get("input", {})), - }, - }) - if tool_calls: - converted["tool_calls"] = tool_calls - messages.append(converted) - return messages - - -def _tools_to_openai(tools): - result = [] - for tool in tools or []: - result.append({ - "type": "function", - "function": { - "name": tool.get("name", "tool"), - "description": tool.get("description", ""), - "parameters": tool.get("input_schema", {"type": "object", "properties": {}}), - }, - }) - return result - - -def _tool_choice_to_openai(choice): - if not choice: - return None - if isinstance(choice, str): - return choice - ctype = choice.get("type") - if ctype == "auto": - return "auto" - if ctype == "any": - return "required" - if ctype == "tool": - return {"type": "function", "function": {"name": choice.get("name", "")}} - return None - - -def _to_openai_request(body, model): - out = { - "model": model or body.get("model"), - "messages": _messages_to_openai(body), - "stream": bool(body.get("stream")), - } - if "max_tokens" in body: - out["max_tokens"] = body["max_tokens"] - for key in ("temperature", "top_p"): - if key in body: - out[key] = body[key] - if body.get("stop_sequences"): - out["stop"] = body["stop_sequences"] - tools = _tools_to_openai(body.get("tools")) - if tools: - out["tools"] = tools - choice = _tool_choice_to_openai(body.get("tool_choice")) - if choice: - out["tool_choice"] = choice - return out - - -def _finish_reason(reason): - return { - "stop": "end_turn", - "length": "max_tokens", - "tool_calls": "tool_use", - "content_filter": "stop_sequence", - }.get(reason or "stop", "end_turn") - - -def _anthropic_response(openai_body, model): - choice = (openai_body.get("choices") or [{}])[0] - message = choice.get("message") or {} - content = [] - if message.get("content"): - content.append({"type": "text", "text": message.get("content")}) - for call in message.get("tool_calls") or []: - fn = call.get("function") or {} - raw_args = fn.get("arguments") or "{}" - try: - args = json.loads(raw_args) - except Exception: - args = {"arguments": raw_args} - content.append({ - "type": "tool_use", - "id": call.get("id", "tool_call"), - "name": fn.get("name", "tool"), - "input": args, - }) - usage = openai_body.get("usage") or {} - return { - "id": openai_body.get("id", "msg_openai_proxy"), - "type": "message", - "role": "assistant", - "model": model, - "content": content or [{"type": "text", "text": ""}], - "stop_reason": _finish_reason(choice.get("finish_reason")), - "stop_sequence": None, - "usage": { - "input_tokens": usage.get("prompt_tokens", 0), - "output_tokens": usage.get("completion_tokens", 0), - }, - } - - -def _write_json(handler, status, obj): - data = _json_dumps(obj).encode("utf-8") - handler.send_response(status) - handler.send_header("Content-Type", "application/json") - handler.send_header("Content-Length", str(len(data))) - handler.end_headers() - handler.wfile.write(data) - - -def _openai_error_to_anthropic(status, raw): - try: - data = json.loads(raw.decode("utf-8")) - except Exception: - data = {"error": raw.decode("utf-8", "replace")} - err = data.get("error", data) - if isinstance(err, dict): - message = err.get("message") or _json_dumps(err) - etype = err.get("type") or "api_error" - else: - message = str(err) - etype = "api_error" - return {"type": "error", "error": {"type": etype, "message": message}} - - -class _ThreadedServer(socketserver.ThreadingMixIn, http.server.HTTPServer): - daemon_threads = True - - -class Proxy(http.server.BaseHTTPRequestHandler): - upstream_base = "" - api_key = "" - model = "" - - def log_message(self, *args): - pass - - def do_GET(self): - path = urllib.parse.urlparse(self.path).path - if path in ("/", "/health"): - _write_json(self, 200, {"ok": True}) - else: - _write_json(self, 404, {"type": "error", "error": {"message": "not found"}}) - - def do_POST(self): - path = urllib.parse.urlparse(self.path).path - if not path.endswith("/messages"): - _write_json(self, 404, {"type": "error", "error": {"message": "not found"}}) - return - try: - body = _read_json(self) - openai_body = _to_openai_request(body, self.model) - if openai_body.get("stream"): - self._proxy_stream(openai_body) - else: - self._proxy_json(openai_body) - except Exception as exc: - _write_json(self, 500, {"type": "error", "error": {"type": "proxy_error", "message": str(exc)}}) - - def _request_upstream(self, payload): - parsed = urllib.parse.urlparse(self.upstream_base.rstrip("/") + "/chat/completions") - conn_cls = http.client.HTTPSConnection if parsed.scheme == "https" else http.client.HTTPConnection - conn = conn_cls(parsed.netloc, timeout=600) - path = parsed.path or "/chat/completions" - if parsed.query: - path += "?" + parsed.query - conn.request( - "POST", - path, - body=_json_dumps(payload).encode("utf-8"), - headers={ - "Authorization": "Bearer " + self.api_key, - "Content-Type": "application/json", - }, - ) - return conn, conn.getresponse() - - def _proxy_json(self, payload): - conn, resp = self._request_upstream(payload) - raw = resp.read() - conn.close() - if resp.status >= 400: - _write_json(self, resp.status, _openai_error_to_anthropic(resp.status, raw)) - return - _write_json(self, resp.status, _anthropic_response(json.loads(raw.decode("utf-8")), self.model)) - - def _sse(self, event, data): - self.wfile.write(("event: " + event + "\n").encode("utf-8")) - self.wfile.write(("data: " + _json_dumps(data) + "\n\n").encode("utf-8")) - self.wfile.flush() - - def _proxy_stream(self, payload): - conn, resp = self._request_upstream(payload) - if resp.status >= 400: - raw = resp.read() - conn.close() - _write_json(self, resp.status, _openai_error_to_anthropic(resp.status, raw)) - return - - self.send_response(200) - self.send_header("Content-Type", "text/event-stream") - self.send_header("Cache-Control", "no-cache") - self.end_headers() - - msg_id = "msg_openai_proxy" - input_tokens = 0 - output_tokens = 0 - finish = "end_turn" - text_started = False - text_index = 0 - tool_indexes = {} - tool_buffers = {} - next_index = 0 - - self._sse("message_start", { - "type": "message_start", - "message": { - "id": msg_id, - "type": "message", - "role": "assistant", - "model": self.model, - "content": [], - "stop_reason": None, - "stop_sequence": None, - "usage": {"input_tokens": 0, "output_tokens": 0}, - }, - }) - self._sse("ping", {"type": "ping"}) - - while True: - line = resp.readline() - if not line: - break - line = line.decode("utf-8", "replace").strip() - if not line or not line.startswith("data:"): - continue - data = line[5:].strip() - if data == "[DONE]": - break - try: - chunk = json.loads(data) - except Exception: - continue - usage = chunk.get("usage") or {} - input_tokens = usage.get("prompt_tokens", input_tokens) - output_tokens = usage.get("completion_tokens", output_tokens) - choice = (chunk.get("choices") or [{}])[0] - if choice.get("finish_reason"): - finish = _finish_reason(choice.get("finish_reason")) - delta = choice.get("delta") or {} - text = delta.get("content") - if text: - if not text_started: - text_started = True - text_index = next_index - next_index += 1 - self._sse("content_block_start", { - "type": "content_block_start", - "index": text_index, - "content_block": {"type": "text", "text": ""}, - }) - self._sse("content_block_delta", { - "type": "content_block_delta", - "index": text_index, - "delta": {"type": "text_delta", "text": text}, - }) - for call in delta.get("tool_calls") or []: - cidx = call.get("index", 0) - if cidx not in tool_indexes: - tool_indexes[cidx] = next_index - next_index += 1 - tool_buffers[cidx] = {"id": call.get("id", "tool_call"), "name": "tool", "args": ""} - buf = tool_buffers[cidx] - if call.get("id"): - buf["id"] = call["id"] - fn = call.get("function") or {} - if fn.get("name"): - buf["name"] = fn["name"] - if fn.get("arguments"): - buf["args"] += fn["arguments"] - conn.close() - - # Fallback: если модель потратила все токены на reasoning и не выдала - # ни одного content-блока, отправляем пустой текстовый блок (Anthropic - # требует минимум один content block в ответе). - if not text_started and not tool_indexes: - text_started = True - text_index = next_index - next_index += 1 - self._sse("content_block_start", { - "type": "content_block_start", - "index": text_index, - "content_block": {"type": "text", "text": ""}, - }) - self._sse("content_block_delta", { - "type": "content_block_delta", - "index": text_index, - "delta": {"type": "text_delta", "text": ""}, - }) - - if text_started: - self._sse("content_block_stop", {"type": "content_block_stop", "index": text_index}) - for cidx, index in sorted(tool_indexes.items(), key=lambda item: item[1]): - buf = tool_buffers[cidx] - try: - parsed_args = json.loads(buf["args"] or "{}") - except Exception: - parsed_args = {"arguments": buf["args"]} - self._sse("content_block_start", { - "type": "content_block_start", - "index": index, - "content_block": { - "type": "tool_use", - "id": buf["id"], - "name": buf["name"], - "input": parsed_args, - }, - }) - self._sse("content_block_stop", {"type": "content_block_stop", "index": index}) - self._sse("message_delta", { - "type": "message_delta", - "delta": {"stop_reason": finish, "stop_sequence": None}, - "usage": {"output_tokens": output_tokens}, - }) - self._sse("message_stop", {"type": "message_stop"}) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--host", default="127.0.0.1") - parser.add_argument("--port", type=int, default=int(os.environ.get("OPENAI_ANTHROPIC_PROXY_PORT", "18767"))) - args = parser.parse_args() - - Proxy.upstream_base = os.environ.get("OPENAI_BASE_URL", "https://api.artemox.com/v1") - Proxy.api_key = os.environ.get("OPENAI_API_KEY", "") - Proxy.model = os.environ.get("OPENAI_MODEL", "kimi-k2.6") - if not Proxy.api_key: - print("OPENAI_API_KEY is required", file=sys.stderr) - return 1 - _ThreadedServer((args.host, args.port), Proxy).serve_forever() - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) -PYEOF -chmod +x "$OPENAI_ANTHROPIC_PROXY_BIN" -success "claude-openai-anthropic-proxy -> $OPENAI_ANTHROPIC_PROXY_BIN" - -# ── 5. antigravity-claude-proxy (Gemini) ──────────────────── +# ── 4c. antigravity-claude-proxy (Gemini) ──────────────────── info "Проверяю antigravity-claude-proxy..." if command -v antigravity-claude-proxy &>/dev/null || command -v acc &>/dev/null; then success "antigravity-claude-proxy уже установлен" @@ -869,8 +412,14 @@ codex_bin="$HOME/.npm-global/bin/codex" [ ! -f "$codex_bin" ] && codex_bin="$(command -v codex 2>/dev/null)" if [ -z "$codex_bin" ] || [ ! -f "$codex_bin" ]; then - echo "Ошибка: OpenAI Codex не найден." - echo "Установите: npm install -g @openai/codex" + echo "OpenAI Codex не найден. Устанавливаю..." + curl -fsSL https://chatgpt.com/codex/install.sh | sh + codex_bin="$HOME/.npm-global/bin/codex" + [ ! -f "$codex_bin" ] && codex_bin="$(command -v codex 2>/dev/null)" +fi + +if [ -z "$codex_bin" ] || [ ! -f "$codex_bin" ]; then + echo "Ошибка: не удалось установить OpenAI Codex." exit 1 fi @@ -951,92 +500,24 @@ chmod +x "$BIN_DIR/claude_deepseek" # === claude_kimi === cat > "$BIN_DIR/claude_kimi" << 'KIMIEOF' #!/usr/bin/env bash -source ~/.local/bin/claude_api_helpers.sh +# claude_kimi — запуск нативного Kimi Code -key_file="$HOME/.config/claude-launcher/kimi_key" -api_key="" -reauth=0 +kimi_bin="$HOME/.kimi-code/bin/kimi" +[ ! -f "$kimi_bin" ] && kimi_bin="$(command -v kimi 2>/dev/null)" -[ -f "$key_file" ] && api_key=$(cat "$key_file") - -if [ -n "$api_key" ]; then - echo -n "Проверка сохранённого Kimi ключа... " - _claude_test_openai_api "https://api.artemox.com/v1/chat/completions" "$api_key" "kimi-k2.6" - _handle_openai_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://artemox.com/dashboard" - ret=$? - if [ $ret -eq 401 ]; then - rm -f "$key_file" - api_key="" - reauth=1 - elif [ $ret -eq 429 ]; then - echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " - read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac - elif [ $ret -ne 0 ]; then - exit 1 - fi +if [ -z "$kimi_bin" ] || [ ! -f "$kimi_bin" ]; then + echo "Kimi Code не найден. Устанавливаю..." + curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash + kimi_bin="$HOME/.kimi-code/bin/kimi" + [ ! -f "$kimi_bin" ] && kimi_bin="$(command -v kimi 2>/dev/null)" fi -if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then - echo -n "Хотите ввести новый Kimi ключ? [Y/n] " - read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac -fi - -if [ -z "$api_key" ]; then - echo "Получить ключ: https://artemox.com/dashboard" - read -r -p "Введите ваш Artemox API ключ: " api_key - [ -z "$api_key" ] && { echo "Выход."; exit 1; } - - echo -n "Проверяю ключ и баланс... " - _claude_test_openai_api "https://api.artemox.com/v1/chat/completions" "$api_key" "kimi-k2.6" - _handle_openai_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://artemox.com/dashboard" - ret=$? - if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then - mkdir -p "$(dirname "$key_file")" - echo "$api_key" > "$key_file" - chmod 600 "$key_file" - echo "Ключ сохранён." - if [ $ret -eq 429 ]; then - echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " - read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac - fi - else - echo "Ключ НЕ сохранён." - exit 1 - fi -fi - -# Запускаем прокси -proxy_bin="$HOME/.local/bin/claude-openai-anthropic-proxy.py" -proxy_port=18767 - -proxy_pid="" -cleanup() { [ -n "$proxy_pid" ] && kill "$proxy_pid" 2>/dev/null; } -trap cleanup EXIT INT TERM - -OPENAI_API_KEY="$api_key" \ -OPENAI_MODEL=kimi-k2.6 \ -python3 "$proxy_bin" --port "$proxy_port" &>/tmp/claude-openai-anthropic-proxy.log & -proxy_pid=$! - -echo -n "Запуск Kimi прокси... " -_i=0; while [ $_i -lt 10 ]; do sleep 1; curl -s --max-time 1 "http://localhost:$proxy_port/" &>/dev/null; [ "$?" -ne 7 ] && break; _i=$((_i + 1)); done -if [ $_i -ge 10 ]; then - echo -e "\033[0;31m[ОШИБКА]\033[0m Kimi прокси не запустился за 10 сек." - echo "Лог прокси:" - cat /tmp/claude-openai-anthropic-proxy.log 2>/dev/null +if [ -z "$kimi_bin" ] || [ ! -f "$kimi_bin" ]; then + echo "Ошибка: не удалось установить Kimi Code." exit 1 fi -echo -e "\033[0;32mOK\033[0m" -ANTHROPIC_BASE_URL="http://localhost:$proxy_port" \ -ANTHROPIC_AUTH_TOKEN=dummy \ -ANTHROPIC_MODEL=kimi-k2.6 \ -ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.6 \ -ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.6 \ -ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \ -CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \ -CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ -claude "$@" +exec "$kimi_bin" "$@" KIMIEOF chmod +x "$BIN_DIR/claude_kimi" @@ -1125,9 +606,9 @@ echo -e "${GREEN} Установка завершена!${NC}" echo -e "${GREEN}════════════════════════════════════════════════════${NC}" echo "" echo "Доступные команды (теперь это независимые скрипты в ~/.local/bin):" -echo -e " ${CYAN}claude_gpt${NC} — OpenAI Codex (нативный CLI)" +echo -e " ${CYAN}claude_gpt${NC} — OpenAI Codex (нативный CLI, автоустановка)" echo -e " ${CYAN}claude_deepseek${NC} — DeepSeek (API ключ сохраняется)" -echo -e " ${CYAN}claude_kimi${NC} — Kimi K2.6 (Artemox API, ключ сохраняется)" +echo -e " ${CYAN}claude_kimi${NC} — Kimi K2.6 (нативный CLI, автоустановка)" echo -e " ${CYAN}claude_gemini${NC} — Gemini (Google OAuth через браузер)" echo "" echo -e "${YELLOW}⚠️ Для Gemini используйте отдельный Google-аккаунт!${NC}" diff --git a/tests/test_fixes.sh b/tests/test_fixes.sh index bf4c211..2063b52 100755 --- a/tests/test_fixes.sh +++ b/tests/test_fixes.sh @@ -12,54 +12,42 @@ fail() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); } # Extract sections GPT_SECTION=$(awk '/^cat > "\$BIN_DIR\/claude_gpt"/,/^GPTEOF/' "$SCRIPT") +KIMI_SECTION=$(awk '/^cat > "\$BIN_DIR\/claude_kimi"/,/^KIMIEOF/' "$SCRIPT") GEMINI_SECTION=$(awk '/^cat > "\$BIN_DIR\/claude_gemini"/,/^GEMINIEOF/' "$SCRIPT") -OPENAI_PROXY_SECTION=$(awk '/^cat > "\$OPENAI_ANTHROPIC_PROXY_BIN"/,/^PYEOF/' "$SCRIPT") -# ── Fix 2: trap EXIT kills proxy ────────────────────────────────────────────── -test_fix2_trap_exit() { - if echo "$GPT_SECTION" | grep -q "trap .* EXIT"; then - ok "Fix2: trap EXIT for proxy cleanup present in claude_gpt" +# ── claude_gpt: auto-install codex ──────────────────────────────────────────── +test_gpt_autoinstall() { + if echo "$GPT_SECTION" | grep -q 'curl -fsSL https://chatgpt.com/codex/install.sh'; then + ok "claude_gpt: auto-installs codex via official install script" else - fail "Fix2: trap EXIT for proxy cleanup missing in claude_gpt" + fail "claude_gpt: missing codex auto-install" fi } -# ── Fix 3: readiness loop replaces bare sleep 1 ────────────────────────────── -test_fix3_readiness_loop() { - if echo "$GPT_SECTION" | grep -q 'while \[ \$_i -lt'; then - ok "Fix3: readiness poll loop present in claude_gpt proxy start" +# ── claude_gpt: no proxy logic (simplified launcher) ────────────────────────── +test_gpt_no_proxy() { + if echo "$GPT_SECTION" | grep -q 'ANTHROPIC_BASE_URL'; then + fail "claude_gpt: still contains proxy logic (ANTHROPIC_BASE_URL)" else - fail "Fix3: readiness poll loop missing in claude_gpt" - fi - - if echo "$GPT_SECTION" | grep -qP 'proxy_pid=\$!\n\s+sleep 1\n\s+fi'; then - fail "Fix3: bare 'sleep 1' still present right after proxy_pid=\$!" - else - ok "Fix3: bare 'sleep 1; fi' pattern removed" + ok "claude_gpt: proxy logic removed (no ANTHROPIC_BASE_URL)" fi } -# ── Fix 3b: curl exit-7 logic correct ──────────────────────────────────────── -test_fix3b_exit7_logic() { - if echo "$GPT_SECTION" | grep -q 'exit 7 = connection refused'; then - ok "Fix3b: exit-7 comment present (connection refused check documented)" +# ── claude_kimi: auto-install kimi ──────────────────────────────────────────── +test_kimi_autoinstall() { + if echo "$KIMI_SECTION" | grep -q 'curl -fsSL https://code.kimi.com/kimi-code/install.sh'; then + ok "claude_kimi: auto-installs kimi via official install script" else - fail "Fix3b: exit-7 comment missing" - fi - - if echo "$GPT_SECTION" | grep -q '\[ "\$?" -ne 7 \]'; then - ok "Fix3b: [ \$? -ne 7 ] break condition present" - else - fail "Fix3b: exit-7 break condition missing" + fail "claude_kimi: missing kimi auto-install" fi } -# ── Fix 4: re-validate after claude_gpt reauth ─────────────────────────────── -test_fix4_gpt_revalidate() { - if echo "$GPT_SECTION" | grep -q '_claude_test_api.*http://localhost:18765'; then - ok "Fix4: _claude_test_api called in claude_gpt" +# ── claude_kimi: no proxy logic (simplified launcher) ───────────────────────── +test_kimi_no_proxy() { + if echo "$KIMI_SECTION" | grep -q 'ANTHROPIC_BASE_URL'; then + fail "claude_kimi: still contains proxy logic (ANTHROPIC_BASE_URL)" else - fail "Fix4: _claude_test_api missing in claude_gpt" + ok "claude_kimi: proxy logic removed (no ANTHROPIC_BASE_URL)" fi } @@ -83,15 +71,6 @@ test_fix7_trap_tmp() { fi } -# ── Kimi/OpenAI proxy: Claude Code sends /v1/messages?beta=true ───────────── -test_kimi_proxy_accepts_query_string() { - if echo "$OPENAI_PROXY_SECTION" | grep -q 'urlparse(self.path).path'; then - ok "Kimi proxy: strips query string before routing /messages" - else - fail "Kimi proxy: does not handle /v1/messages?beta=true" - fi -} - # ── bash syntax of the whole script ───────────────────────────────────────── test_script_syntax() { if bash -n "$SCRIPT" 2>&1; then @@ -103,13 +82,12 @@ test_script_syntax() { # ── run all tests ───────────────────────────────────────────────────────────── test_script_syntax -test_fix2_trap_exit -test_fix3_readiness_loop -test_fix3b_exit7_logic -test_fix4_gpt_revalidate +test_gpt_autoinstall +test_gpt_no_proxy +test_kimi_autoinstall +test_kimi_no_proxy test_fix5_gemini_revalidate test_fix7_trap_tmp -test_kimi_proxy_accepts_query_string echo "" echo "Results: $PASS passed, $FAIL failed"