Admit multiple models per connector caller
This commit is contained in:
parent
2f29b90eeb
commit
b2e76841bf
2 changed files with 63 additions and 9 deletions
|
|
@ -116,16 +116,18 @@ impl CodexDaemonConfig {
|
|||
}
|
||||
let mut caller_ids = HashSet::new();
|
||||
for caller in &self.callers {
|
||||
let mut allowed_models = HashSet::new();
|
||||
if caller.caller_runtime_id.trim().is_empty()
|
||||
|| caller.caller_runtime_id.trim() != caller.caller_runtime_id
|
||||
|| !caller_ids.insert(caller.caller_runtime_id.as_str())
|
||||
|| caller.connection_key_file.as_os_str().is_empty()
|
||||
|| caller.connection_key_epoch == 0
|
||||
|| caller.allowed_models.is_empty()
|
||||
|| caller
|
||||
.allowed_models
|
||||
.iter()
|
||||
.any(|model| model.trim().is_empty() || model.trim() != model)
|
||||
|| caller.allowed_models.iter().any(|model| {
|
||||
model.trim().is_empty()
|
||||
|| model.trim() != model
|
||||
|| !allowed_models.insert(model.as_str())
|
||||
})
|
||||
|| caller.max_concurrent_requests == 0
|
||||
|| caller.max_payload_bytes < 4096
|
||||
|| caller.max_payload_bytes > self.max_frame_bytes - FRAME_OVERHEAD_BUDGET
|
||||
|
|
@ -529,6 +531,23 @@ mod tests {
|
|||
config.validate().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_config_preserves_a_unique_multi_model_caller_allowlist() {
|
||||
let root = Path::new("/srv/codex-connector");
|
||||
let mut config = config(root);
|
||||
config.callers[0].allowed_models =
|
||||
vec!["gpt-5.6-luna".to_string(), "gpt-5.6-terra".to_string()];
|
||||
config.validate().unwrap();
|
||||
|
||||
config.callers[0]
|
||||
.allowed_models
|
||||
.push("gpt-5.6-luna".to_string());
|
||||
assert!(matches!(
|
||||
config.validate(),
|
||||
Err(CodexDaemonError::InvalidConfig("caller admission"))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caller_admission_reserves_every_independently_admitted_request_slot() {
|
||||
let root = Path::new("/srv/codex-connector");
|
||||
|
|
|
|||
45
src/main.rs
45
src/main.rs
|
|
@ -1,9 +1,10 @@
|
|||
use std::collections::HashSet;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_connector::{CodexCallerConfig, CodexDaemonConfig};
|
||||
|
||||
const USAGE: &str = "usage:\n codex-connector --config PATH.cc\n codex-connector --initialize-single-caller-config PATH.cc BIND CODEX_EXECUTABLE CODEX_SHA256 CODEX_HOME REPLAY_STORE CALLER_RUNTIME_ID CONNECTION_KEY_FILE CONNECTION_KEY_EPOCH ALLOWED_MODEL MAX_CONCURRENT_REQUESTS MAX_PAYLOAD_BYTES MAX_OUTPUT_TOKENS\n codex-connector --admit-caller-config SOURCE.cc DESTINATION.cc CALLER_RUNTIME_ID CONNECTION_KEY_FILE CONNECTION_KEY_EPOCH ALLOWED_MODEL MAX_CONCURRENT_REQUESTS MAX_PAYLOAD_BYTES MAX_OUTPUT_TOKENS\n codex-connector --enroll-provider-health-identity PATH.cc\n codex-connector --provider-health-public-key PATH.cc";
|
||||
const USAGE: &str = "usage:\n codex-connector --config PATH.cc\n codex-connector --initialize-single-caller-config PATH.cc BIND CODEX_EXECUTABLE CODEX_SHA256 CODEX_HOME REPLAY_STORE CALLER_RUNTIME_ID CONNECTION_KEY_FILE CONNECTION_KEY_EPOCH ALLOWED_MODELS_CSV MAX_CONCURRENT_REQUESTS MAX_PAYLOAD_BYTES MAX_OUTPUT_TOKENS\n codex-connector --admit-caller-config SOURCE.cc DESTINATION.cc CALLER_RUNTIME_ID CONNECTION_KEY_FILE CONNECTION_KEY_EPOCH ALLOWED_MODELS_CSV MAX_CONCURRENT_REQUESTS MAX_PAYLOAD_BYTES MAX_OUTPUT_TOKENS\n codex-connector --enroll-provider-health-identity PATH.cc\n codex-connector --provider-health-public-key PATH.cc";
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args_os().skip(1).collect::<Vec<_>>();
|
||||
|
|
@ -71,7 +72,7 @@ fn admit_caller_config(args: &[OsString]) -> Result<(), String> {
|
|||
caller_runtime_id,
|
||||
connection_key_file,
|
||||
connection_key_epoch,
|
||||
allowed_model,
|
||||
allowed_models,
|
||||
max_concurrent_requests,
|
||||
max_payload_bytes,
|
||||
max_output_tokens,
|
||||
|
|
@ -86,7 +87,7 @@ fn admit_caller_config(args: &[OsString]) -> Result<(), String> {
|
|||
caller_runtime_id,
|
||||
connection_key_file: PathBuf::from(connection_key_file),
|
||||
connection_key_epoch: parse_number(connection_key_epoch, "connection key epoch")?,
|
||||
allowed_models: vec![os_text(allowed_model, "allowed model")?],
|
||||
allowed_models: parse_allowed_models(allowed_models)?,
|
||||
max_concurrent_requests: parse_number(max_concurrent_requests, "max concurrent requests")?,
|
||||
max_payload_bytes: parse_number(max_payload_bytes, "max payload bytes")?,
|
||||
max_output_tokens: parse_number(max_output_tokens, "max output tokens")?,
|
||||
|
|
@ -109,7 +110,7 @@ fn initialize_single_caller_config(args: &[OsString]) -> Result<(), String> {
|
|||
caller_runtime_id,
|
||||
connection_key_file,
|
||||
connection_key_epoch,
|
||||
allowed_model,
|
||||
allowed_models,
|
||||
max_concurrent_requests,
|
||||
max_payload_bytes,
|
||||
max_output_tokens,
|
||||
|
|
@ -127,7 +128,7 @@ fn initialize_single_caller_config(args: &[OsString]) -> Result<(), String> {
|
|||
caller_runtime_id: os_text(caller_runtime_id, "caller runtime")?,
|
||||
connection_key_file: PathBuf::from(connection_key_file),
|
||||
connection_key_epoch: parse_number(connection_key_epoch, "connection key epoch")?,
|
||||
allowed_models: vec![os_text(allowed_model, "allowed model")?],
|
||||
allowed_models: parse_allowed_models(allowed_models)?,
|
||||
max_concurrent_requests: parse_number(
|
||||
max_concurrent_requests,
|
||||
"max concurrent requests",
|
||||
|
|
@ -148,6 +149,19 @@ fn os_text(value: &OsString, field: &str) -> Result<String, String> {
|
|||
.ok_or_else(|| format!("{field} must be non-empty UTF-8"))
|
||||
}
|
||||
|
||||
fn parse_allowed_models(value: &OsString) -> Result<Vec<String>, String> {
|
||||
let raw = os_text(value, "allowed models")?;
|
||||
let mut seen = HashSet::new();
|
||||
let mut models = Vec::new();
|
||||
for model in raw.split(',') {
|
||||
if model.is_empty() || model.trim() != model || !seen.insert(model) {
|
||||
return Err("allowed models must be unique non-empty comma-separated IDs".to_string());
|
||||
}
|
||||
models.push(model.to_string());
|
||||
}
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
fn parse_number<T>(value: &OsString, field: &str) -> Result<T, String>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
|
|
@ -157,6 +171,27 @@ where
|
|||
.map_err(|_| format!("{field} is not a valid integer"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse_allowed_models;
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[test]
|
||||
fn model_allowlist_parser_preserves_one_or_many_exact_models() {
|
||||
assert_eq!(
|
||||
parse_allowed_models(&OsString::from("gpt-5.6-luna")).unwrap(),
|
||||
vec!["gpt-5.6-luna"]
|
||||
);
|
||||
assert_eq!(
|
||||
parse_allowed_models(&OsString::from("gpt-5.6-luna,gpt-5.6-terra")).unwrap(),
|
||||
vec!["gpt-5.6-luna", "gpt-5.6-terra"]
|
||||
);
|
||||
assert!(parse_allowed_models(&OsString::from("gpt-5.6-luna,")).is_err());
|
||||
assert!(parse_allowed_models(&OsString::from("gpt-5.6-luna, gpt-5.6-terra")).is_err());
|
||||
assert!(parse_allowed_models(&OsString::from("gpt-5.6-luna,gpt-5.6-luna")).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sha256(value: &OsString) -> Result<[u8; 32], String> {
|
||||
let value = os_text(value, "Codex SHA-256")?;
|
||||
if value.len() != 64 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue