Shared client for the multi-AI bridge (ask-chatgpt / ask-gemini / ask-ais)
| Tier | tools |
| Role | unclassified (baselined) |
| Path | crates/tools/ask-ai-core |
| Edition | 2021 |
| Targets | tools_ask_ai_core |
| Public items | 43 across 1 module |
| Tests | 31 |
What it is for
Shared client for the multi-AI bridge.
Exposes:
AiProvider— the supported AI backends.AskRequest/AskResponse— the request/response shape.ask()— synchronous single-provider call against the bridge HTTP
endpoint at /api/ask-{chatgpt,gemini}.
ask_many()— parallel fan-out to multiple providers via OS threads
(no async runtime required). Returns per-provider AskResponse entries; the caller interprets success/failure per the Gate 1.5 Round 1 Q4 decision (tools-ask-ais exit code semantics).
Capabilities
verb:ask
Shared client for the multi-AI bridge.
| Item |
|---|
fn ask(api_url : & str, api_key : & str, req : & AskRequest) -> AskResponse |
fn ask_with_recovery(api_url : & str, api_key : & str, req : & AskRequest) -> AskResponse |
fn ask_many(api_url : & str, api_key : & str, providers : & AiProvider, req : & AskRequest,) -> Vec <AskResponse> |
verb:busy
Shared client for the multi-AI bridge.
| Item |
|---|
fn busy_retry_after(body : & serde_json::Value, max_wait_secs : u64) -> Option <Duration> |
verb:constants
Shared client for the multi-AI bridge.
| Item |
|---|
pub const DEFAULT_API_URL: & str |
pub const DEFAULT_API_KEY: & str |
pub const DEFAULT_TIMEOUT_SECS: u64 |
pub const COLLECT_TIMEOUT_SECS: u64 |
pub const DEFAULT_MAX_BUSY_WAIT_SECS: u64 |
pub const BUSY_POLL_CEILING_SECS: u64 |
verb:default
Shared client for the multi-AI bridge.
| Item |
|---|
fn default_api_url() -> String |
fn default_api_key() -> String |
verb:encode
Shared client for the multi-AI bridge.
| Item |
|---|
fn encode_image(path : & str) -> Result <ImageAttachment, AskError> |
verb:max
Shared client for the multi-AI bridge.
| Item |
|---|
fn max_busy_wait_secs() -> u64 |
verb:session
Shared client for the multi-AI bridge.
| Item |
|---|
fn session_id() -> String |
AiProvider
Shared client for the multi-AI bridge.
| Item |
|---|
pub enum AiProvider |
AiProvider :: fn as_str(self) -> & 'static str |
AiProvider :: fn command_type(self) -> & 'static str |
AiProvider :: fn endpoint_path(self) -> & 'static str |
AiProvider :: fn display_label(self) -> & 'static str |
AiProvider :: fn parse(s : & str) -> Option <Self> |
AskError
Shared client for the multi-AI bridge.
| Item |
|---|
pub enum AskError |
AskRequest
Shared client for the multi-AI bridge.
| Item |
|---|
pub struct AskRequest |
AskResponse
Shared client for the multi-AI bridge.
| Item |
|---|
pub struct AskResponse |
AskResponse :: fn ok(provider : AiProvider, text : String, duration_ms : u128) -> Self |
AskResponse :: fn error(provider : AiProvider, status : AskStatus, error : String, duration_ms : u128,) -> Self |
fn format_single(resp : & AskResponse) -> Result <String, String> |
fn format_labeled(responses : & AskResponse) -> String |
fn format_json(responses : & AskResponse) -> String |
fn exit_code_for(responses : & AskResponse) -> i32 |
AskStatus
Shared client for the multi-AI bridge.
| Item |
|---|
pub enum AskStatus |
AskStatus :: fn as_str(self) -> & 'static str |
AskStatus :: fn is_success(self) -> bool |
fn is_collectible(status : AskStatus, error : Option <& str>) -> bool |
ImageAttachment
Shared client for the multi-AI bridge.
| Item |
|---|
pub struct ImageAttachment |
precheck (other)
Advisory client-side DLP pre-check (Sprint 3.6 T9).
| Item |
|---|
pub const POLICY_ENV: & str |
fn load_policy_from_path(path : Option <& str>) -> Result <ScreenPolicy, String> |
fn load_policy() -> Result <ScreenPolicy, String> |
fn resolve_prompt_arg(message : & str) -> Result <String, String> |
CliGateDecision
Advisory client-side DLP pre-check (Sprint 3.6 T9).
| Item |
|---|
pub enum CliGateDecision |
Precheck
Advisory client-side DLP pre-check (Sprint 3.6 T9).
| Item |
|---|
pub enum Precheck |
fn precheck_prompt(prompt : & str, policy : & ScreenPolicy) -> Precheck |
fn cli_precheck(prompt : & str, force_send : bool) -> CliGateDecision |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
tools_ask_ai_core
precheck
Public surface
`crate root`
| Item | What it is |
|---|---|
pub const DEFAULT_API_URL: & str | — |
pub const DEFAULT_API_KEY: & str | — |
pub const DEFAULT_TIMEOUT_SECS: u64 | 300s: consensus-scale prompts on slow provider modes routinely outlast the old 180s (three timeouts on 2026-08-08 alone) |
pub const COLLECT_TIMEOUT_SECS: u64 | Budget for the post-timeout collect retry (extension-side poll). |
pub const DEFAULT_MAX_BUSY_WAIT_SECS: u64 | How long to keep waiting for a provider tab another session holds, before giving up |
pub const BUSY_POLL_CEILING_SECS: u64 | Longest single sleep between retries while a provider tab is held |
pub enum AiProvider | — |
AiProvider :: fn as_str(self) -> & 'static str | — |
AiProvider :: fn command_type(self) -> & 'static str | The extension-side command type string. |
AiProvider :: fn endpoint_path(self) -> & 'static str | The bridge HTTP endpoint path (sans scheme/host). |
AiProvider :: fn display_label(self) -> & 'static str | Display label used in multi-provider output (=== CHATGPT ===). |
AiProvider :: fn parse(s : & str) -> Option <Self> | — |
pub struct AskRequest | — |
pub struct ImageAttachment | — |
pub enum AskStatus | Status of a single-provider ask. |
AskStatus :: fn as_str(self) -> & 'static str | — |
AskStatus :: fn is_success(self) -> bool | — |
pub struct AskResponse | — |
AskResponse :: fn ok(provider : AiProvider, text : String, duration_ms : u128) -> Self | — |
AskResponse :: fn error(provider : AiProvider, status : AskStatus, error : String, duration_ms : u128,) -> Self | — |
pub enum AskError | — |
fn encode_image(path : & str) -> Result <ImageAttachment, AskError> | Load and base64-encode an image, returning an ImageAttachment ready to attach to an AskRequest |
fn default_api_url() -> String | Read the bridge URL from ASK_CHATGPT_API env var, falling back to the default localhost bridge. |
fn default_api_key() -> String | Read the bridge API key from ASK_CHATGPT_KEY env var, falling back to the dev default. |
fn session_id() -> String | Synchronously ask a single AI provider |
fn busy_retry_after(body : & serde_json::Value, max_wait_secs : u64) -> Option <Duration> | How long to wait before retrying, given a 409 body — or None if this is not a provider-busy refusal and so not something waiting can fix |
fn max_busy_wait_secs() -> u64 | The ceiling on total time spent waiting out other sessions. |
fn ask(api_url : & str, api_key : & str, req : & AskRequest) -> AskResponse | — |
fn is_collectible(status : AskStatus, error : Option <& str>) -> bool | True when a failed ask is worth a collect retry: the send reached the provider and generation likely FINISHED after our clock ran out — a timeout, a gateway 504, or a bridge-side timeout message |
fn ask_with_recovery(api_url : & str, api_key : & str, req : & AskRequest) -> AskResponse | ask plus the timeout-recovery pass: when the ask fails collectibly (see is_collectible), one collect_response attempt recovers the answer stranded in the provider tab |
fn ask_many(api_url : & str, api_key : & str, providers : & AiProvider, req : & AskRequest,) -> Vec <AskResponse> | Fan out to multiple providers in parallel (one OS thread per provider) |
fn format_single(resp : & AskResponse) -> Result <String, String> | Format a single-provider response as plain text (for ask-chatgpt / tools-ask-gemini) |
fn format_labeled(responses : & AskResponse) -> String | Format multi-provider responses as labeled blocks (for tools-ask-ais default mode) |
fn format_json(responses : & AskResponse) -> String | Format multi-provider responses as a single JSON object keyed by provider name (for tools-ask-ais --json) |
fn exit_code_for(responses : & AskResponse) -> i32 | Pick an appropriate process exit code for tools-ask-ais responses per Gate 1.5 Round 1 Q4: total success or partial success → 0, total failure → 1. |
`precheck`
| Item | What it is |
|---|---|
pub const POLICY_ENV: & str | Env var naming the local policy TOML for the client pre-check (same file format the server loads; see the platform-privacy-scan-api README). |
pub enum Precheck | Outcome of the local advisory pre-check. |
fn load_policy_from_path(path : Option <& str>) -> Result <ScreenPolicy, String> | Load the pre-check policy from an explicit path, or the built-in default policy when path is None |
fn load_policy() -> Result <ScreenPolicy, String> | Load the pre-check policy from the DLP_GATE_POLICY env var |
fn precheck_prompt(prompt : & str, policy : & ScreenPolicy) -> Precheck | Run the advisory pre-check on prompt against policy |
pub enum CliGateDecision | The full CLI-side decision: load the local policy (env), pre-check, and fold in --force-send |
fn cli_precheck(prompt : & str, force_send : bool) -> CliGateDecision | Apply the advisory pre-check for a CLI invocation |
fn resolve_prompt_arg(message : & str) -> Result <String, String> | Resolve a CLI prompt argument: - reads the prompt from STDIN |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
ScreenPolicy | infrastructure_dlp_detect::ScreenPolicy |
Boundary
Reaches into infrastructure.
Shares tier tools with 84 other crates: tools-advisory-reach, tools-archive-guard, tools-artifact-scaffold, tools-ask-ais, tools-ask-gemini, tools-book, tools-book-report, tools-boundary-scan, … (84 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | tools |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/tools/ask-ai-core |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_tools["tools"] --> n_infrastructure["infrastructure"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `infrastructure-dlp-detect` | infrastructure | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
base64 | ^0.22 | — | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
thiserror | ^2 | — | no | always |
ureq | ^2.10 | — | no | always |
Development. None.
Build. None.
Depended on by. 2 workspace crates.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_tools_ask_ais["tools-ask-ais"] -->|uses| SELF n_tools_ask_gemini["tools-ask-gemini"] -->|uses| SELF SELF["tools-ask-ai-core"] SELF -->|runtime| n_infrastructure_dlp_detect["infrastructure-dlp-detect"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | tools_ask_ai_core | `src/lib.rs` |
Error model
| Error type | Named by |
|---|---|
AskError | encode_image |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
ASK_AI_MAX_WAIT_SECS | src/lib.rs |
ASK_AI_SESSION_ID | src/lib.rs |
ASK_CHATGPT_API | src/lib.rs |
ASK_CHATGPT_KEY | src/lib.rs |
Related capabilities
2 workspace crates depend on this one: tools-ask-ais, tools-ask-gemini.
Verification
| Kind | Count |
|---|---|
| Unit tests | 31 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 26 | 0 | 19 |
precheck | 8 | 0 | 6 |
What the tests establish, by name:
a_lease_about_to_lapse_is_not_over_waited—src/lib.rsa_long_lease_is_polled_rather_than_slept_out—src/lib.rsa_missing_expiry_still_produces_a_short_wait_rather_than_none—src/lib.rsa_nonsense_expiry_cannot_park_the_caller—src/lib.rsa_session_id_is_stable_within_one_process_and_overridable—src/lib.rsask_status_success_semantics—src/lib.rscollectibility_covers_exactly_the_stranded_answer_cases—src/lib.rsencode_image_rejects_missing_file—src/lib.rsencode_image_rejects_unsupported_extension—src/lib.rsexit_code_nonzero_on_total_failure—src/lib.rsexit_code_zero_on_partial_success—src/lib.rsexit_code_zero_on_total_success—src/lib.rsformat_json_is_keyed_by_provider—src/lib.rsformat_labeled_includes_both_provider_sections—src/lib.rsformat_labeled_marks_failures_explicitly—src/lib.rsformat_single_error_returns_error_message—src/lib.rsformat_single_success_returns_text—src/lib.rsonly_a_busy_refusal_is_worth_waiting_on—src/lib.rsprovider_metadata_is_consistent—src/lib.rsprovider_parsing—src/lib.rsauto_redact_policy_is_advisory_client_never_mutates—src/precheck.rsblocked_prompt_summary_is_masked_only_and_actionable—src/precheck.rsclean_prompt_passes—src/precheck.rscli_precheck_clean_prompt_proceeds—src/precheck.rscli_precheck_force_send_defers_to_server—src/precheck.rscli_precheck_refuses_blocked_prompt—src/precheck.rsemail_prompt_is_advisory_not_blocking—src/precheck.rsload_policy_defaults_when_no_path—src/precheck.rsload_policy_reports_invalid_toml—src/precheck.rsload_policy_reports_missing_file—src/precheck.rs- _… 1 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 29 | 43 |
Public modules with a //! block | 1 | 1 |
pie showData
title Public items with rustdoc
"Documented" : 29
"No rustdoc detected" : 14
Metrics
| Metric | Value |
|---|---|
| Rust source files | 2 |
| Source lines | 1306 |
| Code lines | 978 |
| Public API items | 43 |
| Public modules | 1 |
| Tests | 31 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 6 |
| Workspace reverse dependencies | 2 |
pie showData
title Public API by kind
"constant" : 7
"enum" : 5
"function" : 19
"method" : 9
"struct" : 3
pie showData
title Rust source composition
"Code" : 978
"Blank or comment" : 328
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.