Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ See the full history in [CHANGELOG.md](CHANGELOG.md).
| Ollama | Cookies / API Key | Usage, Cloud Models, Pace windows |
| Azure OpenAI | API Key | Deployment |
| T3 Chat | Cookies / cURL | Base, Overage |
| TinyApi | Browser cookies / manual Cookie header | Available credits |
| TypeSafe | Browser cookies / manual Cookie header | Billing-cycle spend, balance, expiring credits |
| OpenRouter | API Key | Credits |
| JetBrains AI | Local Config | Usage |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ fn cookie_source_provider(provider_id: &str) -> Option<codexbar::core::ProviderI
"museai" => ProviderId::MuseAI,
"lithosai" => ProviderId::LithosAI,
"langdock" => ProviderId::Langdock,
"tinyapi" => ProviderId::TinyApi,
_ => return None,
})
}
Expand Down Expand Up @@ -892,6 +893,13 @@ pub fn cookie_source_options_for(provider_id: &str, lang: Language) -> Vec<Cooki
cookie_option(lang, "manual", None),
cookie_option(lang, "off", None),
],
// Upstream's TinyApi cookie source: automatic browser import,
// a pasted header, or off.
"tinyapi" => vec![
cookie_option(lang, "auto", None),
cookie_option(lang, "manual", None),
cookie_option(lang, "off", None),
],
_ => Vec::new(),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,44 @@ fn workbuddy_exposes_a_cookie_source_picker_and_routes_each_choice() {
assert_eq!(ctx.source_mode, SourceMode::Cli);
assert!(ctx.manual_cookie_header.is_none());
}

