modelsとprovidersをカタログ化

This commit is contained in:
2026-04-24 10:45:03 +09:00
parent 7ecb1e6fc1
commit 30f9abacb8
16 changed files with 777 additions and 561 deletions
+24 -7
View File
@@ -683,11 +683,28 @@ where
St: Store,
{
let manifest = pod.manifest();
let provider = match manifest.model.scheme {
manifest::SchemeKind::Anthropic => "anthropic",
manifest::SchemeKind::OpenaiChat => "openai_chat",
manifest::SchemeKind::OpenaiResponses => "openai_responses",
manifest::SchemeKind::Gemini => "gemini",
// `build_client` がここに到達する前に同じマニフェストで成功している
// ため、カタログ解決も必ず通る。念のため失敗時は "unknown" に落とす。
let resolved = provider::catalog::resolve_model_manifest(&manifest.model).ok();
let (provider_name, model_id) = match resolved {
Some(cfg) => {
let name = match cfg.scheme {
manifest::SchemeKind::Anthropic => "anthropic",
manifest::SchemeKind::OpenaiChat => "openai_chat",
manifest::SchemeKind::OpenaiResponses => "openai_responses",
manifest::SchemeKind::Gemini => "gemini",
};
(name.to_string(), cfg.model_id)
}
None => (
"unknown".to_string(),
manifest
.model
.ref_
.clone()
.or_else(|| manifest.model.model_id.clone())
.unwrap_or_default(),
),
};
// The tool list mirrors what `spawn()` registers on the Worker:
// builtin filesystem tools plus the pod-orchestration tools.
@@ -708,8 +725,8 @@ where
protocol::Greeting {
pod_name: manifest.pod.name.clone(),
cwd: pod.pwd().display().to_string(),
provider: provider.into(),
model: manifest.model.model_id.clone(),
provider: provider_name,
model: model_id,
scope_summary: pod.scope().summary(),
tools: tool_names,
}
+2 -2
View File
@@ -411,7 +411,7 @@ name = "overlay-name"
// overlay layer so later calls win. This also exercises the
// scope union across layers (two allow rules).
assert_eq!(manifest.pod.name, "overlay-name");
assert_eq!(manifest.model.model_id, "project-model");
assert_eq!(manifest.model.model_id.as_deref(), Some("project-model"));
assert_eq!(manifest.scope.allow.len(), 2);
}
@@ -461,7 +461,7 @@ model_id = "project-model"
.unwrap();
// project layer overrides user layer on model.model_id
assert_eq!(manifest.model.model_id, "project-model");
assert_eq!(manifest.model.model_id.as_deref(), Some("project-model"));
// user layer provides the rest
assert_eq!(manifest.pod.name, "from-user");
}
+1 -1
View File
@@ -32,7 +32,7 @@ pub use factory::{FactoryError, PodFactory};
pub use notifier::Notifier;
pub use hook::{Hook, HookEventKind, HookRegistryBuilder};
pub use manifest::{
AuthRef, ModelConfig, PodManifest, PodManifestConfig, PodMetaConfig, Scope, SchemeKind,
AuthRef, ModelManifest, PodManifest, PodManifestConfig, PodMetaConfig, Scope, SchemeKind,
};
pub use pod::{Pod, PodError, PodRunResult, apply_worker_manifest};
pub use prompt_loader::PromptLoader;
+28 -20
View File
@@ -14,8 +14,8 @@ use std::time::Duration;
use async_trait::async_trait;
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use manifest::{
ModelConfig, ModelConfigPartial, Permission, PodManifestConfig, PodMetaConfig, ScopeConfig,
ScopeRule, WorkerManifestConfig,
ModelManifest, Permission, PodManifestConfig, PodMetaConfig, ScopeConfig, ScopeRule,
WorkerManifestConfig,
};
use protocol::Method;
use protocol::stream::JsonLineWriter;
@@ -118,7 +118,7 @@ pub struct SpawnPodTool {
/// Pod's overlay TOML so the child does not need its own provider
/// configuration in the manifest cascade. Per-spawn override is
/// out of scope here (see `tickets/spawn-inherit-provider.md`).
spawner_model: ModelConfig,
spawner_model: ModelManifest,
}
impl SpawnPodTool {
@@ -129,7 +129,7 @@ impl SpawnPodTool {
spawner_pwd: PathBuf,
registry: Arc<SpawnedPodRegistry>,
parent_socket: Option<PathBuf>,
spawner_model: ModelConfig,
spawner_model: ModelManifest,
) -> Self {
Self {
spawner_name,
@@ -350,20 +350,14 @@ fn build_overlay_toml(
name: &str,
instruction: &str,
scope_allow: &[ScopeRule],
model: &ModelConfig,
model: &ModelManifest,
) -> Result<String, toml::ser::Error> {
let overlay = PodManifestConfig {
pod: PodMetaConfig {
name: Some(name.to_string()),
prompt_pack: None,
},
model: ModelConfigPartial {
scheme: Some(model.scheme),
base_url: model.base_url.clone(),
model_id: Some(model.model_id.clone()),
auth: Some(model.auth.clone()),
capability: model.capability.clone(),
},
model: model.clone(),
worker: WorkerManifestConfig {
instruction: Some(instruction.to_string()),
..Default::default()
@@ -460,7 +454,7 @@ pub fn spawn_pod_tool(
spawner_pwd: PathBuf,
registry: Arc<SpawnedPodRegistry>,
parent_socket: Option<PathBuf>,
spawner_model: ModelConfig,
spawner_model: ModelManifest,
) -> ToolDefinition {
Arc::new(move || {
let schema = schemars::schema_for!(SpawnPodInput);
@@ -487,16 +481,16 @@ mod tests {
use manifest::{AuthRef, SchemeKind};
#[test]
fn overlay_inherits_spawner_model() {
let model = ModelConfig {
scheme: SchemeKind::Anthropic,
fn overlay_inherits_inline_spawner_model() {
let model = ModelManifest {
scheme: Some(SchemeKind::Anthropic),
base_url: Some("https://example.test".into()),
model_id: "claude-sonnet-4".into(),
auth: AuthRef::ApiKey {
model_id: Some("claude-sonnet-4".into()),
auth: Some(AuthRef::ApiKey {
env: None,
file: Some(PathBuf::from("/etc/keys/anthropic")),
},
capability: None,
}),
..Default::default()
};
let toml_str = build_overlay_toml("child", "$insomnia/default", &[], &model).unwrap();
@@ -511,4 +505,18 @@ mod tests {
};
assert_eq!(file.as_deref(), Some(Path::new("/etc/keys/anthropic")));
}
#[test]
fn overlay_inherits_ref_spawner_model() {
let model = ModelManifest {
ref_: Some("anthropic/claude-sonnet-4-6".into()),
..Default::default()
};
let toml_str = build_overlay_toml("child", "$insomnia/default", &[], &model).unwrap();
let parsed = PodManifestConfig::from_toml(&toml_str).unwrap();
assert_eq!(
parsed.model.ref_.as_deref(),
Some("anthropic/claude-sonnet-4-6")
);
}
}
+8 -7
View File
@@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
use std::sync::{LazyLock, Mutex};
use llm_worker::tool::{ToolError, ToolOutput};
use manifest::{AuthRef, ModelConfig, Permission, SchemeKind, ScopeRule};
use manifest::{AuthRef, ModelManifest, Permission, SchemeKind, ScopeRule};
use pod::runtime_dir::{RuntimeDir, SpawnedPodRecord};
use pod::scope_lock::{self, LockFileGuard};
use pod::spawn_pod::spawn_pod_tool;
@@ -134,14 +134,15 @@ fn which_true() -> String {
/// Tests don't exercise the model — they intercept the spawned
/// child via a mock socket — but `spawn_pod_tool` needs a value to
/// embed in the overlay TOML. Any well-formed `ModelConfig` works.
fn dummy_model() -> ModelConfig {
ModelConfig {
scheme: SchemeKind::Anthropic,
/// embed in the overlay TOML. Any well-formed `ModelManifest` works.
fn dummy_model() -> ModelManifest {
ModelManifest {
scheme: Some(SchemeKind::Anthropic),
base_url: None,
model_id: "claude-test".into(),
auth: AuthRef::None,
model_id: Some("claude-test".into()),
auth: Some(AuthRef::None),
capability: None,
..Default::default()
}
}