mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
Add default model provider selection to the app server (#61397)
## What changed Add `modelProvider/default/set` with a `providerId` parameter to persist the default provider for new threads and model lists. Return `ConfigWriteResponse`, reject unconfigured providers, and respect managed requirements, read-only configuration, and CLI overrides. Reload the model catalog on its next read when the default changes effectively. Resolve explicit `model/list` provider selections against the current catalog configuration so clients can continue listing models for a retained provider after switching defaults. Update the protocol schemas, generated TypeScript and Python types, and app-server documentation. ## Testing Add coverage for switching defaults and switching back, rejecting unknown providers without changing the catalog, retaining explicit provider selections, reporting CLI overrides, and rejecting writes blocked by configuration restrictions. GitOrigin-RevId: 3615835f58045d33a21b3c57d47932d6d1e3fb12
This commit is contained in:
@@ -2566,6 +2566,18 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderDefaultSetParams": {
|
||||
"properties": {
|
||||
"providerId": {
|
||||
"description": "Configured provider to use by default for new threads and unscoped model lists.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"providerId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderListParams": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -8384,6 +8396,30 @@
|
||||
"title": "ModelProvider/config/writeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"modelProvider/default/set"
|
||||
],
|
||||
"title": "ModelProvider/default/setRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ModelProviderDefaultSetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ModelProvider/default/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
+38
@@ -1968,6 +1968,30 @@
|
||||
"title": "ModelProvider/config/writeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"modelProvider/default/set"
|
||||
],
|
||||
"title": "ModelProvider/default/setRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ModelProviderDefaultSetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ModelProvider/default/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -16820,6 +16844,20 @@
|
||||
"title": "ModelProviderConfigWriteResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderDefaultSetParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"providerId": {
|
||||
"description": "Configured provider to use by default for new threads and unscoped model lists.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"providerId"
|
||||
],
|
||||
"title": "ModelProviderDefaultSetParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderError": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Structured JSON-RPC error data. Unknown errors must retain the client's normal fallback.",
|
||||
|
||||
+38
@@ -3956,6 +3956,30 @@
|
||||
"title": "ModelProvider/config/writeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"modelProvider/default/set"
|
||||
],
|
||||
"title": "ModelProvider/default/setRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ModelProviderDefaultSetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ModelProvider/default/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -12774,6 +12798,20 @@
|
||||
"title": "ModelProviderConfigWriteResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderDefaultSetParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"providerId": {
|
||||
"description": "Configured provider to use by default for new threads and unscoped model lists.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"providerId"
|
||||
],
|
||||
"title": "ModelProviderDefaultSetParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderError": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Structured JSON-RPC error data. Unknown errors must retain the client's normal fallback.",
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"providerId": {
|
||||
"description": "Configured provider to use by default for new threads and unscoped model lists.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"providerId"
|
||||
],
|
||||
"title": "ModelProviderDefaultSetParams",
|
||||
"type": "object"
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
+9
@@ -0,0 +1,9 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ModelProviderDefaultSetParams = {
|
||||
/**
|
||||
* Configured provider to use by default for new threads and unscoped model lists.
|
||||
*/
|
||||
providerId: string, };
|
||||
@@ -349,6 +349,7 @@ export type { ModelProviderCapabilitiesReadParams } from "./ModelProviderCapabil
|
||||
export type { ModelProviderCapabilitiesReadResponse } from "./ModelProviderCapabilitiesReadResponse";
|
||||
export type { ModelProviderConfigWriteParams } from "./ModelProviderConfigWriteParams";
|
||||
export type { ModelProviderConfigWriteResponse } from "./ModelProviderConfigWriteResponse";
|
||||
export type { ModelProviderDefaultSetParams } from "./ModelProviderDefaultSetParams";
|
||||
export type { ModelProviderError } from "./ModelProviderError";
|
||||
export type { ModelProviderListParams } from "./ModelProviderListParams";
|
||||
export type { ModelProviderListResponse } from "./ModelProviderListResponse";
|
||||
|
||||
@@ -1212,6 +1212,12 @@ client_request_definitions! {
|
||||
serialization: global("config"),
|
||||
response: v2::ModelProviderConfigWriteResponse,
|
||||
},
|
||||
ModelProviderDefaultSet => "modelProvider/default/set" {
|
||||
params: v2::ModelProviderDefaultSetParams,
|
||||
serialization: global("config"),
|
||||
manual_payload_conversion: manual,
|
||||
response: v2::ConfigWriteResponse,
|
||||
},
|
||||
ModelProviderCapabilitiesRead => "modelProvider/capabilities/read" {
|
||||
params: v2::ModelProviderCapabilitiesReadParams,
|
||||
serialization: None,
|
||||
|
||||
@@ -248,3 +248,11 @@ pub struct ModelProviderConfigWriteParams {
|
||||
pub struct ModelProviderConfigWriteResponse {
|
||||
pub provider: ModelProviderSummary,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelProviderDefaultSetParams {
|
||||
/// Configured provider to use by default for new threads and unscoped model lists.
|
||||
pub provider_id: String,
|
||||
}
|
||||
|
||||
@@ -121,15 +121,17 @@ denial limit, and interrupted status; neither emits a separate `Error` event.
|
||||
|
||||
# Model catalog provider requirements
|
||||
|
||||
`model/list` accepts an optional `modelProvider` to list models for a configured provider
|
||||
other than the startup default. For example, `{"modelProvider": "my_gateway"}` returns that
|
||||
provider's models after the user's default changes. Omit it to retain the startup provider
|
||||
and caching behavior. Explicit selections use the provider definitions loaded at startup. Restart the server
|
||||
to pick up edits to those definitions. An unknown selector returns JSON-RPC error
|
||||
`modelProvider/default/set` selects a configured provider for new threads and unscoped model lists.
|
||||
Send `{"providerId": "my_gateway"}`. The response uses the same `ConfigWriteResponse` as other config writes.
|
||||
An effective selection refreshes the catalog on its next read. Editing the active provider through
|
||||
`modelProvider/config/write` also refreshes that snapshot.
|
||||
|
||||
`model/list` uses the current default when `modelProvider` is omitted. Pass a provider ID to list
|
||||
models for a chat that retained its provider after a default change. An unknown selector returns JSON-RPC error
|
||||
`-32600` with `data.type = "modelProviderNotConfigured"` and the requested `modelProvider`.
|
||||
|
||||
`model/list` checks its selected provider, and periodic model catalog refreshes check the
|
||||
startup provider, against current managed provider requirements. If that provider no longer
|
||||
current default provider, against current managed provider requirements. If that provider no longer
|
||||
complies, `model/list` returns JSON-RPC error `-32600` asking the client to restart Codex,
|
||||
and background refreshes skip the old endpoint. Requirement load failures also block these
|
||||
operations. Checks apply even when the catalog is cached.
|
||||
|
||||
@@ -347,13 +347,24 @@ impl ConfigManager {
|
||||
));
|
||||
}
|
||||
|
||||
self.persist_user_edits(&layers, &user_layer, provided_path, edits)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn persist_user_edits(
|
||||
&self,
|
||||
layers: &ConfigLayerStack,
|
||||
user_layer: &ConfigLayerEntry,
|
||||
provided_path: AbsolutePathBuf,
|
||||
edits: Vec<(String, JsonValue, MergeStrategy)>,
|
||||
) -> Result<ConfigWriteResponse, ConfigManagerError> {
|
||||
let mut user_config = user_layer.config.clone();
|
||||
let mut credential_provider_edits = CredentialProviderEdits::new(&user_config);
|
||||
let mut parsed_segments = Vec::new();
|
||||
let mut config_edits = Vec::new();
|
||||
|
||||
for (key_path, value, strategy) in edits.into_iter() {
|
||||
let mut segments = writable_key_path(&layers, &key_path)?;
|
||||
let mut segments = writable_key_path(layers, &key_path)?;
|
||||
if (value.is_null() || matches!(strategy, MergeStrategy::Upsert))
|
||||
&& let Some(pattern) = shell_environment_filter_entry(&user_config, &segments)
|
||||
.map(|(pattern, _)| pattern.clone())
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
use super::ConfigManager;
|
||||
use super::ConfigManagerError;
|
||||
use super::TomlValue;
|
||||
use codex_app_server_protocol::ConfigWriteErrorCode;
|
||||
use codex_app_server_protocol::ConfigWriteResponse;
|
||||
use codex_app_server_protocol::GatewayAuth;
|
||||
use codex_app_server_protocol::MergeStrategy;
|
||||
use codex_app_server_protocol::ModelProviderDefaultSetParams;
|
||||
use codex_app_server_protocol::ModelProviderListParams;
|
||||
use codex_app_server_protocol::ModelProviderListResponse;
|
||||
use codex_app_server_protocol::ModelProviderSummary;
|
||||
@@ -62,6 +66,50 @@ pub(super) fn valid_endpoint(value: &str) -> bool {
|
||||
}
|
||||
|
||||
impl ConfigManager {
|
||||
pub(crate) async fn model_provider_default_set(
|
||||
&self,
|
||||
params: ModelProviderDefaultSetParams,
|
||||
) -> Result<ConfigWriteResponse, ConfigManagerError> {
|
||||
let layers = self
|
||||
.load_thread_agnostic_config()
|
||||
.await
|
||||
.map_err(|err| ConfigManagerError::io("failed to read providers", err))?;
|
||||
let user_layer =
|
||||
writable_user_layer(&layers, self.loader_overrides()).ok_or_else(|| {
|
||||
ConfigManagerError::write(
|
||||
ConfigWriteErrorCode::ConfigLayerReadonly,
|
||||
"The user configuration is not writable.",
|
||||
)
|
||||
})?;
|
||||
let provider_id = params.provider_id;
|
||||
if !built_in_model_providers(/*openai_base_url*/ None).contains_key(&provider_id)
|
||||
&& layers
|
||||
.effective_config()
|
||||
.get("model_providers")
|
||||
.and_then(|providers| providers.get(&provider_id))
|
||||
.is_none()
|
||||
{
|
||||
return Err(ConfigManagerError::write(
|
||||
ConfigWriteErrorCode::ConfigValidationError,
|
||||
format!("Model provider `{provider_id}` is not configured."),
|
||||
));
|
||||
}
|
||||
let user_path = self
|
||||
.user_config_path()
|
||||
.map_err(|err| ConfigManagerError::io("failed to resolve user config path", err))?;
|
||||
self.persist_user_edits(
|
||||
&layers,
|
||||
user_layer,
|
||||
user_path,
|
||||
vec![(
|
||||
"model_provider".into(),
|
||||
provider_id.into(),
|
||||
MergeStrategy::Replace,
|
||||
)],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn model_provider_list(
|
||||
&self,
|
||||
_params: ModelProviderListParams,
|
||||
|
||||
@@ -3454,6 +3454,29 @@ async fn cloud_desktop_storage_requirements_control_reads_and_writes() {
|
||||
.expect("unmanaged desktop settings stay writable");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_provider_default_set_reports_a_cli_override() -> Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "")?;
|
||||
let service = ConfigManager::new_for_tests(
|
||||
tmp.path().to_path_buf(),
|
||||
vec![("model_provider".into(), TomlValue::String("openai".into()))],
|
||||
LoaderOverrides::without_managed_config_for_tests(),
|
||||
CloudConfigBundleLoader::default(),
|
||||
);
|
||||
let result = service
|
||||
.model_provider_default_set(codex_app_server_protocol::ModelProviderDefaultSetParams {
|
||||
provider_id: "amazon-bedrock".into(),
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(result.status, WriteStatus::OkOverridden);
|
||||
assert_eq!(
|
||||
result.overridden_metadata.unwrap().effective_value,
|
||||
serde_json::json!("openai")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_provider_list_allows_user_overrides_of_system_defaults() -> Result<()> {
|
||||
use codex_app_server_protocol::ModelProviderListParams;
|
||||
@@ -3550,6 +3573,20 @@ async fn model_provider_list_allows_user_overrides_of_system_defaults() -> Resul
|
||||
.model_provider_list(ModelProviderListParams {})
|
||||
.await?;
|
||||
assert_eq!(response.can_change_default, can_change_default);
|
||||
if ignore_user_config {
|
||||
let error = service
|
||||
.model_provider_default_set(
|
||||
codex_app_server_protocol::ModelProviderDefaultSetParams {
|
||||
provider_id: "openai".into(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
error.write_error_code(),
|
||||
Some(ConfigWriteErrorCode::ConfigLayerReadonly)
|
||||
);
|
||||
}
|
||||
assert_eq!(response.default_provider_id, "team");
|
||||
assert_eq!(
|
||||
serde_json::to_value(response.data)?,
|
||||
|
||||
@@ -1353,6 +1353,11 @@ impl MessageProcessor {
|
||||
.model_provider_config_write(params)
|
||||
.await
|
||||
.map(|r| Some(r.into())),
|
||||
ClientRequest::ModelProviderDefaultSet { params, .. } => self
|
||||
.config_processor
|
||||
.model_provider_default_set(params)
|
||||
.await
|
||||
.map(Some),
|
||||
ClientRequest::ModelProviderCapabilitiesRead { .. } => self
|
||||
.config_processor
|
||||
.model_provider_capabilities_read()
|
||||
|
||||
@@ -63,7 +63,7 @@ impl ModelCatalog {
|
||||
self.config_manager.load_config_layers(cwd).await
|
||||
}
|
||||
|
||||
/// Marks catalog configuration stale after an account transition.
|
||||
/// Reloads catalog configuration on the next read.
|
||||
pub(crate) async fn invalidate(&self) {
|
||||
self.state.lock().await.needs_reload = true;
|
||||
}
|
||||
|
||||
@@ -187,12 +187,10 @@ impl CatalogRequestProcessor {
|
||||
let (config, models_manager) = match params.model_provider.as_deref() {
|
||||
None => (active_config, active_models_manager),
|
||||
Some(provider_id) => {
|
||||
let config = config_for_provider(&self.config, provider_id)?;
|
||||
if provider_id == active_config.model_provider_id
|
||||
&& config.model_provider == active_config.model_provider
|
||||
{
|
||||
if provider_id == active_config.model_provider_id {
|
||||
(active_config, active_models_manager)
|
||||
} else {
|
||||
let config = config_for_provider(&active_config, provider_id)?;
|
||||
// Temporary catalogs must not replace the active provider's disk cache.
|
||||
let manager = self.thread_manager.temporary_models_manager_for_config(
|
||||
&config,
|
||||
|
||||
@@ -240,6 +240,25 @@ impl ConfigRequestProcessor {
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub(crate) fn model_provider_default_set(
|
||||
&self,
|
||||
params: codex_app_server_protocol::ModelProviderDefaultSetParams,
|
||||
) -> futures::future::BoxFuture<'_, Result<ClientResponsePayload, JSONRPCErrorError>> {
|
||||
// Keep provider config resolution off the shared request dispatcher stack.
|
||||
Box::pin(async move {
|
||||
let result = self
|
||||
.config_manager
|
||||
.model_provider_default_set(params)
|
||||
.await
|
||||
.map_err(map_error);
|
||||
let response = self.handle_config_mutation_result(result).await?;
|
||||
if response.status == codex_app_server_protocol::WriteStatus::Ok {
|
||||
self.model_catalog.invalidate().await;
|
||||
}
|
||||
Ok(ClientResponsePayload::ModelProviderDefaultSet(response))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn model_provider_capabilities_read(
|
||||
&self,
|
||||
) -> Result<ModelProviderCapabilitiesReadResponse, JSONRPCErrorError> {
|
||||
|
||||
@@ -96,6 +96,20 @@ X-Local = "no"
|
||||
timeout(Duration::from_secs(/*secs*/ 60), server.read_response(id)).await??;
|
||||
assert_eq!(started.model_provider, "gateway");
|
||||
|
||||
let id = server
|
||||
.send_request(
|
||||
"modelProvider/default/set",
|
||||
Some(json!({"providerId": "openai"})),
|
||||
)
|
||||
.await?;
|
||||
let rejected = server
|
||||
.read_stream_until_error_message(RequestId::Integer(id))
|
||||
.await?;
|
||||
assert_eq!(
|
||||
rejected.error.data,
|
||||
Some(json!({"config_write_error_code": "configRequirementReadonly"}))
|
||||
);
|
||||
|
||||
for (key, value) in [
|
||||
("model_provider", json!("openai")),
|
||||
(
|
||||
|
||||
@@ -5,12 +5,14 @@ use app_test_support::ChatGptAuthFixture;
|
||||
use app_test_support::TestAppServer;
|
||||
use app_test_support::write_chatgpt_auth;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::ConfigWriteResponse;
|
||||
use codex_app_server_protocol::LoginAccountResponse;
|
||||
use codex_app_server_protocol::LogoutAccountResponse;
|
||||
use codex_app_server_protocol::ModelListParams;
|
||||
use codex_app_server_protocol::ModelListResponse;
|
||||
use codex_app_server_protocol::ModelProviderConfigWriteResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::WriteStatus;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
@@ -307,7 +309,20 @@ name = 'Other'
|
||||
)
|
||||
.await?;
|
||||
let _: ModelProviderConfigWriteResponse = server.read_response(id).await?;
|
||||
assert_eq!(list_models(&mut server).await?.data[0].id, "saved-model");
|
||||
let saved = list_models(&mut server).await?;
|
||||
assert_eq!(saved.data[0].id, "saved-model");
|
||||
let id = server
|
||||
.send_request(
|
||||
"modelProvider/default/set",
|
||||
Some(json!({"providerId": "other"})),
|
||||
)
|
||||
.await?;
|
||||
let _: ConfigWriteResponse = server.read_response(id).await?;
|
||||
let id = server
|
||||
.send_request("model/list", Some(json!({"modelProvider": "gateway"})))
|
||||
.await?;
|
||||
let retained: ModelListResponse = server.read_response(id).await?;
|
||||
assert_eq!(retained, saved);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -455,6 +470,39 @@ requires_openai_auth = true
|
||||
let disk_catalog: ModelsResponse = serde_json::from_slice(&std::fs::read(&cache_path)?)?;
|
||||
assert_eq!(disk_catalog, initial_cache);
|
||||
assert_eq!(list_models(&mut server).await?, initial);
|
||||
|
||||
let id = server
|
||||
.send_request(
|
||||
"modelProvider/default/set",
|
||||
Some(json!({"providerId": "second"})),
|
||||
)
|
||||
.await?;
|
||||
let saved: ConfigWriteResponse = server.read_response(id).await?;
|
||||
assert_eq!(saved.status, WriteStatus::Ok);
|
||||
assert_eq!(list_models(&mut server).await?, selected);
|
||||
|
||||
let id = server
|
||||
.send_request(
|
||||
"modelProvider/default/set",
|
||||
Some(json!({"providerId": "missing"})),
|
||||
)
|
||||
.await?;
|
||||
let rejected = server
|
||||
.read_stream_until_error_message(RequestId::Integer(id))
|
||||
.await?;
|
||||
assert_eq!(
|
||||
rejected.error.data,
|
||||
Some(json!({"config_write_error_code": "configValidationError"}))
|
||||
);
|
||||
assert_eq!(list_models(&mut server).await?, selected);
|
||||
let id = server
|
||||
.send_request(
|
||||
"modelProvider/default/set",
|
||||
Some(json!({"providerId": "first"})),
|
||||
)
|
||||
.await?;
|
||||
let _: ConfigWriteResponse = server.read_response(id).await?;
|
||||
assert_eq!(list_models(&mut server).await?, initial);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user