free-claude-code 6.2.64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- free_claude_code/__init__.py +1 -0
- free_claude_code/api/__init__.py +1 -0
- free_claude_code/api/admin_cache.py +49 -0
- free_claude_code/api/admin_routes.py +501 -0
- free_claude_code/api/admin_security.py +67 -0
- free_claude_code/api/admin_static/admin.css +980 -0
- free_claude_code/api/admin_static/admin.js +1934 -0
- free_claude_code/api/admin_static/code_sessions.css +109 -0
- free_claude_code/api/admin_static/code_sessions.js +1693 -0
- free_claude_code/api/admin_static/form_controls.js +15 -0
- free_claude_code/api/admin_static/index.html +201 -0
- free_claude_code/api/admin_static/model_combobox.js +194 -0
- free_claude_code/api/admin_static/providers/LICENSE-llamacpp.txt +21 -0
- free_claude_code/api/admin_static/providers/LICENSE-lobe-icons.txt +21 -0
- free_claude_code/api/admin_static/providers/SOURCES.md +56 -0
- free_claude_code/api/admin_static/providers/agnesai.svg +1 -0
- free_claude_code/api/admin_static/providers/azure-color.svg +1 -0
- free_claude_code/api/admin_static/providers/bedrock-color.svg +1 -0
- free_claude_code/api/admin_static/providers/cerebras-color.svg +1 -0
- free_claude_code/api/admin_static/providers/chutes.png +0 -0
- free_claude_code/api/admin_static/providers/cline.svg +1 -0
- free_claude_code/api/admin_static/providers/cloudflare-color.svg +1 -0
- free_claude_code/api/admin_static/providers/cohere-color.svg +1 -0
- free_claude_code/api/admin_static/providers/deepinfra-color.svg +1 -0
- free_claude_code/api/admin_static/providers/deepseek-color.svg +1 -0
- free_claude_code/api/admin_static/providers/experiential.svg +9 -0
- free_claude_code/api/admin_static/providers/featherless-color.svg +1 -0
- free_claude_code/api/admin_static/providers/fireworks-color.svg +1 -0
- free_claude_code/api/admin_static/providers/gemini-color.svg +1 -0
- free_claude_code/api/admin_static/providers/githubcopilot.svg +1 -0
- free_claude_code/api/admin_static/providers/groq.svg +1 -0
- free_claude_code/api/admin_static/providers/huggingface-color.svg +1 -0
- free_claude_code/api/admin_static/providers/kilocode.svg +1 -0
- free_claude_code/api/admin_static/providers/kimi-color.svg +1 -0
- free_claude_code/api/admin_static/providers/lightning.png +0 -0
- free_claude_code/api/admin_static/providers/llamacpp.svg +77 -0
- free_claude_code/api/admin_static/providers/llm7.png +0 -0
- free_claude_code/api/admin_static/providers/lmstudio.svg +1 -0
- free_claude_code/api/admin_static/providers/minimax-color.svg +1 -0
- free_claude_code/api/admin_static/providers/mistral-color.svg +1 -0
- free_claude_code/api/admin_static/providers/nararoute.png +0 -0
- free_claude_code/api/admin_static/providers/nebius.svg +1 -0
- free_claude_code/api/admin_static/providers/novita-color.svg +1 -0
- free_claude_code/api/admin_static/providers/nvidia-color.svg +1 -0
- free_claude_code/api/admin_static/providers/ollama.svg +1 -0
- free_claude_code/api/admin_static/providers/openai.svg +1 -0
- free_claude_code/api/admin_static/providers/opencode.svg +1 -0
- free_claude_code/api/admin_static/providers/openrouter.svg +1 -0
- free_claude_code/api/admin_static/providers/poolside-color.svg +1 -0
- free_claude_code/api/admin_static/providers/qwen-color.svg +1 -0
- free_claude_code/api/admin_static/providers/sambanova-color.svg +1 -0
- free_claude_code/api/admin_static/providers/scaleway.svg +11 -0
- free_claude_code/api/admin_static/providers/siliconcloud-color.svg +1 -0
- free_claude_code/api/admin_static/providers/together-color.svg +1 -0
- free_claude_code/api/admin_static/providers/tokenrouter.png +0 -0
- free_claude_code/api/admin_static/providers/vercel.svg +1 -0
- free_claude_code/api/admin_static/providers/vertexai-color.svg +1 -0
- free_claude_code/api/admin_static/providers/wafer.png +0 -0
- free_claude_code/api/admin_static/providers/wandb.png +0 -0
- free_claude_code/api/admin_static/providers/xai.svg +1 -0
- free_claude_code/api/admin_static/providers/zai.svg +1 -0
- free_claude_code/api/admin_static/providers/zenmux.svg +1 -0
- free_claude_code/api/admin_static/session_layout.css +552 -0
- free_claude_code/api/admin_static/session_ui.js +202 -0
- free_claude_code/api/app.py +149 -0
- free_claude_code/api/code_sessions_routes.py +386 -0
- free_claude_code/api/command_utils.py +164 -0
- free_claude_code/api/dependencies.py +128 -0
- free_claude_code/api/detection.py +185 -0
- free_claude_code/api/handlers/__init__.py +7 -0
- free_claude_code/api/handlers/classifier_response.py +66 -0
- free_claude_code/api/handlers/messages.py +359 -0
- free_claude_code/api/handlers/responses.py +163 -0
- free_claude_code/api/handlers/token_count.py +87 -0
- free_claude_code/api/markdown.py +71 -0
- free_claude_code/api/model_catalog.py +282 -0
- free_claude_code/api/optimization_handlers.py +162 -0
- free_claude_code/api/ports.py +97 -0
- free_claude_code/api/request_errors.py +100 -0
- free_claude_code/api/request_ids.py +100 -0
- free_claude_code/api/request_lifetime.py +130 -0
- free_claude_code/api/response_streams.py +422 -0
- free_claude_code/api/routes.py +269 -0
- free_claude_code/api/validation_log.py +46 -0
- free_claude_code/application/__init__.py +1 -0
- free_claude_code/application/code_sessions/__init__.py +33 -0
- free_claude_code/application/code_sessions/models.py +227 -0
- free_claude_code/application/code_sessions/ports.py +197 -0
- free_claude_code/application/code_sessions/service.py +1123 -0
- free_claude_code/application/code_sessions/state.py +659 -0
- free_claude_code/application/connected_accounts.py +96 -0
- free_claude_code/application/errors.py +41 -0
- free_claude_code/application/execution.py +467 -0
- free_claude_code/application/model_catalog.py +95 -0
- free_claude_code/application/model_metadata.py +26 -0
- free_claude_code/application/ports.py +127 -0
- free_claude_code/application/readiness.py +43 -0
- free_claude_code/application/reasoning.py +134 -0
- free_claude_code/application/routing.py +291 -0
- free_claude_code/application/session_events.py +140 -0
- free_claude_code/application/web_tools/__init__.py +1 -0
- free_claude_code/application/web_tools/ports.py +35 -0
- free_claude_code/application/web_tools/request.py +348 -0
- free_claude_code/application/web_tools/service.py +481 -0
- free_claude_code/assets/app-icon.icns +0 -0
- free_claude_code/assets/app-icon.ico +0 -0
- free_claude_code/assets/app-icon.png +0 -0
- free_claude_code/assets/app-icon.svg +14 -0
- free_claude_code/cli/__init__.py +5 -0
- free_claude_code/cli/commands.py +379 -0
- free_claude_code/cli/desktop.py +156 -0
- free_claude_code/cli/desktop_assets.py +31 -0
- free_claude_code/cli/desktop_entrypoint.py +29 -0
- free_claude_code/cli/desktop_tray.py +67 -0
- free_claude_code/cli/entrypoints.py +25 -0
- free_claude_code/cli/launchers/__init__.py +1 -0
- free_claude_code/cli/launchers/aider.py +53 -0
- free_claude_code/cli/launchers/aider_config.py +80 -0
- free_claude_code/cli/launchers/catalog_http.py +134 -0
- free_claude_code/cli/launchers/claude.py +34 -0
- free_claude_code/cli/launchers/cline.py +60 -0
- free_claude_code/cli/launchers/cline_config.py +121 -0
- free_claude_code/cli/launchers/codex.py +47 -0
- free_claude_code/cli/launchers/common.py +103 -0
- free_claude_code/cli/launchers/dsh.py +69 -0
- free_claude_code/cli/launchers/dsh_config.py +124 -0
- free_claude_code/cli/launchers/grok.py +116 -0
- free_claude_code/cli/launchers/hermes.py +85 -0
- free_claude_code/cli/launchers/hermes_config.py +134 -0
- free_claude_code/cli/launchers/muse.py +68 -0
- free_claude_code/cli/launchers/opencode.py +83 -0
- free_claude_code/cli/launchers/opencode_config.py +107 -0
- free_claude_code/cli/launchers/pi.py +70 -0
- free_claude_code/cli/launchers/pi_extension.ts +190 -0
- free_claude_code/cli/launchers/runner.py +155 -0
- free_claude_code/cli/local_http.py +12 -0
- free_claude_code/cli/managed/__init__.py +6 -0
- free_claude_code/cli/managed/claude.py +225 -0
- free_claude_code/cli/managed/diagnostics.py +47 -0
- free_claude_code/cli/managed/manager.py +207 -0
- free_claude_code/cli/managed/session.py +293 -0
- free_claude_code/cli/process_registry.py +76 -0
- free_claude_code/cli/server_socket.py +71 -0
- free_claude_code/cli/uvicorn_server.py +41 -0
- free_claude_code/config/__init__.py +10 -0
- free_claude_code/config/admin/__init__.py +1 -0
- free_claude_code/config/admin/manifest.py +610 -0
- free_claude_code/config/admin/persistence.py +202 -0
- free_claude_code/config/admin/provider_manifest.py +443 -0
- free_claude_code/config/admin/specs.py +77 -0
- free_claude_code/config/admin/state.py +18 -0
- free_claude_code/config/admin/status.py +116 -0
- free_claude_code/config/admin/validation.py +40 -0
- free_claude_code/config/admin/values.py +115 -0
- free_claude_code/config/constants.py +9 -0
- free_claude_code/config/env_files.py +74 -0
- free_claude_code/config/env_migrations.py +457 -0
- free_claude_code/config/loader.py +196 -0
- free_claude_code/config/logging_config.py +197 -0
- free_claude_code/config/model_refs.py +118 -0
- free_claude_code/config/nim.py +121 -0
- free_claude_code/config/paths.py +104 -0
- free_claude_code/config/provider_catalog.py +746 -0
- free_claude_code/config/provider_proxies.py +44 -0
- free_claude_code/config/reasoning.py +24 -0
- free_claude_code/config/server_urls.py +55 -0
- free_claude_code/config/settings.py +812 -0
- free_claude_code/core/__init__.py +15 -0
- free_claude_code/core/anthropic/__init__.py +99 -0
- free_claude_code/core/anthropic/content.py +31 -0
- free_claude_code/core/anthropic/conversion.py +876 -0
- free_claude_code/core/anthropic/errors.py +97 -0
- free_claude_code/core/anthropic/image_sources.py +77 -0
- free_claude_code/core/anthropic/models.py +193 -0
- free_claude_code/core/anthropic/native.py +184 -0
- free_claude_code/core/anthropic/native_stream.py +306 -0
- free_claude_code/core/anthropic/request_serialization.py +57 -0
- free_claude_code/core/anthropic/request_snapshot.py +35 -0
- free_claude_code/core/anthropic/server_tool_sse.py +234 -0
- free_claude_code/core/anthropic/server_tool_types.py +9 -0
- free_claude_code/core/anthropic/sse_aggregation.py +144 -0
- free_claude_code/core/anthropic/stream_contracts.py +208 -0
- free_claude_code/core/anthropic/streaming/__init__.py +43 -0
- free_claude_code/core/anthropic/streaming/decoder.py +49 -0
- free_claude_code/core/anthropic/streaming/emitter.py +62 -0
- free_claude_code/core/anthropic/streaming/ledger.py +538 -0
- free_claude_code/core/anthropic/streaming/recovery.py +211 -0
- free_claude_code/core/anthropic/thinking.py +140 -0
- free_claude_code/core/anthropic/tokens.py +111 -0
- free_claude_code/core/anthropic/tool_results.py +63 -0
- free_claude_code/core/anthropic/tool_schema.py +94 -0
- free_claude_code/core/anthropic/tools.py +493 -0
- free_claude_code/core/anthropic/usage.py +38 -0
- free_claude_code/core/anthropic/utils.py +9 -0
- free_claude_code/core/async_iterators.py +26 -0
- free_claude_code/core/async_tasks.py +26 -0
- free_claude_code/core/data/README.md +12 -0
- free_claude_code/core/data/TIKTOKEN-LICENSE +21 -0
- free_claude_code/core/data/cl100k_base.tiktoken +100256 -0
- free_claude_code/core/diagnostics.py +319 -0
- free_claude_code/core/failures.py +50 -0
- free_claude_code/core/gateway_model_ids.py +73 -0
- free_claude_code/core/history_replay.py +561 -0
- free_claude_code/core/interprocess_lock.py +90 -0
- free_claude_code/core/json_types.py +7 -0
- free_claude_code/core/model_capabilities.py +10 -0
- free_claude_code/core/openai_chat.py +54 -0
- free_claude_code/core/openai_responses/__init__.py +90 -0
- free_claude_code/core/openai_responses/chat_request.py +747 -0
- free_claude_code/core/openai_responses/errors.py +78 -0
- free_claude_code/core/openai_responses/events.py +82 -0
- free_claude_code/core/openai_responses/ids.py +32 -0
- free_claude_code/core/openai_responses/items.py +35 -0
- free_claude_code/core/openai_responses/messages_request.py +624 -0
- free_claude_code/core/openai_responses/messages_stream.py +404 -0
- free_claude_code/core/openai_responses/models.py +33 -0
- free_claude_code/core/openai_responses/native.py +147 -0
- free_claude_code/core/openai_responses/provider_input.py +409 -0
- free_claude_code/core/openai_responses/provider_stream.py +282 -0
- free_claude_code/core/openai_responses/reasoning.py +73 -0
- free_claude_code/core/openai_responses/streaming/__init__.py +1 -0
- free_claude_code/core/openai_responses/streaming/blocks.py +36 -0
- free_claude_code/core/openai_responses/streaming/completion.py +169 -0
- free_claude_code/core/openai_responses/streaming/error_mapping.py +28 -0
- free_claude_code/core/openai_responses/streaming/event_builders.py +198 -0
- free_claude_code/core/openai_responses/streaming/ledger.py +96 -0
- free_claude_code/core/openai_responses/tokens.py +133 -0
- free_claude_code/core/openai_responses/tool_adaptation.py +828 -0
- free_claude_code/core/openai_responses/tool_search.py +204 -0
- free_claude_code/core/openai_responses/tools.py +169 -0
- free_claude_code/core/openai_tool_names.py +178 -0
- free_claude_code/core/rate_limit.py +72 -0
- free_claude_code/core/reasoning.py +136 -0
- free_claude_code/core/token_estimation.py +76 -0
- free_claude_code/core/tool_schema_patterns.py +168 -0
- free_claude_code/core/trace.py +197 -0
- free_claude_code/core/version.py +15 -0
- free_claude_code/core/web_tools.py +17 -0
- free_claude_code/core/windows_dpi.py +27 -0
- free_claude_code/harnesses/__init__.py +1 -0
- free_claude_code/harnesses/claude.py +42 -0
- free_claude_code/harnesses/claude_desktop_integration.py +418 -0
- free_claude_code/harnesses/claude_integration.py +167 -0
- free_claude_code/harnesses/codex.py +94 -0
- free_claude_code/harnesses/codex_integration.py +147 -0
- free_claude_code/harnesses/codex_model_catalog.py +109 -0
- free_claude_code/harnesses/config_file.py +33 -0
- free_claude_code/harnesses/environment.py +78 -0
- free_claude_code/harnesses/jetbrains_acp_integration.py +242 -0
- free_claude_code/harnesses/launch.py +21 -0
- free_claude_code/harnesses/resources.py +42 -0
- free_claude_code/messaging/__init__.py +16 -0
- free_claude_code/messaging/cli_event_constants.py +67 -0
- free_claude_code/messaging/command_context.py +99 -0
- free_claude_code/messaging/command_dispatcher.py +31 -0
- free_claude_code/messaging/commands.py +182 -0
- free_claude_code/messaging/event_parser.py +184 -0
- free_claude_code/messaging/limiter.py +342 -0
- free_claude_code/messaging/managed_protocols.py +35 -0
- free_claude_code/messaging/models.py +57 -0
- free_claude_code/messaging/node_event_pipeline.py +120 -0
- free_claude_code/messaging/node_runner.py +408 -0
- free_claude_code/messaging/platforms/__init__.py +20 -0
- free_claude_code/messaging/platforms/discord.py +319 -0
- free_claude_code/messaging/platforms/discord_inbound.py +112 -0
- free_claude_code/messaging/platforms/discord_io.py +167 -0
- free_claude_code/messaging/platforms/factory.py +109 -0
- free_claude_code/messaging/platforms/outbox.py +143 -0
- free_claude_code/messaging/platforms/ports.py +99 -0
- free_claude_code/messaging/platforms/telegram.py +300 -0
- free_claude_code/messaging/platforms/telegram_inbound.py +133 -0
- free_claude_code/messaging/platforms/telegram_io.py +287 -0
- free_claude_code/messaging/platforms/voice_flow.py +388 -0
- free_claude_code/messaging/rendering/__init__.py +3 -0
- free_claude_code/messaging/rendering/discord_markdown.py +318 -0
- free_claude_code/messaging/rendering/markdown_tables.py +47 -0
- free_claude_code/messaging/rendering/profiles.py +53 -0
- free_claude_code/messaging/rendering/telegram_markdown.py +331 -0
- free_claude_code/messaging/safe_diagnostics.py +15 -0
- free_claude_code/messaging/session/__init__.py +5 -0
- free_claude_code/messaging/session/managed_message_log.py +135 -0
- free_claude_code/messaging/session/persistence.py +161 -0
- free_claude_code/messaging/session/store.py +162 -0
- free_claude_code/messaging/transcript/__init__.py +6 -0
- free_claude_code/messaging/transcript/buffer.py +220 -0
- free_claude_code/messaging/transcript/context.py +18 -0
- free_claude_code/messaging/transcript/renderer.py +65 -0
- free_claude_code/messaging/transcript/segments.py +176 -0
- free_claude_code/messaging/transcript/subagents.py +108 -0
- free_claude_code/messaging/transcription.py +132 -0
- free_claude_code/messaging/trees/__init__.py +41 -0
- free_claude_code/messaging/trees/graph.py +250 -0
- free_claude_code/messaging/trees/identity.py +16 -0
- free_claude_code/messaging/trees/manager.py +587 -0
- free_claude_code/messaging/trees/node.py +56 -0
- free_claude_code/messaging/trees/processor.py +272 -0
- free_claude_code/messaging/trees/queue.py +47 -0
- free_claude_code/messaging/trees/repository.py +135 -0
- free_claude_code/messaging/trees/runtime.py +487 -0
- free_claude_code/messaging/trees/snapshot.py +221 -0
- free_claude_code/messaging/trees/transitions.py +174 -0
- free_claude_code/messaging/turn_intake.py +241 -0
- free_claude_code/messaging/ui_updates.py +99 -0
- free_claude_code/messaging/voice.py +376 -0
- free_claude_code/messaging/workflow.py +714 -0
- free_claude_code/providers/__init__.py +12 -0
- free_claude_code/providers/admission.py +942 -0
- free_claude_code/providers/anthropic_messages/__init__.py +1 -0
- free_claude_code/providers/anthropic_messages/request_policy.py +162 -0
- free_claude_code/providers/anthropic_messages/transport.py +499 -0
- free_claude_code/providers/base.py +73 -0
- free_claude_code/providers/cloudflare/__init__.py +8 -0
- free_claude_code/providers/cloudflare/client.py +166 -0
- free_claude_code/providers/credential_validation.py +314 -0
- free_claude_code/providers/deepseek/__init__.py +5 -0
- free_claude_code/providers/deepseek/client.py +117 -0
- free_claude_code/providers/deepseek/compat.py +241 -0
- free_claude_code/providers/endpoint.py +21 -0
- free_claude_code/providers/endpoint_types.py +21 -0
- free_claude_code/providers/failure_policy.py +574 -0
- free_claude_code/providers/gemini/__init__.py +5 -0
- free_claude_code/providers/gemini/client.py +42 -0
- free_claude_code/providers/github_copilot/__init__.py +1 -0
- free_claude_code/providers/github_copilot/auth.py +502 -0
- free_claude_code/providers/github_copilot/broker.py +248 -0
- free_claude_code/providers/github_copilot/lifecycle.py +18 -0
- free_claude_code/providers/github_copilot/login.py +112 -0
- free_claude_code/providers/github_copilot/native_cli.py +82 -0
- free_claude_code/providers/github_copilot/provider.py +367 -0
- free_claude_code/providers/github_copilot/request_policy.py +119 -0
- free_claude_code/providers/github_copilot/responses_events.py +90 -0
- free_claude_code/providers/github_copilot/sdk.py +496 -0
- free_claude_code/providers/github_copilot/types.py +67 -0
- free_claude_code/providers/google_openai/__init__.py +17 -0
- free_claude_code/providers/google_openai/provider.py +73 -0
- free_claude_code/providers/google_openai/reasoning.py +150 -0
- free_claude_code/providers/google_openai/thought_signatures.py +117 -0
- free_claude_code/providers/groq/__init__.py +5 -0
- free_claude_code/providers/groq/client.py +342 -0
- free_claude_code/providers/groq/tpm.py +114 -0
- free_claude_code/providers/history_replay.py +260 -0
- free_claude_code/providers/http.py +105 -0
- free_claude_code/providers/kilo/__init__.py +5 -0
- free_claude_code/providers/kilo/client.py +45 -0
- free_claude_code/providers/kilo/models.py +60 -0
- free_claude_code/providers/lmstudio/__init__.py +5 -0
- free_claude_code/providers/lmstudio/client.py +220 -0
- free_claude_code/providers/mistral/__init__.py +5 -0
- free_claude_code/providers/mistral/client.py +91 -0
- free_claude_code/providers/mistral/reasoning.py +410 -0
- free_claude_code/providers/model_listing.py +479 -0
- free_claude_code/providers/nvidia_nim/__init__.py +5 -0
- free_claude_code/providers/nvidia_nim/client.py +224 -0
- free_claude_code/providers/nvidia_nim/native_tool_stream.py +693 -0
- free_claude_code/providers/nvidia_nim/request_options.py +141 -0
- free_claude_code/providers/nvidia_nim/retry.py +85 -0
- free_claude_code/providers/nvidia_nim/tool_schema.py +255 -0
- free_claude_code/providers/nvidia_nim/voice.py +107 -0
- free_claude_code/providers/open_router/__init__.py +5 -0
- free_claude_code/providers/open_router/client.py +113 -0
- free_claude_code/providers/openai_api/__init__.py +5 -0
- free_claude_code/providers/openai_api/provider.py +150 -0
- free_claude_code/providers/openai_chat/__init__.py +72 -0
- free_claude_code/providers/openai_chat/base_url.py +7 -0
- free_claude_code/providers/openai_chat/behavior.py +133 -0
- free_claude_code/providers/openai_chat/client.py +40 -0
- free_claude_code/providers/openai_chat/extra_body.py +63 -0
- free_claude_code/providers/openai_chat/output_cap.py +161 -0
- free_claude_code/providers/openai_chat/profiles.py +778 -0
- free_claude_code/providers/openai_chat/provider.py +244 -0
- free_claude_code/providers/openai_chat/reasoning.py +169 -0
- free_claude_code/providers/openai_chat/reasoning_details.py +128 -0
- free_claude_code/providers/openai_chat/request_policy.py +140 -0
- free_claude_code/providers/openai_chat/stream_output.py +752 -0
- free_claude_code/providers/openai_chat/tool_calls.py +462 -0
- free_claude_code/providers/openai_chat/transport.py +1643 -0
- free_claude_code/providers/openai_chat/usage.py +117 -0
- free_claude_code/providers/openai_client.py +74 -0
- free_claude_code/providers/openai_codex/__init__.py +1 -0
- free_claude_code/providers/openai_codex/auth.py +626 -0
- free_claude_code/providers/openai_codex/endpoint.py +53 -0
- free_claude_code/providers/openai_codex/login.py +265 -0
- free_claude_code/providers/openai_codex/provider.py +259 -0
- free_claude_code/providers/openai_responses/__init__.py +5 -0
- free_claude_code/providers/openai_responses/presentation.py +115 -0
- free_claude_code/providers/openai_responses/transport.py +621 -0
- free_claude_code/providers/openai_stream.py +21 -0
- free_claude_code/providers/opencode/__init__.py +5 -0
- free_claude_code/providers/opencode/catalog.py +340 -0
- free_claude_code/providers/opencode/provider.py +359 -0
- free_claude_code/providers/reasoning_compatibility.py +199 -0
- free_claude_code/providers/request_recovery.py +122 -0
- free_claude_code/providers/runtime/__init__.py +10 -0
- free_claude_code/providers/runtime/config.py +91 -0
- free_claude_code/providers/runtime/discovery.py +63 -0
- free_claude_code/providers/runtime/factory.py +282 -0
- free_claude_code/providers/runtime/model_cache.py +93 -0
- free_claude_code/providers/runtime/runtime.py +108 -0
- free_claude_code/providers/stream_recovery.py +164 -0
- free_claude_code/providers/vertex/__init__.py +5 -0
- free_claude_code/providers/vertex/auth.py +105 -0
- free_claude_code/providers/vertex/client.py +140 -0
- free_claude_code/providers/vertex/endpoint.py +50 -0
- free_claude_code/providers/vertex/models.py +52 -0
- free_claude_code/runtime/__init__.py +1 -0
- free_claude_code/runtime/application.py +1121 -0
- free_claude_code/runtime/asgi.py +63 -0
- free_claude_code/runtime/bootstrap.py +153 -0
- free_claude_code/runtime/code_sessions_sqlite.py +785 -0
- free_claude_code/runtime/codex_app_server.py +775 -0
- free_claude_code/runtime/codex_catalog.py +62 -0
- free_claude_code/runtime/codex_protocol.py +662 -0
- free_claude_code/runtime/configuration.py +53 -0
- free_claude_code/runtime/folder_picker.py +134 -0
- free_claude_code/runtime/native_folder_dialog.py +123 -0
- free_claude_code/runtime/provider_manager.py +716 -0
- free_claude_code/runtime/retired_chat.py +47 -0
- free_claude_code/runtime/web_tools/__init__.py +1 -0
- free_claude_code/runtime/web_tools/client.py +238 -0
- free_claude_code/runtime/web_tools/constants.py +17 -0
- free_claude_code/runtime/web_tools/egress.py +89 -0
- free_claude_code/runtime/web_tools/parsers.py +78 -0
- free_claude_code-6.2.64.data/scripts/fcc-update +4 -0
- free_claude_code-6.2.64.data/scripts/fcc-update.cmd +5 -0
- free_claude_code-6.2.64.dist-info/METADATA +555 -0
- free_claude_code-6.2.64.dist-info/RECORD +429 -0
- free_claude_code-6.2.64.dist-info/WHEEL +4 -0
- free_claude_code-6.2.64.dist-info/entry_points.txt +15 -0
- free_claude_code-6.2.64.dist-info/licenses/LICENSE +239 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Account-scoped discovery and inert endpoint sessions with draining leases."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import AsyncIterator, Mapping
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
from dataclasses import replace
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
|
|
10
|
+
from free_claude_code.application.errors import InvalidRequestError
|
|
11
|
+
from free_claude_code.providers.endpoint_types import HttpEndpoint
|
|
12
|
+
|
|
13
|
+
from .lifecycle import drain_owned
|
|
14
|
+
from .types import (
|
|
15
|
+
CopilotAuthenticationRequired,
|
|
16
|
+
CopilotEgress,
|
|
17
|
+
CopilotEndpoint,
|
|
18
|
+
CopilotIdentity,
|
|
19
|
+
CopilotModel,
|
|
20
|
+
CopilotRuntime,
|
|
21
|
+
CopilotSession,
|
|
22
|
+
CopilotUnavailable,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
_REFRESH_EARLY_SECONDS = 30
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _EndpointOwner:
|
|
29
|
+
def __init__(self, runtime: CopilotRuntime, model_id: str) -> None:
|
|
30
|
+
self._runtime = runtime
|
|
31
|
+
self._model_id = model_id
|
|
32
|
+
self._lock = asyncio.Lock()
|
|
33
|
+
self._session_task: asyncio.Task[CopilotSession] | None = None
|
|
34
|
+
self._snapshot: CopilotEndpoint | None = None
|
|
35
|
+
|
|
36
|
+
async def endpoint(self, *, force_refresh: bool) -> CopilotEndpoint:
|
|
37
|
+
async with self._lock:
|
|
38
|
+
if self._session_task is None or (
|
|
39
|
+
self._session_task.done()
|
|
40
|
+
and (
|
|
41
|
+
self._session_task.cancelled()
|
|
42
|
+
or self._session_task.exception() is not None
|
|
43
|
+
)
|
|
44
|
+
):
|
|
45
|
+
self._session_task = asyncio.create_task(
|
|
46
|
+
self._runtime.session(self._model_id)
|
|
47
|
+
)
|
|
48
|
+
session = await asyncio.shield(self._session_task)
|
|
49
|
+
snapshot = self._snapshot
|
|
50
|
+
if (
|
|
51
|
+
force_refresh
|
|
52
|
+
or snapshot is None
|
|
53
|
+
or (
|
|
54
|
+
snapshot.expires_at is not None
|
|
55
|
+
and snapshot.expires_at <= time.time() + _REFRESH_EARLY_SECONDS
|
|
56
|
+
)
|
|
57
|
+
):
|
|
58
|
+
snapshot = await session.endpoint()
|
|
59
|
+
if (
|
|
60
|
+
snapshot.expires_at is not None
|
|
61
|
+
and snapshot.expires_at <= time.time()
|
|
62
|
+
):
|
|
63
|
+
raise CopilotUnavailable(
|
|
64
|
+
"Copilot returned an expired endpoint credential. Reconnect Copilot."
|
|
65
|
+
)
|
|
66
|
+
self._snapshot = snapshot
|
|
67
|
+
return snapshot
|
|
68
|
+
|
|
69
|
+
async def drain_creation(self) -> None:
|
|
70
|
+
if self._session_task is not None:
|
|
71
|
+
await asyncio.gather(self._session_task, return_exceptions=True)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CopilotLease:
|
|
75
|
+
"""Retain one concrete model and its routing family throughout inference."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
broker: CopilotBroker,
|
|
80
|
+
owner: _EndpointOwner,
|
|
81
|
+
model: CopilotModel,
|
|
82
|
+
egress: CopilotEgress,
|
|
83
|
+
) -> None:
|
|
84
|
+
self._broker = broker
|
|
85
|
+
self._owner = owner
|
|
86
|
+
self.model = model
|
|
87
|
+
self.egress = egress
|
|
88
|
+
self._released = False
|
|
89
|
+
|
|
90
|
+
async def endpoint(self, *, force_refresh: bool = False) -> HttpEndpoint:
|
|
91
|
+
if self._released:
|
|
92
|
+
raise CopilotUnavailable("Copilot endpoint lease has already closed.")
|
|
93
|
+
await self._broker.validate_identity()
|
|
94
|
+
self._broker.validate_model(self.model.info.model_id)
|
|
95
|
+
snapshot = await self._owner.endpoint(force_refresh=force_refresh)
|
|
96
|
+
identity = await self._broker.validate_identity()
|
|
97
|
+
self._broker.validate_model(self.model.info.model_id)
|
|
98
|
+
if snapshot.egress is not self.egress:
|
|
99
|
+
raise CopilotUnavailable(
|
|
100
|
+
"Copilot changed this model's upstream protocol. Retry the request to use its new endpoint."
|
|
101
|
+
)
|
|
102
|
+
return replace(snapshot.http, account_id=f"{identity.host}/{identity.login}")
|
|
103
|
+
|
|
104
|
+
def release(self) -> None:
|
|
105
|
+
self._released = True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CopilotBroker:
|
|
109
|
+
"""Coalesce SDK discovery and retain sessions until every response closes."""
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
runtime: CopilotRuntime,
|
|
114
|
+
*,
|
|
115
|
+
expected_identity: CopilotIdentity | None = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
self._runtime = runtime
|
|
118
|
+
self._identity = expected_identity
|
|
119
|
+
self._models: Mapping[str, CopilotModel] = MappingProxyType({})
|
|
120
|
+
self._owners: dict[str, _EndpointOwner] = {}
|
|
121
|
+
self._condition = asyncio.Condition()
|
|
122
|
+
self._start_task: asyncio.Task[None] | None = None
|
|
123
|
+
self._refresh_task: asyncio.Task[Mapping[str, CopilotModel]] | None = None
|
|
124
|
+
self._close_task: asyncio.Task[None] | None = None
|
|
125
|
+
self._closing = False
|
|
126
|
+
self._closed = False
|
|
127
|
+
self._leases = 0
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def identity(self) -> CopilotIdentity | None:
|
|
131
|
+
return self._identity
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def current_models(self) -> Mapping[str, CopilotModel]:
|
|
135
|
+
return self._models
|
|
136
|
+
|
|
137
|
+
async def _start(self) -> None:
|
|
138
|
+
async with self._condition:
|
|
139
|
+
if self._closing:
|
|
140
|
+
raise CopilotUnavailable("Copilot connection is closing.")
|
|
141
|
+
if self._start_task is None:
|
|
142
|
+
self._start_task = asyncio.create_task(self._runtime.start())
|
|
143
|
+
task = self._start_task
|
|
144
|
+
await asyncio.shield(task)
|
|
145
|
+
|
|
146
|
+
async def validate_identity(self) -> CopilotIdentity:
|
|
147
|
+
identity = await self._runtime.identity()
|
|
148
|
+
if identity is None:
|
|
149
|
+
raise CopilotAuthenticationRequired(
|
|
150
|
+
"Copilot is signed out. Connect your GitHub account in Admin."
|
|
151
|
+
)
|
|
152
|
+
if self._identity is not None and identity != self._identity:
|
|
153
|
+
raise CopilotAuthenticationRequired(
|
|
154
|
+
"The selected GitHub profile changed. Reconnect Copilot in Admin."
|
|
155
|
+
)
|
|
156
|
+
self._identity = identity
|
|
157
|
+
return identity
|
|
158
|
+
|
|
159
|
+
async def snapshot(self, *, refresh: bool = False) -> Mapping[str, CopilotModel]:
|
|
160
|
+
await self._start()
|
|
161
|
+
await self.validate_identity()
|
|
162
|
+
async with self._condition:
|
|
163
|
+
if self._closing:
|
|
164
|
+
raise CopilotUnavailable("Copilot connection is closing.")
|
|
165
|
+
task = self._refresh_task
|
|
166
|
+
if task is None or task.done():
|
|
167
|
+
if self._models and not refresh:
|
|
168
|
+
return self._models
|
|
169
|
+
task = asyncio.create_task(self._discover())
|
|
170
|
+
self._refresh_task = task
|
|
171
|
+
return await asyncio.shield(task)
|
|
172
|
+
|
|
173
|
+
def validate_model(self, model_id: str) -> CopilotModel:
|
|
174
|
+
model = self._models.get(model_id)
|
|
175
|
+
if model is None:
|
|
176
|
+
raise InvalidRequestError(
|
|
177
|
+
"This model is not available from the connected Copilot account. Refresh the model list."
|
|
178
|
+
)
|
|
179
|
+
return model
|
|
180
|
+
|
|
181
|
+
async def _discover(self) -> Mapping[str, CopilotModel]:
|
|
182
|
+
await self.validate_identity()
|
|
183
|
+
models = await self._runtime.models()
|
|
184
|
+
await self.validate_identity()
|
|
185
|
+
result = {model.info.model_id: model for model in models}
|
|
186
|
+
if len(result) != len(models):
|
|
187
|
+
raise CopilotUnavailable("Copilot returned conflicting model identifiers.")
|
|
188
|
+
async with self._condition:
|
|
189
|
+
if self._closing:
|
|
190
|
+
raise CopilotUnavailable("Copilot connection closed during discovery.")
|
|
191
|
+
self._models = MappingProxyType(result)
|
|
192
|
+
if not result:
|
|
193
|
+
raise CopilotUnavailable(
|
|
194
|
+
"This GitHub account exposes no enabled Copilot models. Check its subscription and model policies."
|
|
195
|
+
)
|
|
196
|
+
return self._models
|
|
197
|
+
|
|
198
|
+
@asynccontextmanager
|
|
199
|
+
async def lease(self, model_id: str) -> AsyncIterator[CopilotLease]:
|
|
200
|
+
await self.snapshot()
|
|
201
|
+
async with self._condition:
|
|
202
|
+
if self._closing:
|
|
203
|
+
raise CopilotUnavailable("Copilot connection is closing.")
|
|
204
|
+
model = self.validate_model(model_id)
|
|
205
|
+
self._leases += 1
|
|
206
|
+
owner = self._owners.get(model_id)
|
|
207
|
+
if owner is None:
|
|
208
|
+
owner = _EndpointOwner(self._runtime, model_id)
|
|
209
|
+
self._owners[model_id] = owner
|
|
210
|
+
lease = None
|
|
211
|
+
try:
|
|
212
|
+
await self.validate_identity()
|
|
213
|
+
endpoint = await owner.endpoint(force_refresh=False)
|
|
214
|
+
await self.validate_identity()
|
|
215
|
+
self.validate_model(model_id)
|
|
216
|
+
lease = CopilotLease(self, owner, model, endpoint.egress)
|
|
217
|
+
yield lease
|
|
218
|
+
finally:
|
|
219
|
+
if lease is not None:
|
|
220
|
+
lease.release()
|
|
221
|
+
async with self._condition:
|
|
222
|
+
self._leases -= 1
|
|
223
|
+
self._condition.notify_all()
|
|
224
|
+
|
|
225
|
+
async def close(self) -> None:
|
|
226
|
+
if self._closed:
|
|
227
|
+
return
|
|
228
|
+
if self._close_task is None or self._close_task.done():
|
|
229
|
+
self._closing = True
|
|
230
|
+
self._close_task = asyncio.create_task(self._close())
|
|
231
|
+
await drain_owned(self._close_task)
|
|
232
|
+
|
|
233
|
+
async def _close(self) -> None:
|
|
234
|
+
# Stop discovery publication; SDK-owned work itself has bounded RPC timeouts.
|
|
235
|
+
tasks = [
|
|
236
|
+
task for task in (self._start_task, self._refresh_task) if task is not None
|
|
237
|
+
]
|
|
238
|
+
if tasks:
|
|
239
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
240
|
+
async with self._condition:
|
|
241
|
+
await self._condition.wait_for(lambda: self._leases == 0)
|
|
242
|
+
await asyncio.gather(
|
|
243
|
+
*(owner.drain_creation() for owner in self._owners.values())
|
|
244
|
+
)
|
|
245
|
+
await self._runtime.close()
|
|
246
|
+
self._owners.clear()
|
|
247
|
+
self._models = MappingProxyType({})
|
|
248
|
+
self._closed = True
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Drain Copilot-owned work before propagating caller cancellation."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def drain_owned[T](task: asyncio.Task[T]) -> T:
|
|
7
|
+
cancelled = False
|
|
8
|
+
while not task.done():
|
|
9
|
+
try:
|
|
10
|
+
await asyncio.shield(task)
|
|
11
|
+
except asyncio.CancelledError:
|
|
12
|
+
if task.cancelled():
|
|
13
|
+
raise
|
|
14
|
+
cancelled = True
|
|
15
|
+
result = task.result()
|
|
16
|
+
if cancelled:
|
|
17
|
+
raise asyncio.CancelledError
|
|
18
|
+
return result
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Official native Copilot device login; FCC never handles the resulting token."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from contextlib import suppress
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from urllib.parse import urlsplit
|
|
11
|
+
|
|
12
|
+
from .lifecycle import drain_owned
|
|
13
|
+
from .native_cli import profile_environment, verified_cli_path
|
|
14
|
+
from .types import CopilotUnavailable
|
|
15
|
+
|
|
16
|
+
LOGIN_TIMEOUT_SECONDS = 15 * 60
|
|
17
|
+
_CHALLENGE = re.compile(
|
|
18
|
+
r"visit\s+(https://[^\s]+/login/device)\s+and enter code\s+([A-Z0-9]{4}-[A-Z0-9]{4})",
|
|
19
|
+
re.IGNORECASE,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class DeviceChallenge:
|
|
25
|
+
verification_url: str
|
|
26
|
+
user_code: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_challenge(text: str) -> DeviceChallenge | None:
|
|
30
|
+
match = _CHALLENGE.search(text)
|
|
31
|
+
if match is None:
|
|
32
|
+
return None
|
|
33
|
+
url, code = match.groups()
|
|
34
|
+
parsed = urlsplit(url)
|
|
35
|
+
if not parsed.hostname or parsed.username or parsed.password:
|
|
36
|
+
return None
|
|
37
|
+
return DeviceChallenge(url, code.upper())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def device_login(on_challenge: Callable[[DeviceChallenge], None]) -> None:
|
|
41
|
+
"""Run the pinned CLI login and own its child and both bounded readers."""
|
|
42
|
+
env = profile_environment()
|
|
43
|
+
path = await verified_cli_path(env)
|
|
44
|
+
spawning = asyncio.create_task(
|
|
45
|
+
asyncio.create_subprocess_exec(
|
|
46
|
+
path,
|
|
47
|
+
"login",
|
|
48
|
+
"--device-code",
|
|
49
|
+
stdout=asyncio.subprocess.PIPE,
|
|
50
|
+
stderr=asyncio.subprocess.PIPE,
|
|
51
|
+
env=env,
|
|
52
|
+
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
readers: list[asyncio.Task[None]] = []
|
|
56
|
+
|
|
57
|
+
async def read_output(stream: asyncio.StreamReader) -> None:
|
|
58
|
+
buffer = ""
|
|
59
|
+
previous: DeviceChallenge | None = None
|
|
60
|
+
while chunk := await stream.read(1024):
|
|
61
|
+
buffer = (buffer + chunk.decode("utf-8", errors="replace"))[-4096:]
|
|
62
|
+
challenge = parse_challenge(buffer)
|
|
63
|
+
if challenge is not None and challenge != previous:
|
|
64
|
+
on_challenge(challenge)
|
|
65
|
+
previous = challenge
|
|
66
|
+
|
|
67
|
+
async def cleanup() -> None:
|
|
68
|
+
try:
|
|
69
|
+
process = await spawning
|
|
70
|
+
except Exception:
|
|
71
|
+
return
|
|
72
|
+
# Cancellation can arrive before normal readers are installed, or cancel
|
|
73
|
+
# their gather. Pipe transports must finish before process.wait can settle.
|
|
74
|
+
for reader in readers:
|
|
75
|
+
reader.cancel()
|
|
76
|
+
await asyncio.gather(*readers, return_exceptions=True)
|
|
77
|
+
|
|
78
|
+
async def discard(stream: asyncio.StreamReader) -> None:
|
|
79
|
+
while await stream.read(65536):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
draining = [
|
|
83
|
+
asyncio.create_task(discard(stream))
|
|
84
|
+
for stream in (process.stdout, process.stderr)
|
|
85
|
+
if stream is not None
|
|
86
|
+
]
|
|
87
|
+
try:
|
|
88
|
+
if process.returncode is None:
|
|
89
|
+
with suppress(ProcessLookupError):
|
|
90
|
+
process.kill()
|
|
91
|
+
await process.wait()
|
|
92
|
+
finally:
|
|
93
|
+
await asyncio.gather(*draining)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
process = await asyncio.shield(spawning)
|
|
97
|
+
if process.stdout is None or process.stderr is None:
|
|
98
|
+
raise CopilotUnavailable("Copilot login output is unavailable.")
|
|
99
|
+
readers.extend(
|
|
100
|
+
(
|
|
101
|
+
asyncio.create_task(read_output(process.stdout)),
|
|
102
|
+
asyncio.create_task(read_output(process.stderr)),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
code = await process.wait()
|
|
106
|
+
await asyncio.gather(*readers)
|
|
107
|
+
if code != 0:
|
|
108
|
+
raise CopilotUnavailable(
|
|
109
|
+
"Copilot sign-in did not complete. Retry Connect or run copilot login --device-code."
|
|
110
|
+
)
|
|
111
|
+
finally:
|
|
112
|
+
await drain_owned(asyncio.create_task(cleanup()))
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Native Copilot CLI checks without importing the Copilot SDK."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from contextlib import suppress
|
|
10
|
+
|
|
11
|
+
from .lifecycle import drain_owned
|
|
12
|
+
from .types import CopilotUnavailable
|
|
13
|
+
|
|
14
|
+
COPILOT_CLI_VERSION = "1.0.83"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_TOKEN_ENV = frozenset(
|
|
18
|
+
{
|
|
19
|
+
"COPILOT_GITHUB_TOKEN",
|
|
20
|
+
"GH_TOKEN",
|
|
21
|
+
"GITHUB_TOKEN",
|
|
22
|
+
"GITHUB_COPILOT_API_TOKEN",
|
|
23
|
+
"COPILOT_API_URL",
|
|
24
|
+
"COPILOT_SDK_AUTH_TOKEN",
|
|
25
|
+
"COPILOT_DISABLE_KEYTAR",
|
|
26
|
+
"COPILOT_OFFLINE",
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def profile_environment(source: Mapping[str, str] | None = None) -> dict[str, str]:
|
|
32
|
+
"""Keep native profile selection while excluding token and BYOK overrides."""
|
|
33
|
+
return {
|
|
34
|
+
key: value
|
|
35
|
+
for key, value in (os.environ if source is None else source).items()
|
|
36
|
+
if key.upper() not in _TOKEN_ENV
|
|
37
|
+
and not key.upper().startswith("COPILOT_PROVIDER_")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def verified_cli_path(env: Mapping[str, str]) -> str:
|
|
42
|
+
path = shutil.which("copilot", path=env.get("PATH"))
|
|
43
|
+
if path is None:
|
|
44
|
+
raise CopilotUnavailable(
|
|
45
|
+
f"Install GitHub Copilot CLI {COPILOT_CLI_VERSION}, then connect again."
|
|
46
|
+
)
|
|
47
|
+
process = await asyncio.create_subprocess_exec(
|
|
48
|
+
path,
|
|
49
|
+
"--version",
|
|
50
|
+
stdout=asyncio.subprocess.PIPE,
|
|
51
|
+
stderr=asyncio.subprocess.PIPE,
|
|
52
|
+
env=env,
|
|
53
|
+
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
|
|
54
|
+
)
|
|
55
|
+
try:
|
|
56
|
+
async with asyncio.timeout(15):
|
|
57
|
+
stdout, _ = await process.communicate()
|
|
58
|
+
except TimeoutError:
|
|
59
|
+
raise CopilotUnavailable(
|
|
60
|
+
"Copilot CLI did not report its version in time. Check the CLI installation and reconnect."
|
|
61
|
+
) from None
|
|
62
|
+
finally:
|
|
63
|
+
if process.returncode is None:
|
|
64
|
+
await drain_owned(asyncio.create_task(_close_cli_probe(process)))
|
|
65
|
+
if (
|
|
66
|
+
process.returncode != 0
|
|
67
|
+
or re.match(
|
|
68
|
+
rf"GitHub Copilot CLI {re.escape(COPILOT_CLI_VERSION)}\.?(?:\r?\n|\Z)",
|
|
69
|
+
stdout.decode("utf-8", errors="replace"),
|
|
70
|
+
)
|
|
71
|
+
is None
|
|
72
|
+
):
|
|
73
|
+
raise CopilotUnavailable(
|
|
74
|
+
f"FCC requires GitHub Copilot CLI {COPILOT_CLI_VERSION} for its pinned SDK. Install that version and reconnect."
|
|
75
|
+
)
|
|
76
|
+
return path
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def _close_cli_probe(process: asyncio.subprocess.Process) -> None:
|
|
80
|
+
with suppress(ProcessLookupError):
|
|
81
|
+
process.kill()
|
|
82
|
+
await process.communicate()
|