Add API tokens, CLI server API and shared core crate
- Personal API tokens (full/read scope, expiry, revoke) and CLI password login - Cargo workspace: codecraft-core (types, providers, permission rules) shared by server and the upcoming terminal client; server re-exports the old paths - CLI server API: /cli/config, /cli/skill, /cli/tool (server MCP tools), /llm/stream model gateway with budgets and usage per user - Theme/language preferences mirrored to the server; personal memory endpoint - Statistics: provider and mode tables stacked; CLI usage labelled - cli/: work in progress (terminal client is being rewritten as a local TUI) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fca1c41087
commit
aa1f8cf016
40 files changed
+2454
-814
No files matched your search
@@ -0,0 +1,122 @@
|
||||
//! Provider, model and permission configuration shared by server and CLI.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn yes() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProviderKind {
|
||||
Anthropic,
|
||||
Openai,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ProviderConfig {
|
||||
pub kind: ProviderKind,
|
||||
#[serde(default)]
|
||||
pub base_url: String,
|
||||
#[serde(default)]
|
||||
pub api_key: String,
|
||||
#[serde(default)]
|
||||
pub auth_header: Option<String>,
|
||||
#[serde(default)]
|
||||
pub auth_prefix: Option<String>,
|
||||
#[serde(default)]
|
||||
pub headers: BTreeMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub models: Vec<ModelEntry>,
|
||||
#[serde(default = "yes")]
|
||||
pub enabled: bool,
|
||||
/// Max. simultaneous requests to this provider/host (None = unlimited).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_concurrency: Option<usize>,
|
||||
/// Monthly budget in the configured currency (tracked from recorded costs).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub monthly_budget: Option<f64>,
|
||||
/// Optional admin key to read the provider's own cost report
|
||||
/// (Anthropic: sk-ant-admin01-…, organisation-wide).
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub admin_key: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ModelEntry {
|
||||
Id(String),
|
||||
Full(ModelConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct ModelConfig {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub context: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub max_output: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub reasoning: bool,
|
||||
#[serde(default)]
|
||||
pub vision: bool,
|
||||
/// Reasoning effort (Anthropic: low|medium|high|xhigh|max, OpenAI: low|medium|high).
|
||||
#[serde(default)]
|
||||
pub effort: Option<String>,
|
||||
/// Prices in currency units per 1M tokens (used for cost statistics).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub input_price: Option<f64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub output_price: Option<f64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cache_read_price: Option<f64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cache_write_price: Option<f64>,
|
||||
/// Max. simultaneous requests to this model (None = unlimited).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_concurrency: Option<usize>,
|
||||
/// Extra request body fields sent as-is (e.g. temperature, reasoning_budget_tokens).
|
||||
#[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
|
||||
pub options: serde_json::Map<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
impl ModelConfig {
|
||||
/// Cost of one request. Cache prices default to the input price.
|
||||
pub fn cost(&self, u: &crate::types::Usage) -> f64 {
|
||||
let inp = self.input_price.unwrap_or(0.0);
|
||||
let per = |tokens: u64, price: f64| tokens as f64 * price / 1_000_000.0;
|
||||
per(u.input_tokens, inp)
|
||||
+ per(u.output_tokens, self.output_price.unwrap_or(0.0))
|
||||
+ per(u.cache_read_tokens, self.cache_read_price.unwrap_or(inp))
|
||||
+ per(u.cache_write_tokens, self.cache_write_price.unwrap_or(inp))
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelEntry {
|
||||
pub fn resolved(&self) -> ModelConfig {
|
||||
match self {
|
||||
ModelEntry::Id(id) => ModelConfig { id: id.clone(), ..Default::default() },
|
||||
ModelEntry::Full(m) => m.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct PermissionConfig {
|
||||
#[serde(default)]
|
||||
pub bash_rules: BTreeMap<String, String>,
|
||||
#[serde(flatten)]
|
||||
pub tools: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
/// Replace `${NAME}` with the environment variable (empty if unset).
|
||||
pub fn expand_env(s: &str) -> String {
|
||||
let re = regex::Regex::new(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}").unwrap();
|
||||
re.replace_all(s, |c: ®ex::Captures| std::env::var(&c[1]).unwrap_or_default())
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user