/// TinyApi is web-only: the picker chooses the session, a stored header
/// always wins, an empty manual source fails closed without reading a browser,
/// and off reaches the provider as Cli (its "cookies are disabled" error).
#[test]
fn tinyapi_exposes_a_cookie_source_picker_and_routes_each_choice() {
let settings = Settings::default();
assert_eq!(
super::provider_cookie_source_lookup(&settings, "tinyapi").as_deref(),
Some("auto")
);
let values: Vec<_> = super::cookie_source_options_for("tinyapi", Language::English)
.into_iter()
.map(|option| option.value)
.collect();
assert_eq!(values, vec!["auto", "manual", "off"]);

let cookie = "session=fixture";
for source in ["auto", "manual"] {
let ctx = session_context(ProviderId::TinyApi, Some(source), "auto", Some(cookie));
assert!(
matches!(ctx.source_mode, SourceMode::Auto | SourceMode::Web),
"{source}"
);
assert_eq!(
ctx.manual_cookie_header.as_deref(),
Some(cookie),
"{source}"
);
assert!(!ctx.manual_cookie_missing, "{source}");
}

let ctx = session_context(ProviderId::TinyApi, Some("manual"), "auto", None);
assert_eq!(ctx.source_mode, SourceMode::Web);
assert!(ctx.manual_cookie_header.is_none());
assert!(ctx.manual_cookie_missing);

let ctx = session_context(ProviderId::TinyApi, Some("off"), "auto", Some(cookie));
assert_eq!(ctx.source_mode, SourceMode::Cli);
assert!(ctx.manual_cookie_header.is_none());
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const MAC_PARITY_ACCENTS: Array<[string, string]> = [
["sakana", "#2975db"],
["stepfun", "#2196f2"],
["t3chat", "#f56647"],
["tinyapi", "#f97316"],
["typesafe", "#111111"],
["v0", "#111111"],
["warp", "#938bb4"],
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-tauri/src/components/providers/providerIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import sub2api from "./icons/ProviderIcon-sub2api.svg?raw";
import synthetic from "./icons/ProviderIcon-synthetic.svg?raw";
import clawrouter from "./icons/ProviderIcon-clawrouter.svg?raw";
import t3chat from "./icons/ProviderIcon-t3chat.svg?raw";
import tinyapi from "./icons/ProviderIcon-tinyapi.svg?raw";
import venice from "./icons/ProviderIcon-venice.svg?raw";
import vercel from "./icons/ProviderIcon-vercel.svg?raw";
import vertexai from "./icons/ProviderIcon-vertexai.svg?raw";
Expand Down Expand Up @@ -179,6 +180,7 @@ const RAW: Record<string, string> = {
synthetic: tint(synthetic),
clawrouter: tint(clawrouter),
t3chat: tint(t3chat),
tinyapi: tint(tinyapi),
venice: tint(venice),
vercel: tint(vercel),
vertexai: tint(vertexai),
Expand Down Expand Up @@ -243,6 +245,7 @@ export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
ollama: { id: "ollama", brandColor: "#888888", fallbackLetter: "○", svgPath: RAW.ollama },
azureopenai: { id: "azureopenai", brandColor: "#0078d4", fallbackLetter: "A" },
t3chat: { id: "t3chat", brandColor: "#f56647", fallbackLetter: "T", svgPath: RAW.t3chat },
tinyapi: { id: "tinyapi", brandColor: "#f97316", fallbackLetter: "T", svgPath: RAW.tinyapi },
opencode: { id: "opencode", brandColor: "#3b82f6", fallbackLetter: "○", svgPath: RAW.opencode },
opencodego: { id: "opencodego", brandColor: "#3b82f6", fallbackLetter: "○", svgPath: RAW.opencodego },
openrouter: { id: "openrouter", brandColor: "#6467f2", fallbackLetter: "R", svgPath: RAW.openrouter },
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/test/providerCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const TEST_PROVIDER_CATALOG: Array<[string, string]> = [
["huggingface", "Hugging Face"],
["helmcode", "Helmcode"],
["v0", "v0"],
["tinyapi", "TinyApi"],
["typesafe", "TypeSafe"],
["llmproxy", "LLM Proxy"],
["chutes", "Chutes"],
Expand Down
30 changes: 30 additions & 0 deletions docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,36 @@ to `https://muse.ai/` with the session cookies and `Sec-Fetch-*` headers.
- A 401, a 403 or a redirect to `auth.muse.ai` is reported as an expired
session. Response bodies and cookies are never logged or echoed in errors.

### TinyApi

TinyApi reads available credit balance from the signed-in dashboard using
`GET https://tinyapi.rest/api/user/credits` with session cookies or a manual
Cookie header. The response returns a JSON object with a `data.totalAvailable`
field containing the available credits as a number.

- The cookie source in Settings → Providers → TinyApi picks the session and
defaults to Automatic. Automatic imports the `tinyapi.rest` cookies from the
browser you select (Chrome, Edge, Brave, Firefox); Manual accepts a pasted
Cookie header. Without an imported or pasted header, Automatic tries each
detected browser's `tinyapi.rest` cookies in turn, and Manual fails closed.
Off reads nothing.
- Requests send the Cookie header and expect a JSON response with `success: true`
and `data.totalAvailable` as a finite non-negative number. HTTP 401 or 403
indicates an expired session; the provider skips to the next browser session
if Automatic has more candidates, and reports "TinyApi session expired. Sign
in again or paste a fresh Cookie header." if none remain. HTTP 429 is
rate-limited, and 500+ is unavailable. Other HTTP codes are reported with
their status code. Bodies over 256 KiB or without a valid balance report a
missing balance, and non-JSON bodies report invalid JSON. Response bodies and
cookies are never logged or echoed in errors.
- The card shows a single "Available credits" detail row with the balance
formatted to up to two decimal places and the "credits" unit (e.g. "125.5
credits" or "100 credits"). The menu bar reads "Available credits: <value>"
from an informational window that is hidden on the card. Login method is
shown as "Browser session".
- No multi-account support. A transport or timeout error while reading the
body keeps its cause instead of being reported as an invalid response.

### WorkBuddy credits

WorkBuddy is Tencent's desktop AI work assistant, billed in credits. It is
Expand Down
10 changes: 9 additions & 1 deletion rust/src/core/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub enum ProviderId {
HuggingFace,
Helmcode,
V0,
TinyApi,
TypeSafe,
LLMProxy,
Chutes,
Expand Down Expand Up @@ -172,6 +173,7 @@ impl ProviderId {
ProviderId::HuggingFace,
ProviderId::Helmcode,
ProviderId::V0,
ProviderId::TinyApi,
ProviderId::TypeSafe,
ProviderId::LLMProxy,
ProviderId::Chutes,
Expand Down Expand Up @@ -299,6 +301,7 @@ impl ProviderId {
ProviderId::HuggingFace => "huggingface",
ProviderId::Helmcode => "helmcode",
ProviderId::V0 => "v0",
ProviderId::TinyApi => "tinyapi",
ProviderId::TypeSafe => "typesafe",
ProviderId::LLMProxy => "llmproxy",
ProviderId::Chutes => "chutes",
Expand Down Expand Up @@ -405,6 +408,7 @@ impl ProviderId {
ProviderId::HuggingFace => "Hugging Face",
ProviderId::Helmcode => "Helmcode",
ProviderId::V0 => "v0",
ProviderId::TinyApi => "TinyApi",
ProviderId::TypeSafe => "TypeSafe",
ProviderId::LLMProxy => "LLM Proxy",
ProviderId::Chutes => "Chutes",
Expand Down Expand Up @@ -524,6 +528,7 @@ impl ProviderId {
ProviderId::Groq => Some("groq.com"),
ProviderId::HuggingFace => None,
ProviderId::Helmcode => Some("helmcode.com"),
ProviderId::TinyApi => Some("tinyapi.rest"),
ProviderId::TypeSafe => Some("typesafe.ai"),
ProviderId::V0 => None,
ProviderId::LLMProxy => None,
Expand Down Expand Up @@ -642,6 +647,7 @@ impl ProviderId {
"huggingface" | "hugging-face" | "hugging face" | "hf" => Some(ProviderId::HuggingFace),
"helmcode" | "nan-builders" | "nan builders" => Some(ProviderId::Helmcode),
"v0" | "v0-dev" | "v0.dev" => Some(ProviderId::V0),
"tinyapi" | "tiny-api" => Some(ProviderId::TinyApi),
"typesafe" | "type-safe" | "type safe" => Some(ProviderId::TypeSafe),
"llmproxy" | "llm-proxy" | "llm proxy" => Some(ProviderId::LLMProxy),
"chutes" | "chutes-ai" | "chutes ai" => Some(ProviderId::Chutes),
Expand Down Expand Up @@ -1353,6 +1359,7 @@ pub fn brand_color(id: ProviderId) -> &'static str {
ProviderId::HuggingFace => "#FFD21E",
ProviderId::Helmcode => "#4934E1",
ProviderId::V0 => "#111111",
ProviderId::TinyApi => "#F97316",
ProviderId::TypeSafe => "#111111",
ProviderId::LLMProxy => "#24B47E",
ProviderId::Chutes => "#3184FF",
Expand Down Expand Up @@ -1416,7 +1423,7 @@ mod tests {
#[test]
fn test_provider_id_all() {
let all = ProviderId::all();
assert_eq!(all.len(), 99);
assert_eq!(all.len(), 100);
assert!(all.contains(&ProviderId::Claude));
assert!(all.contains(&ProviderId::Codex));
assert!(all.contains(&ProviderId::Pi));
Expand Down Expand Up @@ -1496,6 +1503,7 @@ mod tests {
assert!(all.contains(&ProviderId::MuseAI));
assert!(all.contains(&ProviderId::WorkBuddy));
assert!(all.contains(&ProviderId::SailResearch));
assert!(all.contains(&ProviderId::TinyApi));
}

#[test]
Expand Down
10 changes: 6 additions & 4 deletions rust/src/core/provider_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ use crate::providers::{
NousProvider, OllamaProvider, OpenAIApiProvider, OpenCodeGoProvider, OpenCodeProvider,
OpenRouterProvider, PerplexityProvider, PiProvider, PoeProvider, QoderProvider,
QwenCloudProvider, RaycastProvider, ReplicateProvider, SailResearchProvider, SakanaProvider,
SofyaProvider, StepFunProvider, Sub2ApiProvider, T3ChatProvider, TypeSafeProvider, V0Provider,
VeniceProvider, VercelProvider, VertexAIProvider, WarpProvider, WayfinderProvider,
WindsurfProvider, XKiroProvider, XaiProvider, ZaiProvider, ZedProvider, ZenMuxProvider,
ZoomMateProvider,
SofyaProvider, StepFunProvider, Sub2ApiProvider, T3ChatProvider, TinyApiProvider,
TypeSafeProvider, V0Provider, VeniceProvider, VercelProvider, VertexAIProvider, WarpProvider,
WayfinderProvider, WindsurfProvider, XKiroProvider, XaiProvider, ZaiProvider, ZedProvider,
ZenMuxProvider, ZoomMateProvider,
};

/// Instantiate the concrete [`Provider`] implementation for a given [`ProviderId`].
Expand Down Expand Up @@ -104,6 +104,7 @@ pub fn instantiate(id: ProviderId) -> Box<dyn Provider> {
ProviderId::HuggingFace => Box::new(HuggingFaceProvider::new()),
ProviderId::Helmcode => Box::new(HelmcodeProvider::new()),
ProviderId::V0 => Box::new(V0Provider::new()),
ProviderId::TinyApi => Box::new(TinyApiProvider::new()),
ProviderId::TypeSafe => Box::new(TypeSafeProvider::new()),
ProviderId::LLMProxy => Box::new(LLMProxyProvider::new()),
ProviderId::Chutes => Box::new(ChutesProvider::new()),
Expand Down Expand Up @@ -214,6 +215,7 @@ pub fn pace_capability(id: ProviderId) -> ProviderPaceCapability {
| ProviderId::HuggingFace
| ProviderId::Helmcode
| ProviderId::V0
| ProviderId::TinyApi
| ProviderId::TypeSafe
| ProviderId::LLMProxy
| ProviderId::Chutes
Expand Down
3 changes: 2 additions & 1 deletion rust/src/core/token_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,8 @@ impl TokenAccountSupport {
| ProviderId::MuseAI
| ProviderId::WorkBuddy
| ProviderId::SailResearch
| ProviderId::Sofya => None,
| ProviderId::Sofya
| ProviderId::TinyApi => None,
}
}

Expand Down
2 changes: 2 additions & 0 deletions rust/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub mod stepfun;
pub mod sub2api;
pub mod synthetic;
pub mod t3chat;
pub mod tinyapi;
pub mod typesafe;
pub mod v0;
pub mod venice;
Expand Down Expand Up @@ -195,6 +196,7 @@ pub use stepfun::StepFunProvider;
pub use sub2api::Sub2ApiProvider;
pub use synthetic::SyntheticProvider;
pub use t3chat::T3ChatProvider;
pub use tinyapi::TinyApiProvider;
pub use typesafe::TypeSafeProvider;
pub use v0::V0Provider;
pub use venice::VeniceProvider;
Expand Down
Loading