spacr.qt.ai.providers¶
AI chat providers through vendor command-line tools or spaCR’s hosted relay. The vendor providers invoke the user’s installed CLI; authentication and account usage follow that CLI’s configuration.
Anthropic Claude → the
claudeCLI (“Claude Code”)OpenAI ChatGPT → the
codexCLIGoogle Gemini → the
geminiCLI
A fourth, HostedProvider, needs no CLI and no account: it asks spaCR’s relay
for hosted advice. It is the default while network access is allowed unless a configured
vendor CLI is explicitly preferred (default_provider()). Configuration checks do
not guarantee that a request will succeed or that quota remains.
Each vendor-CLI provider:
is_installed() — is the CLI on PATH?
is_logged_in() — best-effort check; falls back to "assume yes if
installed" (the actual auth error surfaces on
the first stream chunk).
stream_chat() — spawn the CLI subprocess, yield stdout chunks.
For ordinary vendor-CLI requests, conversation history is flattened into one prompt with role labels. An approved Claude session resume sends the newest message with the saved session id instead. These vendor-CLI prompts have no size limit enforced by this module.
How each CLI is installed is data, not prose: INSTALL_METHODS holds
one row per way of installing it on each operating system, and both the
install_hint a screen shows and the command
spacr.qt.ai.cli_install runs are read from those rows. The GitHub CLI
is described the same way by GitHubCli, which shares the
CommandLineTool interface without being a chat provider.
Exceptions¶
The hosted request gave no advice because of a quota, network or other relay error. |
|
A provider request failed. For a vendor CLI, a non-zero exit status means its output |
Classes¶
Common interface for vendor-CLI and hosted AI chat providers. |
|
Anthropic Claude via the |
|
OpenAI ChatGPT via the |
|
A vendor command-line tool spaCR can find, install and sign in to. |
|
Google Gemini via the |
|
The GitHub CLI, |
|
spaCR's hosted assistant: advice from spaCR's relay, no account needed. |
|
One way of installing a command-line tool on one operating system. |
Functions¶
|
Save a provider choice and try to apply it to open consoles. |
|
Return providers that pass configuration checks, spaCR AI (free) first. |
|
Return the provider the AI switch uses. |
|
One line naming what a denied tool call would have done. |
|
Return the name of the provider Preferences shows as chosen. |
|
Return a cached explanation or request one from the selected provider. |
|
Look up a provider by its short id. |
|
The row that installs the GitHub CLI with conda. |
|
The GitHub CLI, described the way the AI providers are. |
|
One line saying how many free hosted-AI questions are left today. |
|
The hint for connecting a CLI that uses the user's own provider account. |
|
The free allowance of spaCR AI, as Preferences states it. |
|
Return spaCR's hosted provider. |
|
The sentence shown when the hosted provider gives no advice. |
|
The one-line install hint for tool |
|
The ways of installing tool |
|
Return every vendor CLI provider, regardless of install state. |
|
The time the free allowances reset, in this computer's local time. |
|
Name the operating system |
|
The key a fresh question's answer is saved under on this computer. |
|
A path as it is typed in a command on |
|
Request termination of active provider subprocesses and retain uncertain processes |
|
Build a |
|
What the usage bar under the AI chat box shows for a provider. |
|
Prepare a report to request or include an AI summary. |
|
Have the user's own AI summarise an anonymised report, or return |
Module Contents¶
- exception spacr.qt.ai.providers.HostedProviderRefused(message: str, reason: str = 'error')[source]¶
Bases:
ProviderFailedThe hosted request gave no advice because of a quota, network or other relay error.
A
ProviderFailed, so the console writes the message as it stands after “[AI error]”.- Parameters:
message – the sentence the user reads.
reason –
"quota","network"or"error", fromspacr.crashreport.RelayRefused, kept on the exception asreason.
Keep the reason; the message is the whole text shown.
- exception spacr.qt.ai.providers.ProviderFailed(cli: str, exit_status: int, output_tail: str, login_command: str = '')[source]¶
Bases:
RuntimeErrorA provider request failed. For a vendor CLI, a non-zero exit status means its output is an error message rather than an answer.
The three vendor CLIs report a failure the way any command-line tool does: a line on stdout or stderr, then a non-zero exit. A signed-out
claudeprintsNot logged in · Please run /loginand exits 1. In GitHub #117 an expired one printedFailed to authenticate: OAuth session expired and could not be refreshed. Streamed as if it were a reply, that line was shown as spaCR AI’s answer to a crash, and it was filed into GitHub issues as “spaCR AI’s analysis of this error”.- Parameters:
cli – the executable that failed, for the message.
exit_status – its exit status.
output_tail – the last lines it printed, already stripped.
login_command – the command that signs in to this provider, or
""when the caller did not say which provider this was.
- Variables:
exit_status – the exit status, for a caller that needs the number.
output_tail – the quoted output, for a caller that needs the text.
Build the message a user reads after
[AI error].
- class spacr.qt.ai.providers.ChatProvider[source]¶
Bases:
CommandLineTool,abc.ABCCommon interface for vendor-CLI and hosted AI chat providers.
Subclasses set the
name/label/cli_name/install_hint/login_commandclass attributes and implementstream_chat().- Variables:
name – short id (“claude” / “codex” / “gemini” / “spacr_hosted”).
label – human-readable label shown in the UI.
cli_name – executable expected on
PATH.install_hint – shell one-liner suggested for installation, built from
install_methods.login_command – shell one-liner the user runs to authenticate.
Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- cancel_stream() None[source]¶
Request termination of the running subprocess, if any, with bounded waits and a kill attempt after a timeout.
Terminating the process is what unblocks a stream that is waiting on stdout; a Python flag is only checked between chunks, and the next chunk may never arrive.
- is_configured() bool[source]¶
Return True when the install and best-effort login checks pass. The default login check assumes an installed CLI is signed in.
- is_logged_in() bool[source]¶
Best-effort — override per provider if a cheap check exists.
Default: assume yes when installed. The real auth error will surface as a normal subprocess failure on the first send.
- class spacr.qt.ai.providers.ClaudeCliProvider[source]¶
Bases:
ChatProviderAnthropic Claude via the
claude(Claude Code) CLI.Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- allow_once(denials: List[Dict], session_id: str) List[str][source]¶
Let the next
stream_chat()resumesession_idwith these tool calls allowed, for that one reply only.- Parameters:
denials – entries of
last_permission_denialsthe user approved.session_id – the session they were denied in.
- Returns:
the
--allowedToolsrules that will be passed.
- stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]¶
Stream a chat completion from the
claudeCLI.- Parameters:
messages – conversation history as
{role, content}dicts.system – optional system prompt appended via
--append-system-prompt.model – optional model override passed via
--model. When None the current response-speed setting supplies one.
- Returns:
iterator yielding stdout text chunks.
- class spacr.qt.ai.providers.CodexCliProvider[source]¶
Bases:
ChatProviderOpenAI ChatGPT via the
codexCLI.Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]¶
Stream a chat completion from the
codexCLI.- Parameters:
messages – conversation history as
{role, content}dicts.system – optional system prompt folded into the prompt body.
model – optional model override passed via
--model. When None the current response-speed setting supplies one.
- Returns:
iterator yielding stdout text chunks.
- class spacr.qt.ai.providers.CommandLineTool[source]¶
A vendor command-line tool spaCR can find, install and sign in to.
The AI providers and the GitHub CLI share this, so “is it there?” and “is it signed in?” are asked one way for all four.
- Variables:
name – short id, and the key of
INSTALL_METHODS.label – human-readable label shown in the UI.
cli_name – executable expected on
PATH.install_methods – the ways of installing it on this system, tried in order.
install_hint – the one-line hint built from
install_methods.login_command – the command a user runs to sign in.
status_command – a command that exits 0 when the tool is signed in, or
()when the tool has none.
- check_signed_in() bool | None[source]¶
Ask the tool itself whether it is signed in.
Runs
status_commandwith its output discarded – for the GitHub CLI that output is the token – so call it off the GUI thread.- Returns:
Truewhen the command exits 0,Falsewhen it exits otherwise, cannot be started or runs pastSTATUS_TIMEOUT_S, andNonewhen the tool has no status command to ask.
- class spacr.qt.ai.providers.GeminiCliProvider[source]¶
Bases:
ChatProviderGoogle Gemini via the
geminiCLI.Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]¶
Stream a chat completion from the
geminiCLI.- Parameters:
messages – conversation history as
{role, content}dicts.system – optional system prompt folded into the prompt body.
model – optional model override passed via
-m. When None the current response-speed setting supplies one.
- Returns:
iterator yielding stdout text chunks.
- class spacr.qt.ai.providers.GitHubCli[source]¶
Bases:
CommandLineToolThe GitHub CLI,
gh: installed and signed in like a provider.spaCR reads a token from it (
spacr.qt.ai.github_auth) to file an issue without a browser round-trip. Signing in isgh auth login;gh auth tokenexits 0 exactly when there is a token to read.
- class spacr.qt.ai.providers.HostedProvider[source]¶
Bases:
ChatProviderspaCR’s hosted assistant: advice from spaCR’s relay, no account needed.
It sends only what
_hosted_request()keeps, the anonymised error summary and the question, to the relay’s/diagnoseroute throughspacr.crashreport.diagnose_remotely(). The answer is generated advice and is labelled as such. It is the default while network access is allowed unless a configured vendor CLI is explicitly preferred (default_provider()). It is unavailable when the network is forbidden (spacr.crashreport.network_allowed()).Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]¶
Ask the relay and yield its advice, then a line saying what it is.
- Parameters:
messages – conversation history as
{role, content}dicts; only the anonymised summary and the newest question leave this computer.system – ignored; the relay holds spaCR’s own prompt.
model – ignored; the relay chooses its free model.
- Returns:
iterator yielding the answer.
- Raises:
HostedProviderRefused – when the request is refused because of a quota, a forbidden or unavailable network, or another relay error.
- class spacr.qt.ai.providers.InstallMethod[source]¶
Bases:
NamedTupleOne way of installing a command-line tool on one operating system.
- Variables:
needs – executables that must already be on
PATHfor this way to work, such as("npm",).command – the command as a user would type it.
runner – how
spacr.qt.ai.cli_installstarts it:"exec"runs the command’s first word as a program with the rest as its arguments,"shell"hands the command tobash -o pipefail -c(for a pipe such ascurl ... | bash), and"cmd"runs it ascmd /c "<command>"on Windows.
- spacr.qt.ai.providers.apply_provider_choice(name: str) bool[source]¶
Save a provider choice and try to apply it to open consoles.
The choice is saved even when its configuration checks fail. Open consoles are given the chosen provider when those checks pass, otherwise the fallback from
default_provider(), which can be another vendor CLI orNonewhen the network is forbidden. Choosing"none"unsets the provider without a fallback. Exceptions while updating consoles are ignored.- Parameters:
name – a name from
PROVIDER_CHOICES.- Returns:
whether the chosen provider passed configuration checks, regardless of whether updating every console succeeded.
- spacr.qt.ai.providers.configured_providers() List[ChatProvider][source]¶
Return providers that pass configuration checks, spaCR AI (free) first.
The hosted provider leads whenever the network is allowed. A vendor CLI follows when its install and best-effort login checks pass; the current vendor classes treat an executable on
PATHas configured. An explicit saved"none"choice returns an empty list without discovery. These checks do not confirm relay capacity or a working authenticated CLI session.
- spacr.qt.ai.providers.default_provider(preferred: str = '') ChatProvider | None[source]¶
Return the provider the AI switch uses.
- Parameters:
preferred – the provider Preferences names, or
"".- Returns:
the matching
preferredprovider fromconfigured_providers(); otherwise the first provider in that list, orNoneif it is empty. The hosted provider leads while the network is allowed; otherwise vendor CLIs are considered in their registered order. An explicit"none"returnsNonewithout provider discovery. This is a configuration choice, not a guarantee of authentication, remaining quota or request success.
- spacr.qt.ai.providers.describe_tool_call(denial: Dict) str[source]¶
One line naming what a denied tool call would have done.
- Parameters:
denial – one
permission_denialsentry.- Returns:
the command for a shell call, otherwise the tool’s name and its path, address or pattern.
- spacr.qt.ai.providers.effective_provider_name(preferred: str = '') str[source]¶
Return the name of the provider Preferences shows as chosen.
- Parameters:
preferred – the stored preference;
""(never chosen) means spaCR AI (free).- Returns:
a name from
PROVIDER_CHOICES.
- spacr.qt.ai.providers.explain_failure(summary: Dict, provider: ChatProvider | None, timeout: float | None = None) Dict[source]¶
Return a cached explanation or request one from the selected provider.
After a local cache miss, spaCR AI (free) asks the relay’s
/diagnosewith the anonymised summary andspacr.crashreport.EXPLAIN_QUESTION; a vendor CLI answersspacr.crashreport.ai_summary_prompt()here (write_report_summary()). The returned text can be used for the console and the report’sai_summary. After a local miss, this function attempts one provider request; a relay reply can itself be cached. Blocks, so call it off the GUI thread.- Parameters:
summary – a summary from
spacr.crashreport.public_summary().provider – the active provider, or
None.timeout – seconds a vendor CLI may take; defaults to
spacr.crashreport.AI_SUMMARY_TIMEOUT_S.
- Returns:
{"text": explanation or "", "tier": "best"|"light"|"", "refused": "" or "quota"|"network"|"error"|"none"}. A local cache hit or a completed provider call also includes"cause"."saved"is"local"for a local hit,"relay"for reused non-empty relay advice, or""for a fresh hosted reply; other branches omit it.
With no provider, return
"refused": "none"without consulting the local cache. Otherwise a valid explanation fromspacr.crashreport.saved_explanation()is reused before any request, for every provider. Cache entries can expire or be evicted, and failed saves are ignored, so the same error can require another request.
- spacr.qt.ai.providers.get_provider(name: str) ChatProvider | None[source]¶
Look up a provider by its short id.
- Parameters:
name – provider id (
"claude","codex","gemini"orHOSTED_PROVIDER_NAME).- Returns:
the matching provider, or
Noneif no such id.
- spacr.qt.ai.providers.gh_conda_row(prefix: str, platform: str) InstallMethod[source]¶
The row that installs the GitHub CLI with conda.
- Parameters:
prefix – spaCR’s own environment,
sys.prefix.platform – a
sys.platformvalue, for quotingprefix.
- Returns:
when
prefixis a conda environment, a command that names it with--prefixand addsghwith--freeze-installed, so the command on screen says which environment changes and conda does not update the packages spaCR runs on; otherwise conda’s plain command, which installs into whichever environment conda treats as active.
- spacr.qt.ai.providers.github_cli() GitHubCli[source]¶
The GitHub CLI, described the way the AI providers are.
- spacr.qt.ai.providers.hosted_allowance_text(allowance: Dict | None) str[source]¶
One line saying how many free hosted-AI questions are left today.
- Parameters:
allowance – what
spacr.crashreport.fetch_allowance()returns, orNonewhen it is not known.- Returns:
the translated line, or
""when nothing is known.
- spacr.qt.ai.providers.hosted_cli_hint() str[source]¶
The hint for connecting a CLI that uses the user’s own provider account.
- Returns:
the translated sentence.
- spacr.qt.ai.providers.hosted_daily_allowance_text() str[source]¶
The free allowance of spaCR AI, as Preferences states it.
- Returns:
the translated sentence.
- spacr.qt.ai.providers.hosted_provider() HostedProvider[source]¶
Return spaCR’s hosted provider.
- spacr.qt.ai.providers.hosted_refusal_message(reason: str, scope: str = '', resets_at=None) str[source]¶
The sentence shown when the hosted provider gives no advice.
- Parameters:
reason –
"quota","network"or anything else.scope – for a quota refusal,
"install"(this install’s daily questions are used),"global"(spaCR’s shared free capacity is) or"ip".resets_at – when the limit resets; defaults to the next 00:00 UTC.
- Returns:
the translated sentence; a quota refusal also carries
hosted_cli_hint().
- spacr.qt.ai.providers.install_hint_for(name: str, platform: str) str[source]¶
The one-line install hint for tool
nameonplatform.Built from the same rows the Install button runs, so the two cannot drift apart. On macOS and Linux the alternatives are joined by three spaces and then
# or, which a POSIX shell reads as a comment; on Windows only the first row is shown, becausecmdhas no such comment and would hand the words after#to the installer as arguments.- Parameters:
name – a key of
INSTALL_METHODS.platform – a
sys.platformvalue.
- Returns:
the hint, or
""when the tool has no rows.
- spacr.qt.ai.providers.install_methods_for(name: str, platform: str) Tuple[InstallMethod, ...][source]¶
The ways of installing tool
nameonplatform, in order.- Parameters:
name – a key of
INSTALL_METHODS, such as"claude".platform – a
sys.platformvalue.
- Returns:
the rows, or
()for a tool spaCR cannot install.
- spacr.qt.ai.providers.list_providers() List[ChatProvider][source]¶
Return every vendor CLI provider, regardless of install state.
The hosted provider is not one of them: it has nothing to install or sign in to.
hosted_provider()returns it.
- spacr.qt.ai.providers.local_reset_time(resets_at=None) str[source]¶
The time the free allowances reset, in this computer’s local time.
- Parameters:
resets_at – an aware datetime or an ISO-8601 string; defaults to the next 00:00 UTC.
- Returns:
HH:MM, followed by the weekday when it is not today.
- spacr.qt.ai.providers.platform_family(platform: str) str[source]¶
Name the operating system
INSTALL_METHODSis keyed by.- Parameters:
platform – a
sys.platformvalue.- Returns:
"win32"for any Windows value,"darwin"for macOS, and"linux"for everything else.
- spacr.qt.ai.providers.question_cache_key(messages: List[Dict]) str[source]¶
The key a fresh question’s answer is saved under on this computer.
Only a conversation consisting of a single user message is eligible. A message containing
Traceback (most recent call last)is excluded even when short. The original message, including console context, code and active-app metadata, must fit withinspacr.crashreport.QUESTION_CACHE_CONTEXT_MAXcharacters.spacr.crashreport.question_key()validates and hashes the cleaned question and active app; the key does not depend on the provider.- Parameters:
messages – conversation history as
{role, content}dicts.- Returns:
the key, or
""when the answer must not be saved.
- spacr.qt.ai.providers.quote_path(path: str, platform: str) str[source]¶
A path as it is typed in a command on
platform.- Parameters:
path – the path.
platform – a
sys.platformvalue.
- Returns:
on Windows the path in double quotes when it holds a space, else unchanged; elsewhere quoted for a POSIX shell.
- spacr.qt.ai.providers.terminate_all_streams() int[source]¶
Request termination of active provider subprocesses and retain uncertain processes for a later retry.
- Returns:
Number of subprocesses for which termination was requested.
- spacr.qt.ai.providers.tool_rule(denial: Dict) str[source]¶
Build a
--allowedToolsrule from a denied tool call.A shell command is allowed as written,
Bash(gh auth status); a web fetch for its host; any other tool by its name.- Parameters:
denial – one
permission_denialsentry.- Returns:
the rule, or
""when the entry names no tool.
- spacr.qt.ai.providers.usage_bar(provider_name: str | None, allowance: Dict | None = None) Tuple[int | None, str][source]¶
What the usage bar under the AI chat box shows for a provider.
For spaCR AI (free) the bar is the user’s share of today’s shared free capacity,
neurons_left / share_neurons, from the relay’s dynamic allowance. For a vendor CLI there is no reliable usage source (CLI_USAGE_TEXT), so the bar is hidden and only a line is shown.- Parameters:
provider_name – the selected provider’s name, or
None.allowance – what
spacr.crashreport.fetch_allowance()returned, for spaCR AI.
- Returns:
(percent or None to hide the bar, translated text).
- spacr.qt.ai.providers.with_ai_summary(summary: Dict, provider: ChatProvider | None) Dict[source]¶
Prepare a report to request or include an AI summary.
For spaCR AI (free), set
summarize: trueso report submission can request a relay-written summary; this function does not contact the relay itself. A vendor CLI writes the summary here (write_report_summary()) and a non-empty result is included asai_summary. With no provider, or when the CLI gives no text, the copied report data is unchanged.- Parameters:
summary – a summary from
spacr.crashreport.public_summary().provider – the active provider, or
None.
- Returns:
a new summary dict.
- spacr.qt.ai.providers.write_report_summary(summary: Dict, provider: ChatProvider | None, timeout: float | None = None) str[source]¶
Have the user’s own AI summarise an anonymised report, or return
"".The provider is given
spacr.crashreport.ai_summary_prompt()as a single user message, and a worker thread collects its reply. If the worker is still running aftertimeoutseconds, cancellation is requested and""is returned; a provider exception or an empty reply also gives"". Blocks, so call it off the GUI thread.- Parameters:
summary – a summary from
spacr.crashreport.public_summary().provider – a vendor CLI provider, or
None.timeout – seconds to wait; defaults to
spacr.crashreport.AI_SUMMARY_TIMEOUT_S.
- Returns:
the summary text, scrubbed and cut to
spacr.crashreport.MAX_AI_SUMMARY_CHARS.