mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(analytics): generate an installation_id and pass it in responsesapi client_metadata (#16912)
## Summary This adds a stable Codex installation ID and includes it on Responses API requests via `x-codex-installation-id` passed in via the `client_metadata` field for analytics/debugging. The main pieces are: - persist a UUID in `$CODEX_HOME/installation_id` - thread the installation ID into `ModelClient` - send it in `client_metadata` on Responses requests so it works consistently across HTTP and WebSocket transports
This commit is contained in:
committed by
GitHub
Unverified
parent
2b9bf5d3d4
commit
5d1671ca70
@@ -169,6 +169,8 @@ pub struct ResponsesApiRequest {
|
||||
pub prompt_cache_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text: Option<TextControls>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub client_metadata: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl From<&ResponsesApiRequest> for ResponseCreateWsRequest {
|
||||
@@ -189,7 +191,7 @@ impl From<&ResponsesApiRequest> for ResponseCreateWsRequest {
|
||||
prompt_cache_key: request.prompt_cache_key.clone(),
|
||||
text: request.text.clone(),
|
||||
generate: None,
|
||||
client_metadata: None,
|
||||
client_metadata: request.client_metadata.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +278,7 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> {
|
||||
service_tier: None,
|
||||
prompt_cache_key: None,
|
||||
text: None,
|
||||
client_metadata: None,
|
||||
};
|
||||
let client = ResponsesClient::new(transport.clone(), provider, NoAuth);
|
||||
|
||||
@@ -320,6 +321,7 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> {
|
||||
service_tier: None,
|
||||
prompt_cache_key: None,
|
||||
text: None,
|
||||
client_metadata: None,
|
||||
};
|
||||
|
||||
let mut extra_headers = HeaderMap::new();
|
||||
|
||||
@@ -118,6 +118,7 @@ use codex_response_debug_context::telemetry_api_error_message;
|
||||
use codex_response_debug_context::telemetry_transport_error_message;
|
||||
|
||||
pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta";
|
||||
pub const X_CODEX_INSTALLATION_ID_HEADER: &str = "x-codex-installation-id";
|
||||
pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state";
|
||||
pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata";
|
||||
pub const X_CODEX_PARENT_THREAD_ID_HEADER: &str = "x-codex-parent-thread-id";
|
||||
@@ -142,6 +143,7 @@ struct ModelClientState {
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
conversation_id: ThreadId,
|
||||
window_generation: AtomicU64,
|
||||
installation_id: String,
|
||||
provider: ModelProviderInfo,
|
||||
auth_env_telemetry: AuthEnvTelemetry,
|
||||
session_source: SessionSource,
|
||||
@@ -263,6 +265,7 @@ impl ModelClient {
|
||||
pub fn new(
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
conversation_id: ThreadId,
|
||||
installation_id: String,
|
||||
provider: ModelProviderInfo,
|
||||
session_source: SessionSource,
|
||||
model_verbosity: Option<VerbosityConfig>,
|
||||
@@ -280,6 +283,7 @@ impl ModelClient {
|
||||
auth_manager,
|
||||
conversation_id,
|
||||
window_generation: AtomicU64::new(0),
|
||||
installation_id,
|
||||
provider,
|
||||
auth_env_telemetry,
|
||||
session_source,
|
||||
@@ -429,7 +433,11 @@ impl ModelClient {
|
||||
text,
|
||||
};
|
||||
|
||||
let mut extra_headers = self.build_responses_identity_headers();
|
||||
let mut extra_headers = ApiHeaderMap::new();
|
||||
if let Ok(header_value) = HeaderValue::from_str(&self.state.installation_id) {
|
||||
extra_headers.insert(X_CODEX_INSTALLATION_ID_HEADER, header_value);
|
||||
}
|
||||
extra_headers.extend(self.build_responses_identity_headers());
|
||||
extra_headers.extend(build_conversation_headers(Some(
|
||||
self.state.conversation_id.to_string(),
|
||||
)));
|
||||
@@ -515,6 +523,10 @@ impl ModelClient {
|
||||
turn_metadata_header: Option<&str>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut client_metadata = HashMap::new();
|
||||
client_metadata.insert(
|
||||
X_CODEX_INSTALLATION_ID_HEADER.to_string(),
|
||||
self.state.installation_id.clone(),
|
||||
);
|
||||
client_metadata.insert(
|
||||
X_CODEX_WINDOW_ID_HEADER.to_string(),
|
||||
self.current_window_id(),
|
||||
@@ -817,6 +829,10 @@ impl ModelClientSession {
|
||||
},
|
||||
prompt_cache_key,
|
||||
text,
|
||||
client_metadata: Some(HashMap::from([(
|
||||
X_CODEX_INSTALLATION_ID_HEADER.to_string(),
|
||||
self.client.state.installation_id.clone(),
|
||||
)])),
|
||||
};
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ fn serializes_text_verbosity_when_set() {
|
||||
verbosity: Some(OpenAiVerbosity::Low),
|
||||
format: None,
|
||||
}),
|
||||
client_metadata: None,
|
||||
};
|
||||
|
||||
let v = serde_json::to_value(&req).expect("json");
|
||||
@@ -69,6 +70,7 @@ fn serializes_text_schema_with_strict_format() {
|
||||
prompt_cache_key: None,
|
||||
service_tier: None,
|
||||
text: Some(text_controls),
|
||||
client_metadata: None,
|
||||
};
|
||||
|
||||
let v = serde_json::to_value(&req).expect("json");
|
||||
@@ -106,6 +108,7 @@ fn omits_text_when_not_set() {
|
||||
prompt_cache_key: None,
|
||||
service_tier: None,
|
||||
text: None,
|
||||
client_metadata: None,
|
||||
};
|
||||
|
||||
let v = serde_json::to_value(&req).expect("json");
|
||||
@@ -128,6 +131,7 @@ fn serializes_flex_service_tier_when_set() {
|
||||
prompt_cache_key: None,
|
||||
service_tier: Some(ServiceTier::Flex.to_string()),
|
||||
text: None,
|
||||
client_metadata: None,
|
||||
};
|
||||
|
||||
let v = serde_json::to_value(&req).expect("json");
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::AuthRequestTelemetryContext;
|
||||
use super::ModelClient;
|
||||
use super::PendingUnauthorizedRetry;
|
||||
use super::UnauthorizedRecoveryExecution;
|
||||
use super::X_CODEX_INSTALLATION_ID_HEADER;
|
||||
use super::X_CODEX_PARENT_THREAD_ID_HEADER;
|
||||
use super::X_CODEX_TURN_METADATA_HEADER;
|
||||
use super::X_CODEX_WINDOW_ID_HEADER;
|
||||
@@ -23,6 +24,7 @@ fn test_model_client(session_source: SessionSource) -> ModelClient {
|
||||
ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
ThreadId::new(),
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
provider,
|
||||
session_source,
|
||||
/*model_verbosity*/ None,
|
||||
@@ -107,6 +109,10 @@ fn build_ws_client_metadata_includes_window_lineage_and_turn_metadata() {
|
||||
assert_eq!(
|
||||
client_metadata,
|
||||
std::collections::HashMap::from([
|
||||
(
|
||||
X_CODEX_INSTALLATION_ID_HEADER.to_string(),
|
||||
"11111111-1111-4111-8111-111111111111".to_string(),
|
||||
),
|
||||
(
|
||||
X_CODEX_WINDOW_ID_HEADER.to_string(),
|
||||
format!("{conversation_id}:1"),
|
||||
|
||||
@@ -24,6 +24,7 @@ use crate::compact_remote::run_inline_remote_auto_compact_task;
|
||||
use crate::config::ManagedFeatures;
|
||||
use crate::connectors;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::installation_id::resolve_installation_id;
|
||||
use crate::parse_turn_item;
|
||||
use crate::path_utils::normalize_for_native_workdir;
|
||||
use crate::realtime_conversation::RealtimeConversationManager;
|
||||
@@ -1927,6 +1928,7 @@ impl Session {
|
||||
});
|
||||
}
|
||||
|
||||
let installation_id = resolve_installation_id(&config.codex_home).await?;
|
||||
let services = SessionServices {
|
||||
// Initialize the MCP connection manager with an uninitialized
|
||||
// instance. It will be replaced with one created via
|
||||
@@ -1970,6 +1972,7 @@ impl Session {
|
||||
model_client: ModelClient::new(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
conversation_id,
|
||||
installation_id,
|
||||
session_configuration.provider.clone(),
|
||||
session_configuration.session_source.clone(),
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -255,6 +255,7 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
|
||||
/*auth_manager*/ None,
|
||||
ThreadId::try_from("00000000-0000-4000-8000-000000000001")
|
||||
.expect("test thread id should be valid"),
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
ModelProviderInfo::create_openai_provider(/* base_url */ /*base_url*/ None),
|
||||
codex_protocol::protocol::SessionSource::Exec,
|
||||
/*model_verbosity*/ None,
|
||||
@@ -2772,6 +2773,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
model_client: ModelClient::new(
|
||||
Some(auth_manager.clone()),
|
||||
conversation_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
session_configuration.provider.clone(),
|
||||
session_configuration.session_source.clone(),
|
||||
config.model_verbosity,
|
||||
@@ -3613,6 +3615,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
model_client: ModelClient::new(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
conversation_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
session_configuration.provider.clone(),
|
||||
session_configuration.session_source.clone(),
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
use std::io::Result;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) const INSTALLATION_ID_FILENAME: &str = "installation_id";
|
||||
|
||||
pub(crate) async fn resolve_installation_id(codex_home: &Path) -> Result<String> {
|
||||
let path = codex_home.join(INSTALLATION_ID_FILENAME);
|
||||
fs::create_dir_all(codex_home).await?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut options = OpenOptions::new();
|
||||
options.read(true).write(true).create(true);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
options.mode(0o644);
|
||||
}
|
||||
|
||||
let mut file = options.open(&path)?;
|
||||
file.lock()?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let metadata = file.metadata()?;
|
||||
let current_mode = metadata.permissions().mode() & 0o777;
|
||||
if current_mode != 0o644 {
|
||||
let mut permissions = metadata.permissions();
|
||||
permissions.set_mode(0o644);
|
||||
file.set_permissions(permissions)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
let trimmed = contents.trim();
|
||||
if !trimmed.is_empty()
|
||||
&& let Ok(existing) = Uuid::parse_str(trimmed)
|
||||
{
|
||||
return Ok(existing.to_string());
|
||||
}
|
||||
|
||||
let installation_id = Uuid::new_v4().to_string();
|
||||
file.set_len(0)?;
|
||||
file.seek(SeekFrom::Start(0))?;
|
||||
file.write_all(installation_id.as_bytes())?;
|
||||
file.flush()?;
|
||||
file.sync_all()?;
|
||||
|
||||
Ok(installation_id)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::INSTALLATION_ID_FILENAME;
|
||||
use super::resolve_installation_id;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_installation_id_generates_and_persists_uuid() {
|
||||
let codex_home = TempDir::new().expect("create temp dir");
|
||||
let persisted_path = codex_home.path().join(INSTALLATION_ID_FILENAME);
|
||||
|
||||
let installation_id = resolve_installation_id(codex_home.path())
|
||||
.await
|
||||
.expect("resolve installation id");
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&persisted_path).expect("read persisted installation id"),
|
||||
installation_id
|
||||
);
|
||||
assert!(Uuid::parse_str(&installation_id).is_ok());
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mode = std::fs::metadata(&persisted_path)
|
||||
.expect("read installation id metadata")
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(mode, 0o644);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_installation_id_reuses_existing_uuid() {
|
||||
let codex_home = TempDir::new().expect("create temp dir");
|
||||
let existing = Uuid::new_v4().to_string().to_uppercase();
|
||||
std::fs::write(
|
||||
codex_home.path().join(INSTALLATION_ID_FILENAME),
|
||||
existing.clone(),
|
||||
)
|
||||
.expect("write installation id");
|
||||
|
||||
let resolved = resolve_installation_id(codex_home.path())
|
||||
.await
|
||||
.expect("resolve installation id");
|
||||
|
||||
assert_eq!(
|
||||
resolved,
|
||||
Uuid::parse_str(existing.as_str())
|
||||
.expect("parse existing installation id")
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_installation_id_rewrites_invalid_file_contents() {
|
||||
let codex_home = TempDir::new().expect("create temp dir");
|
||||
std::fs::write(
|
||||
codex_home.path().join(INSTALLATION_ID_FILENAME),
|
||||
"not-a-uuid",
|
||||
)
|
||||
.expect("write invalid installation id");
|
||||
|
||||
let resolved = resolve_installation_id(codex_home.path())
|
||||
.await
|
||||
.expect("resolve installation id");
|
||||
|
||||
assert!(Uuid::parse_str(&resolved).is_ok());
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(codex_home.path().join(INSTALLATION_ID_FILENAME))
|
||||
.expect("read rewritten installation id"),
|
||||
resolved
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ mod flags;
|
||||
mod git_info_tests;
|
||||
mod guardian;
|
||||
mod hook_runtime;
|
||||
mod installation_id;
|
||||
pub(crate) mod instructions;
|
||||
pub(crate) mod landlock;
|
||||
pub use landlock::spawn_command_under_linux_sandbox;
|
||||
@@ -179,6 +180,7 @@ pub mod util;
|
||||
|
||||
pub use client::ModelClient;
|
||||
pub use client::ModelClientSession;
|
||||
pub use client::X_CODEX_INSTALLATION_ID_HEADER;
|
||||
pub use client::X_CODEX_TURN_METADATA_HEADER;
|
||||
pub use client_common::Prompt;
|
||||
pub use client_common::REVIEW_PROMPT;
|
||||
|
||||
@@ -31,6 +31,8 @@ fn normalize_git_remote_url(url: &str) -> String {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
const TEST_INSTALLATION_ID: &str = "11111111-1111-4111-8111-111111111111";
|
||||
|
||||
#[tokio::test]
|
||||
async fn responses_stream_includes_subagent_header_on_review() {
|
||||
core_test_support::skip_if_no_network!();
|
||||
@@ -98,6 +100,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
let client = ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
session_source,
|
||||
config.model_verbosity,
|
||||
@@ -147,6 +150,10 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
Some(expected_window_id.as_str())
|
||||
);
|
||||
assert_eq!(request.header("x-codex-parent-thread-id"), None);
|
||||
assert_eq!(
|
||||
request.body_json()["client_metadata"]["x-codex-installation-id"].as_str(),
|
||||
Some(TEST_INSTALLATION_ID)
|
||||
);
|
||||
assert_eq!(request.header("x-codex-sandbox"), None);
|
||||
}
|
||||
|
||||
@@ -218,6 +225,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
let client = ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
session_source,
|
||||
config.model_verbosity,
|
||||
@@ -331,6 +339,7 @@ async fn responses_respects_model_info_overrides_from_config() {
|
||||
let client = ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
session_source,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -81,6 +81,8 @@ use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
use wiremock::matchers::query_param;
|
||||
|
||||
const INSTALLATION_ID_FILENAME: &str = "installation_id";
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
fn assert_message_role(request_body: &serde_json::Value, role: &str) {
|
||||
assert_eq!(request_body["role"].as_str().unwrap(), role);
|
||||
@@ -760,10 +762,18 @@ async fn includes_conversation_id_and_model_headers_in_request() {
|
||||
.header("authorization")
|
||||
.expect("authorization header");
|
||||
let request_originator = request.header("originator").expect("originator header");
|
||||
let request_body = request.body_json();
|
||||
let installation_id =
|
||||
std::fs::read_to_string(test.codex_home_path().join(INSTALLATION_ID_FILENAME))
|
||||
.expect("read installation id");
|
||||
|
||||
assert_eq!(request_session_id, session_id.to_string());
|
||||
assert_eq!(request_originator, originator().value);
|
||||
assert_eq!(request_authorization, "Bearer Test API Key");
|
||||
assert_eq!(
|
||||
request_body["client_metadata"]["x-codex-installation-id"].as_str(),
|
||||
Some(installation_id.as_str())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -868,6 +878,7 @@ async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuth
|
||||
"unused-api-key",
|
||||
))),
|
||||
conversation_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
provider,
|
||||
SessionSource::Exec,
|
||||
config.model_verbosity,
|
||||
@@ -1048,11 +1059,18 @@ async fn chatgpt_auth_sends_correct_request() {
|
||||
let request_body = request.body_json();
|
||||
|
||||
let session_id = request.header("session_id").expect("session_id header");
|
||||
let installation_id =
|
||||
std::fs::read_to_string(test.codex_home_path().join(INSTALLATION_ID_FILENAME))
|
||||
.expect("read installation id");
|
||||
assert_eq!(session_id, thread_id.to_string());
|
||||
|
||||
assert_eq!(request_originator, originator().value);
|
||||
assert_eq!(request_authorization, "Bearer Access Token");
|
||||
assert_eq!(request_chatgpt_account_id, "account_id");
|
||||
assert_eq!(
|
||||
request_body["client_metadata"]["x-codex-installation-id"].as_str(),
|
||||
Some(installation_id.as_str())
|
||||
);
|
||||
assert!(request_body["stream"].as_bool().unwrap());
|
||||
assert_eq!(
|
||||
request_body["include"][0].as_str().unwrap(),
|
||||
@@ -2179,6 +2197,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
let client = ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
provider.clone(),
|
||||
SessionSource::Exec,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -55,6 +55,7 @@ const MODEL: &str = "gpt-5.2-codex";
|
||||
const OPENAI_BETA_HEADER: &str = "OpenAI-Beta";
|
||||
const WS_V2_BETA_HEADER_VALUE: &str = "responses_websockets=2026-02-06";
|
||||
const X_CLIENT_REQUEST_ID_HEADER: &str = "x-client-request-id";
|
||||
const TEST_INSTALLATION_ID: &str = "11111111-1111-4111-8111-111111111111";
|
||||
|
||||
fn assert_request_trace_matches(body: &serde_json::Value, expected_trace: &W3cTraceContext) {
|
||||
let client_metadata = body["client_metadata"]
|
||||
@@ -125,6 +126,10 @@ async fn responses_websocket_streams_request() {
|
||||
handshake.header(X_CLIENT_REQUEST_ID_HEADER),
|
||||
Some(harness.conversation_id.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
body["client_metadata"]["x-codex-installation-id"].as_str(),
|
||||
Some(TEST_INSTALLATION_ID)
|
||||
);
|
||||
|
||||
server.shutdown().await;
|
||||
}
|
||||
@@ -1756,6 +1761,7 @@ async fn websocket_harness_with_provider_options(
|
||||
let client = ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
SessionSource::Exec,
|
||||
config.model_verbosity,
|
||||
|
||||
Reference in New Issue
Block a user