feat: codex subscription support (#6600)
Signed-off-by: Adrian Cole <adrian@tetrate.io> Co-authored-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Generated
+7
@@ -3050,6 +3050,7 @@ dependencies = [
|
|||||||
"urlencoding",
|
"urlencoding",
|
||||||
"utoipa",
|
"utoipa",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"v_htmlescape",
|
||||||
"webbrowser",
|
"webbrowser",
|
||||||
"which 8.0.0",
|
"which 8.0.0",
|
||||||
"winapi",
|
"winapi",
|
||||||
@@ -8584,6 +8585,12 @@ dependencies = [
|
|||||||
"vsimd",
|
"vsimd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "v_htmlescape"
|
||||||
|
version = "0.15.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
crates/goose-bench/src/eval_suites/core/developer/simple_repo_clone_test.rs::run
|
|
||||||
crates/goose-cli/src/cli.rs::cli
|
|
||||||
crates/goose-cli/src/commands/configure.rs::configure_extensions_dialog
|
|
||||||
crates/goose-cli/src/commands/configure.rs::configure_provider_dialog
|
crates/goose-cli/src/commands/configure.rs::configure_provider_dialog
|
||||||
crates/goose-cli/src/commands/configure.rs::configure_tool_permissions_dialog
|
crates/goose-cli/src/commands/configure.rs::configure_tool_permissions_dialog
|
||||||
crates/goose-cli/src/commands/configure.rs::handle_configure
|
|
||||||
crates/goose-cli/src/commands/project.rs::handle_project_default
|
crates/goose-cli/src/commands/project.rs::handle_project_default
|
||||||
crates/goose-cli/src/commands/project.rs::handle_projects_interactive
|
crates/goose-cli/src/commands/project.rs::handle_projects_interactive
|
||||||
crates/goose-cli/src/commands/web.rs::process_message_streaming
|
|
||||||
crates/goose-cli/src/session/builder.rs::build_session
|
crates/goose-cli/src/session/builder.rs::build_session
|
||||||
crates/goose-cli/src/session/export.rs::tool_response_to_markdown
|
crates/goose-cli/src/session/export.rs::tool_response_to_markdown
|
||||||
crates/goose-cli/src/session/mod.rs::interactive
|
|
||||||
crates/goose-cli/src/session/mod.rs::process_agent_response
|
crates/goose-cli/src/session/mod.rs::process_agent_response
|
||||||
crates/goose-mcp/src/computercontroller/docx_tool.rs::docx_tool
|
|
||||||
crates/goose-mcp/src/computercontroller/mod.rs::new
|
crates/goose-mcp/src/computercontroller/mod.rs::new
|
||||||
crates/goose-mcp/src/computercontroller/pdf_tool.rs::pdf_tool
|
crates/goose-mcp/src/computercontroller/pdf_tool.rs::pdf_tool
|
||||||
crates/goose-mcp/src/memory/mod.rs::new
|
crates/goose-mcp/src/memory/mod.rs::new
|
||||||
@@ -23,12 +16,10 @@ crates/goose/src/agents/agent.rs::create_recipe
|
|||||||
crates/goose/src/agents/agent.rs::dispatch_tool_call
|
crates/goose/src/agents/agent.rs::dispatch_tool_call
|
||||||
crates/goose/src/agents/agent.rs::reply
|
crates/goose/src/agents/agent.rs::reply
|
||||||
crates/goose/src/agents/agent.rs::reply_internal
|
crates/goose/src/agents/agent.rs::reply_internal
|
||||||
crates/goose/src/agents/extension_manager.rs::add_extension
|
|
||||||
crates/goose/src/providers/formats/anthropic.rs::format_messages
|
crates/goose/src/providers/formats/anthropic.rs::format_messages
|
||||||
crates/goose/src/providers/formats/anthropic.rs::response_to_streaming_message<S>
|
crates/goose/src/providers/formats/anthropic.rs::response_to_streaming_message
|
||||||
crates/goose/src/providers/formats/databricks.rs::format_messages
|
crates/goose/src/providers/formats/databricks.rs::format_messages
|
||||||
crates/goose/src/providers/formats/google.rs::format_messages
|
crates/goose/src/providers/formats/google.rs::format_messages
|
||||||
crates/goose/src/providers/formats/openai.rs::format_messages
|
crates/goose/src/providers/formats/openai.rs::format_messages
|
||||||
crates/goose/src/providers/formats/openai.rs::response_to_streaming_message<S>
|
crates/goose/src/providers/formats/openai.rs::response_to_streaming_message
|
||||||
crates/goose/src/providers/gcpvertexai.rs::post_with_location
|
|
||||||
crates/goose/src/providers/snowflake.rs::post
|
crates/goose/src/providers/snowflake.rs::post
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ derive_utoipa!(Icon as IconSchema);
|
|||||||
super::routes::config_management::remove_custom_provider,
|
super::routes::config_management::remove_custom_provider,
|
||||||
super::routes::config_management::check_provider,
|
super::routes::config_management::check_provider,
|
||||||
super::routes::config_management::set_config_provider,
|
super::routes::config_management::set_config_provider,
|
||||||
|
super::routes::config_management::configure_provider_oauth,
|
||||||
super::routes::config_management::get_pricing,
|
super::routes::config_management::get_pricing,
|
||||||
super::routes::prompts::get_prompts,
|
super::routes::prompts::get_prompts,
|
||||||
super::routes::prompts::get_prompt,
|
super::routes::prompts::get_prompt,
|
||||||
|
|||||||
@@ -210,6 +210,13 @@ fn mask_secret(secret: Value) -> String {
|
|||||||
format!("{}{}", visible, mask)
|
format!("{}{}", visible, mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_valid_provider_name(provider_name: &str) -> bool {
|
||||||
|
!provider_name.is_empty()
|
||||||
|
&& provider_name
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/config/read",
|
path = "/config/read",
|
||||||
@@ -823,6 +830,54 @@ pub async fn set_config_provider(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/config/providers/{name}/oauth",
|
||||||
|
params(
|
||||||
|
("name" = String, Path, description = "Provider name")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "OAuth configuration completed"),
|
||||||
|
(status = 400, description = "OAuth configuration failed")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn configure_provider_oauth(
|
||||||
|
Path(provider_name): Path<String>,
|
||||||
|
) -> Result<Json<String>, (StatusCode, String)> {
|
||||||
|
use goose::model::ModelConfig;
|
||||||
|
use goose::providers::create;
|
||||||
|
|
||||||
|
if !is_valid_provider_name(&provider_name) {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "Invalid provider name".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let temp_model =
|
||||||
|
ModelConfig::new("temp").map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
|
||||||
|
|
||||||
|
let provider = create(&provider_name, temp_model).await.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
format!("Failed to create provider: {}", e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
provider.configure_oauth().await.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
format!("OAuth configuration failed: {}", e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Mark the provider as configured after successful OAuth
|
||||||
|
let configured_marker = format!("{}_configured", provider_name);
|
||||||
|
let config = goose::config::Config::global();
|
||||||
|
config
|
||||||
|
.set_param(&configured_marker, true)
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Json("OAuth configuration completed".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn routes(state: Arc<AppState>) -> Router {
|
pub fn routes(state: Arc<AppState>) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/config", get(read_all_config))
|
.route("/config", get(read_all_config))
|
||||||
@@ -851,6 +906,10 @@ pub fn routes(state: Arc<AppState>) -> Router {
|
|||||||
.route("/config/custom-providers/{id}", get(get_custom_provider))
|
.route("/config/custom-providers/{id}", get(get_custom_provider))
|
||||||
.route("/config/check_provider", post(check_provider))
|
.route("/config/check_provider", post(check_provider))
|
||||||
.route("/config/set_provider", post(set_config_provider))
|
.route("/config/set_provider", post(set_config_provider))
|
||||||
|
.route(
|
||||||
|
"/config/providers/{name}/oauth",
|
||||||
|
post(configure_provider_oauth),
|
||||||
|
)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,16 @@ pub fn check_provider_configured(metadata: &ProviderMetadata, provider_type: Pro
|
|||||||
.is_ok();
|
.is_ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case: OAuth providers - check for configured marker
|
||||||
|
let has_oauth_key = metadata.config_keys.iter().any(|key| key.oauth_flow);
|
||||||
|
if has_oauth_key {
|
||||||
|
let configured_marker = format!("{}_configured", metadata.name);
|
||||||
|
if matches!(config.get_param::<bool>(&configured_marker), Ok(true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Special case: Zero-config providers (no config keys)
|
// Special case: Zero-config providers (no config keys)
|
||||||
if metadata.config_keys.is_empty() {
|
if metadata.config_keys.is_empty() {
|
||||||
// Check if the provider has been explicitly configured via the UI
|
// Check if the provider has been explicitly configured via the UI
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ rand = "0.8.5"
|
|||||||
utoipa = { version = "4.1", features = ["chrono"] }
|
utoipa = { version = "4.1", features = ["chrono"] }
|
||||||
tokio-cron-scheduler = "0.14.0"
|
tokio-cron-scheduler = "0.14.0"
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
|
v_htmlescape = "0.15"
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite", "chrono", "json"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite", "chrono", "json"] }
|
||||||
|
|
||||||
# For Bedrock provider
|
# For Bedrock provider
|
||||||
|
|||||||
@@ -633,9 +633,13 @@ impl McpClientTrait for AppsManagerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn schema<T: JsonSchema>() -> JsonObject {
|
fn schema<T: JsonSchema>() -> JsonObject {
|
||||||
serde_json::to_value(schema_for!(T))
|
let mut obj = serde_json::to_value(schema_for!(T))
|
||||||
.map(|v| v.as_object().unwrap().clone())
|
.map(|v| v.as_object().unwrap().clone())
|
||||||
.expect("valid schema")
|
.expect("valid schema");
|
||||||
|
// Ensure properties key exists (required by OpenAI-compatible APIs)
|
||||||
|
obj.entry("properties")
|
||||||
|
.or_insert_with(|| serde_json::json!({}));
|
||||||
|
obj
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_string(args: &JsonObject, key: &str) -> Result<String, String> {
|
fn extract_string(args: &JsonObject, key: &str) -> Result<String, String> {
|
||||||
|
|||||||
@@ -3724,6 +3724,10 @@
|
|||||||
"provider_model": "gpt-5.1-codex-mini",
|
"provider_model": "gpt-5.1-codex-mini",
|
||||||
"canonical_model": "openai/gpt-5.1-codex-mini"
|
"canonical_model": "openai/gpt-5.1-codex-mini"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"provider_model": "gpt-5.2-codex",
|
||||||
|
"canonical_model": "openai/gpt-5.2-codex"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"provider_model": "gpt-5.2",
|
"provider_model": "gpt-5.2",
|
||||||
"canonical_model": "openai/gpt-5.2"
|
"canonical_model": "openai/gpt-5.2"
|
||||||
|
|||||||
@@ -2033,6 +2033,27 @@
|
|||||||
"completion": 0.000014
|
"completion": 0.000014
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "openai/gpt-5.2-codex",
|
||||||
|
"name": "OpenAI: GPT-5.2-Codex",
|
||||||
|
"context_length": 400000,
|
||||||
|
"max_completion_tokens": 128000,
|
||||||
|
"input_modalities": [
|
||||||
|
"file",
|
||||||
|
"image",
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"output_modalities": [
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"supports_tools": true,
|
||||||
|
"pricing": {
|
||||||
|
"prompt": 1.75e-6,
|
||||||
|
"completion": 0.000014,
|
||||||
|
"request": 0.0,
|
||||||
|
"image": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "openai/gpt-5.2-pro",
|
"id": "openai/gpt-5.2-pro",
|
||||||
"name": "OpenAI: GPT-5.2 Pro",
|
"name": "OpenAI: GPT-5.2 Pro",
|
||||||
|
|||||||
@@ -133,7 +133,10 @@ fn swap_claude_word_order(model: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_hosting_provider(provider: &str) -> bool {
|
fn is_hosting_provider(provider: &str) -> bool {
|
||||||
matches!(provider, "databricks" | "openrouter" | "azure" | "bedrock")
|
matches!(
|
||||||
|
provider,
|
||||||
|
"databricks" | "openrouter" | "azure" | "bedrock" | "chatgpt_codex"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Infer the real provider from model name patterns
|
/// Infer the real provider from model name patterns
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ use super::{
|
|||||||
azure::AzureProvider,
|
azure::AzureProvider,
|
||||||
base::{Provider, ProviderMetadata},
|
base::{Provider, ProviderMetadata},
|
||||||
bedrock::BedrockProvider,
|
bedrock::BedrockProvider,
|
||||||
|
chatgpt_codex::ChatGptCodexProvider,
|
||||||
claude_code::ClaudeCodeProvider,
|
claude_code::ClaudeCodeProvider,
|
||||||
codex::CodexProvider,
|
codex::CodexProvider,
|
||||||
cursor_agent::CursorAgentProvider,
|
cursor_agent::CursorAgentProvider,
|
||||||
@@ -46,6 +47,10 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
|
|||||||
.register::<AnthropicProvider, _>(|m| Box::pin(AnthropicProvider::from_env(m)), true);
|
.register::<AnthropicProvider, _>(|m| Box::pin(AnthropicProvider::from_env(m)), true);
|
||||||
registry.register::<AzureProvider, _>(|m| Box::pin(AzureProvider::from_env(m)), false);
|
registry.register::<AzureProvider, _>(|m| Box::pin(AzureProvider::from_env(m)), false);
|
||||||
registry.register::<BedrockProvider, _>(|m| Box::pin(BedrockProvider::from_env(m)), false);
|
registry.register::<BedrockProvider, _>(|m| Box::pin(BedrockProvider::from_env(m)), false);
|
||||||
|
registry.register::<ChatGptCodexProvider, _>(
|
||||||
|
|m| Box::pin(ChatGptCodexProvider::from_env(m)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
registry
|
registry
|
||||||
.register::<ClaudeCodeProvider, _>(|m| Box::pin(ClaudeCodeProvider::from_env(m)), true);
|
.register::<ClaudeCodeProvider, _>(|m| Box::pin(ClaudeCodeProvider::from_env(m)), true);
|
||||||
registry.register::<CodexProvider, _>(|m| Box::pin(CodexProvider::from_env(m)), true);
|
registry.register::<CodexProvider, _>(|m| Box::pin(CodexProvider::from_env(m)), true);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub mod azureauth;
|
|||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod bedrock;
|
pub mod bedrock;
|
||||||
pub mod canonical;
|
pub mod canonical;
|
||||||
|
pub mod chatgpt_codex;
|
||||||
pub mod claude_code;
|
pub mod claude_code;
|
||||||
pub mod codex;
|
pub mod codex;
|
||||||
pub mod cursor_agent;
|
pub mod cursor_agent;
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ parse_violation() {
|
|||||||
case "$violation_parser" in
|
case "$violation_parser" in
|
||||||
"function_name")
|
"function_name")
|
||||||
jq -r 'select(.message.code.code == "'"$rule_code"'") |
|
jq -r 'select(.message.code.code == "'"$rule_code"'") |
|
||||||
"\(.message.spans[0].file_name)::\(.message.spans[0].text[0].text | split("fn ")[1] | split("(")[0])"'
|
.message.spans[0] as $span |
|
||||||
|
($span.text | map(.text) | map(select(test("\\bfn\\b"))) | first // "") as $line |
|
||||||
|
if $line == "" then empty else "\($span.file_name)::\($line | capture("fn\\s+(?<name>[a-z_][a-z0-9_]*)") | .name)" end'
|
||||||
;;
|
;;
|
||||||
"type_name")
|
"type_name")
|
||||||
jq -r 'select(.message.code.code == "'"$rule_code"'") |
|
jq -r 'select(.message.code.code == "'"$rule_code"'") |
|
||||||
|
|||||||
@@ -1325,6 +1325,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/config/providers/{name}/oauth": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"operationId": "configure_provider_oauth",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Provider name",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OAuth configuration completed"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "OAuth configuration failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/config/read": {
|
"/config/read": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2261,6 +2261,32 @@ export type GetProviderModelsResponses = {
|
|||||||
|
|
||||||
export type GetProviderModelsResponse = GetProviderModelsResponses[keyof GetProviderModelsResponses];
|
export type GetProviderModelsResponse = GetProviderModelsResponses[keyof GetProviderModelsResponses];
|
||||||
|
|
||||||
|
export type ConfigureProviderOauthData = {
|
||||||
|
body?: never;
|
||||||
|
path: {
|
||||||
|
/**
|
||||||
|
* Provider name
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
query?: never;
|
||||||
|
url: '/config/providers/{name}/oauth';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigureProviderOauthErrors = {
|
||||||
|
/**
|
||||||
|
* OAuth configuration failed
|
||||||
|
*/
|
||||||
|
400: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfigureProviderOauthResponses = {
|
||||||
|
/**
|
||||||
|
* OAuth configuration completed
|
||||||
|
*/
|
||||||
|
200: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
export type ReadConfigData = {
|
export type ReadConfigData = {
|
||||||
body: ConfigKeyQuery;
|
body: ConfigKeyQuery;
|
||||||
path?: never;
|
path?: never;
|
||||||
|
|||||||
@@ -73,20 +73,19 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full p-4 sm:p-6 bg-background-muted border border-background-hover rounded-xl">
|
<div className="w-full p-3 sm:p-4 bg-background-muted border border-background-hover rounded-xl">
|
||||||
<div className="flex items-start justify-between mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="flex-1">
|
<Key className="w-4 h-4 text-text-standard flex-shrink-0" />
|
||||||
<Key className="w-4 h-4 mb-3 text-text-standard" />
|
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
||||||
<h3 className="font-medium text-text-standard text-sm sm:text-base">
|
<h3 className="font-medium text-text-standard text-sm sm:text-base">
|
||||||
Quick Setup with API Key
|
Quick Setup with API Key
|
||||||
</h3>
|
</h3>
|
||||||
|
<span className="text-text-muted text-xs sm:text-sm">
|
||||||
|
Auto-detect your provider
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-text-muted text-sm sm:text-base mb-4">
|
|
||||||
Enter your API key and we'll automatically detect which provider it works with.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex gap-2 items-stretch">
|
<div className="flex gap-2 items-stretch">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useConfig } from './ConfigContext';
|
|||||||
import { SetupModal } from './SetupModal';
|
import { SetupModal } from './SetupModal';
|
||||||
import { startOpenRouterSetup } from '../utils/openRouterSetup';
|
import { startOpenRouterSetup } from '../utils/openRouterSetup';
|
||||||
import { startTetrateSetup } from '../utils/tetrateSetup';
|
import { startTetrateSetup } from '../utils/tetrateSetup';
|
||||||
|
import { startChatGptCodexSetup } from '../utils/chatgptCodexSetup';
|
||||||
import WelcomeGooseLogo from './WelcomeGooseLogo';
|
import WelcomeGooseLogo from './WelcomeGooseLogo';
|
||||||
import { toastService } from '../toasts';
|
import { toastService } from '../toasts';
|
||||||
import { OllamaSetup } from './OllamaSetup';
|
import { OllamaSetup } from './OllamaSetup';
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
trackOnboardingSetupFailed,
|
trackOnboardingSetupFailed,
|
||||||
} from '../utils/analytics';
|
} from '../utils/analytics';
|
||||||
|
|
||||||
import { Goose, OpenRouter, Tetrate } from './icons';
|
import { Goose, OpenRouter, Tetrate, ChatGPT } from './icons';
|
||||||
|
|
||||||
interface ProviderGuardProps {
|
interface ProviderGuardProps {
|
||||||
didSelectProvider: boolean;
|
didSelectProvider: boolean;
|
||||||
@@ -69,6 +70,14 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
|||||||
autoClose?: number;
|
autoClose?: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
|
const [chatgptCodexSetupState, setChatgptCodexSetupState] = useState<{
|
||||||
|
show: boolean;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
showRetry: boolean;
|
||||||
|
autoClose?: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
const handleTetrateSetup = async () => {
|
const handleTetrateSetup = async () => {
|
||||||
trackOnboardingProviderSelected('tetrate');
|
trackOnboardingProviderSelected('tetrate');
|
||||||
try {
|
try {
|
||||||
@@ -97,6 +106,34 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChatGptCodexSetup = async () => {
|
||||||
|
trackOnboardingProviderSelected('chatgpt_codex');
|
||||||
|
try {
|
||||||
|
const result = await startChatGptCodexSetup();
|
||||||
|
if (result.success) {
|
||||||
|
setSwitchModelProvider('chatgpt_codex');
|
||||||
|
setShowSwitchModelModal(true);
|
||||||
|
} else {
|
||||||
|
trackOnboardingSetupFailed('chatgpt_codex', result.message);
|
||||||
|
setChatgptCodexSetupState({
|
||||||
|
show: true,
|
||||||
|
title: 'Setup Failed',
|
||||||
|
message: result.message,
|
||||||
|
showRetry: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('ChatGPT Codex setup error:', error);
|
||||||
|
trackOnboardingSetupFailed('chatgpt_codex', 'unexpected_error');
|
||||||
|
setChatgptCodexSetupState({
|
||||||
|
show: true,
|
||||||
|
title: 'Setup Error',
|
||||||
|
message: 'An unexpected error occurred during setup.',
|
||||||
|
showRetry: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleApiKeySuccess = async (provider: string, _model: string, apiKey: string) => {
|
const handleApiKeySuccess = async (provider: string, _model: string, apiKey: string) => {
|
||||||
trackOnboardingProviderSelected('api_key');
|
trackOnboardingProviderSelected('api_key');
|
||||||
const keyName = `${provider.toUpperCase()}_API_KEY`;
|
const keyName = `${provider.toUpperCase()}_API_KEY`;
|
||||||
@@ -163,21 +200,26 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
|||||||
setShowOllamaSetup(false);
|
setShowOllamaSetup(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRetrySetup = (setupType: 'openrouter' | 'tetrate') => {
|
const handleRetrySetup = (setupType: 'openrouter' | 'tetrate' | 'chatgpt_codex') => {
|
||||||
if (setupType === 'openrouter') {
|
if (setupType === 'openrouter') {
|
||||||
setOpenRouterSetupState(null);
|
setOpenRouterSetupState(null);
|
||||||
handleOpenRouterSetup();
|
handleOpenRouterSetup();
|
||||||
} else {
|
} else if (setupType === 'tetrate') {
|
||||||
setTetrateSetupState(null);
|
setTetrateSetupState(null);
|
||||||
handleTetrateSetup();
|
handleTetrateSetup();
|
||||||
|
} else {
|
||||||
|
setChatgptCodexSetupState(null);
|
||||||
|
handleChatGptCodexSetup();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeSetupModal = (setupType: 'openrouter' | 'tetrate') => {
|
const closeSetupModal = (setupType: 'openrouter' | 'tetrate' | 'chatgpt_codex') => {
|
||||||
if (setupType === 'openrouter') {
|
if (setupType === 'openrouter') {
|
||||||
setOpenRouterSetupState(null);
|
setOpenRouterSetupState(null);
|
||||||
} else {
|
} else if (setupType === 'tetrate') {
|
||||||
setTetrateSetupState(null);
|
setTetrateSetupState(null);
|
||||||
|
} else {
|
||||||
|
setChatgptCodexSetupState(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -274,9 +316,49 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* ChatGPT Subscription Card - Full Width */}
|
||||||
|
<div className="relative w-full mb-4">
|
||||||
|
<div className="absolute -top-2 -right-2 sm:-top-3 sm:-right-3 z-20">
|
||||||
|
<span className="inline-block px-2 py-1 text-xs font-medium bg-blue-600 text-white rounded-full">
|
||||||
|
Recommended if you have ChatGPT Plus/Pro
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onClick={handleChatGptCodexSetup}
|
||||||
|
className="w-full p-4 sm:p-6 bg-transparent border border-background-hover rounded-xl hover:border-text-muted transition-all duration-200 cursor-pointer group"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ChatGPT className="w-5 h-5 text-text-standard" />
|
||||||
|
<span className="font-medium text-text-standard text-sm sm:text-base">
|
||||||
|
ChatGPT Subscription
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-text-muted group-hover:text-text-standard transition-colors">
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4 sm:w-5 sm:h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-text-muted text-sm sm:text-base">
|
||||||
|
Use your ChatGPT Plus/Pro subscription for GPT-5 Codex models.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Tetrate Card - Full Width */}
|
{/* Tetrate Card - Full Width */}
|
||||||
<div className="relative w-full mb-4">
|
<div className="relative w-full mb-4">
|
||||||
{/* Recommended pill */}
|
|
||||||
<div className="absolute -top-2 -right-2 sm:-top-3 sm:-right-3 z-20">
|
<div className="absolute -top-2 -right-2 sm:-top-3 sm:-right-3 z-20">
|
||||||
<span className="inline-block px-2 py-1 text-xs font-medium bg-blue-600 text-white rounded-full">
|
<span className="inline-block px-2 py-1 text-xs font-medium bg-blue-600 text-white rounded-full">
|
||||||
Recommended for new users
|
Recommended for new users
|
||||||
@@ -415,6 +497,17 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{chatgptCodexSetupState?.show && (
|
||||||
|
<SetupModal
|
||||||
|
title={chatgptCodexSetupState.title}
|
||||||
|
message={chatgptCodexSetupState.message}
|
||||||
|
showRetry={chatgptCodexSetupState.showRetry}
|
||||||
|
onRetry={() => handleRetrySetup('chatgpt_codex')}
|
||||||
|
onClose={() => closeSetupModal('chatgpt_codex')}
|
||||||
|
autoClose={chatgptCodexSetupState.autoClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{showSwitchModelModal && (
|
{showSwitchModelModal && (
|
||||||
<SwitchModelModal
|
<SwitchModelModal
|
||||||
sessionId={null}
|
sessionId={null}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export default function ChatGPT({ className = '' }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import ArrowDown from './ArrowDown';
|
|||||||
import ArrowUp from './ArrowUp';
|
import ArrowUp from './ArrowUp';
|
||||||
import Attach from './Attach';
|
import Attach from './Attach';
|
||||||
import Back from './Back';
|
import Back from './Back';
|
||||||
|
import ChatGPT from './ChatGPT';
|
||||||
import { Bird1 } from './Bird1';
|
import { Bird1 } from './Bird1';
|
||||||
import { Bird2 } from './Bird2';
|
import { Bird2 } from './Bird2';
|
||||||
import { Bird3 } from './Bird3';
|
import { Bird3 } from './Bird3';
|
||||||
@@ -59,6 +60,7 @@ export {
|
|||||||
ArrowUp,
|
ArrowUp,
|
||||||
Attach,
|
Attach,
|
||||||
Back,
|
Back,
|
||||||
|
ChatGPT,
|
||||||
Bird1,
|
Bird1,
|
||||||
Bird2,
|
Bird2,
|
||||||
Bird3,
|
Bird3,
|
||||||
|
|||||||
@@ -187,7 +187,8 @@ export const SwitchModelModal = ({
|
|||||||
// Load providers for manual model selection
|
// Load providers for manual model selection
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const providersResponse = await getProviders(false);
|
// Force refresh if initialProvider is set (OAuth flow needs fresh data)
|
||||||
|
const providersResponse = await getProviders(!!initialProvider);
|
||||||
const activeProviders = providersResponse.filter((provider) => provider.is_configured);
|
const activeProviders = providersResponse.filter((provider) => provider.is_configured);
|
||||||
// Create provider options and add "Use other provider" option
|
// Create provider options and add "Use other provider" option
|
||||||
setProviderOptions([
|
setProviderOptions([
|
||||||
@@ -260,7 +261,7 @@ export const SwitchModelModal = ({
|
|||||||
setLoadingModels(false);
|
setLoadingModels(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [getProviders, getProviderModels, usePredefinedModels, read]);
|
}, [getProviders, getProviderModels, usePredefinedModels, read, initialProvider]);
|
||||||
|
|
||||||
const filteredModelOptions = provider
|
const filteredModelOptions = provider
|
||||||
? modelOptions.filter((group) => group.options[0]?.provider === provider)
|
? modelOptions.filter((group) => group.options[0]?.provider === provider)
|
||||||
|
|||||||
@@ -16,10 +16,24 @@ import { SecureStorageNotice } from './subcomponents/SecureStorageNotice';
|
|||||||
import { providerConfigSubmitHandler } from './subcomponents/handlers/DefaultSubmitHandler';
|
import { providerConfigSubmitHandler } from './subcomponents/handlers/DefaultSubmitHandler';
|
||||||
import { useConfig } from '../../../ConfigContext';
|
import { useConfig } from '../../../ConfigContext';
|
||||||
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle, LogIn } from 'lucide-react';
|
||||||
import { ProviderDetails, removeCustomProvider } from '../../../../api';
|
import { ProviderDetails, removeCustomProvider, configureProviderOauth } from '../../../../api';
|
||||||
import { Button } from '../../../../components/ui/button';
|
import { Button } from '../../../../components/ui/button';
|
||||||
|
|
||||||
|
const formatErrorMessage = (error: unknown): string => {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(error);
|
||||||
|
} catch {
|
||||||
|
return String(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
interface ProviderConfigurationModalProps {
|
interface ProviderConfigurationModalProps {
|
||||||
provider: ProviderDetails;
|
provider: ProviderDetails;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -38,11 +52,15 @@ export default function ProviderConfigurationModal({
|
|||||||
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
|
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
|
||||||
const [isActiveProvider, setIsActiveProvider] = useState(false);
|
const [isActiveProvider, setIsActiveProvider] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isOAuthLoading, setIsOAuthLoading] = useState(false);
|
||||||
|
|
||||||
const requiredParameters = provider.metadata.config_keys.filter(
|
const requiredParameters = provider.metadata.config_keys.filter(
|
||||||
(param) => param.required === true
|
(param) => param.required === true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check if this provider uses OAuth for configuration
|
||||||
|
const isOAuthProvider = provider.metadata.config_keys.some((key) => key.oauth_flow);
|
||||||
|
|
||||||
const isConfigured = provider.is_configured;
|
const isConfigured = provider.is_configured;
|
||||||
const headerText = showDeleteConfirmation
|
const headerText = showDeleteConfirmation
|
||||||
? `Delete configuration for ${provider.metadata.display_name}`
|
? `Delete configuration for ${provider.metadata.display_name}`
|
||||||
@@ -52,7 +70,28 @@ export default function ProviderConfigurationModal({
|
|||||||
? isActiveProvider
|
? isActiveProvider
|
||||||
? `You cannot delete this provider while it's currently in use. Please switch to a different model first.`
|
? `You cannot delete this provider while it's currently in use. Please switch to a different model first.`
|
||||||
: 'This will permanently delete the current provider configuration.'
|
: 'This will permanently delete the current provider configuration.'
|
||||||
: `Add your API key(s) for this provider to integrate into Goose`;
|
: isOAuthProvider
|
||||||
|
? `Sign in with your ${provider.metadata.display_name} account to use this provider`
|
||||||
|
: `Add your API key(s) for this provider to integrate into goose`;
|
||||||
|
|
||||||
|
const handleOAuthLogin = async () => {
|
||||||
|
setIsOAuthLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await configureProviderOauth({
|
||||||
|
path: { name: provider.name },
|
||||||
|
});
|
||||||
|
if (onConfigured) {
|
||||||
|
onConfigured(provider);
|
||||||
|
} else {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(`OAuth login failed: ${formatErrorMessage(err)}`);
|
||||||
|
} finally {
|
||||||
|
setIsOAuthLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmitForm = async (e: React.FormEvent) => {
|
const handleSubmitForm = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -91,7 +130,7 @@ export default function ProviderConfigurationModal({
|
|||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(`${error}`);
|
setError(formatErrorMessage(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,38 +218,70 @@ export default function ProviderConfigurationModal({
|
|||||||
{/* Contains information used to set up each provider */}
|
{/* Contains information used to set up each provider */}
|
||||||
{/* Only show the form when NOT in delete confirmation mode */}
|
{/* Only show the form when NOT in delete confirmation mode */}
|
||||||
{!showDeleteConfirmation ? (
|
{!showDeleteConfirmation ? (
|
||||||
<>
|
isOAuthProvider ? (
|
||||||
{/* Contains information used to set up each provider */}
|
<div className="flex flex-col items-center gap-4 py-6">
|
||||||
<DefaultProviderSetupForm
|
<Button
|
||||||
configValues={configValues}
|
onClick={handleOAuthLogin}
|
||||||
setConfigValues={setConfigValues}
|
disabled={isOAuthLoading}
|
||||||
provider={provider}
|
className="flex items-center gap-2 px-6 py-3"
|
||||||
validationErrors={validationErrors}
|
size="lg"
|
||||||
/>
|
>
|
||||||
|
<LogIn size={20} />
|
||||||
|
{isOAuthLoading
|
||||||
|
? 'Signing in...'
|
||||||
|
: `Sign in with ${provider.metadata.display_name}`}
|
||||||
|
</Button>
|
||||||
|
<p className="text-sm text-textSubtle text-center">
|
||||||
|
A browser window will open for you to complete the login.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Contains information used to set up each provider */}
|
||||||
|
<DefaultProviderSetupForm
|
||||||
|
configValues={configValues}
|
||||||
|
setConfigValues={setConfigValues}
|
||||||
|
provider={provider}
|
||||||
|
validationErrors={validationErrors}
|
||||||
|
/>
|
||||||
|
|
||||||
{requiredParameters.length > 0 &&
|
{requiredParameters.length > 0 &&
|
||||||
provider.metadata.config_keys &&
|
provider.metadata.config_keys &&
|
||||||
provider.metadata.config_keys.length > 0 && <SecureStorageNotice />}
|
provider.metadata.config_keys.length > 0 && <SecureStorageNotice />}
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<ProviderSetupActions
|
{isOAuthProvider && !showDeleteConfirmation ? (
|
||||||
requiredParameters={requiredParameters}
|
<div className="flex gap-2">
|
||||||
onCancel={handleCancel}
|
<Button variant="outline" onClick={handleCancel}>
|
||||||
onSubmit={handleSubmitForm}
|
Cancel
|
||||||
onDelete={handleDelete}
|
</Button>
|
||||||
showDeleteConfirmation={showDeleteConfirmation}
|
{isConfigured && (
|
||||||
onConfirmDelete={handleConfirmDelete}
|
<Button variant="destructive" onClick={handleDelete}>
|
||||||
onCancelDelete={() => {
|
Remove Configuration
|
||||||
setIsActiveProvider(false);
|
</Button>
|
||||||
setShowDeleteConfirmation(false);
|
)}
|
||||||
}}
|
</div>
|
||||||
canDelete={isConfigured && !isActiveProvider}
|
) : (
|
||||||
providerName={provider.metadata.display_name}
|
<ProviderSetupActions
|
||||||
isActiveProvider={isActiveProvider}
|
requiredParameters={requiredParameters}
|
||||||
/>
|
onCancel={handleCancel}
|
||||||
|
onSubmit={handleSubmitForm}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
showDeleteConfirmation={showDeleteConfirmation}
|
||||||
|
onConfirmDelete={handleConfirmDelete}
|
||||||
|
onCancelDelete={() => {
|
||||||
|
setIsActiveProvider(false);
|
||||||
|
setShowDeleteConfirmation(false);
|
||||||
|
}}
|
||||||
|
canDelete={isConfigured && !isActiveProvider}
|
||||||
|
providerName={provider.metadata.display_name}
|
||||||
|
isActiveProvider={isActiveProvider}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export type AnalyticsEvent =
|
|||||||
| { name: 'onboarding_started'; properties: Record<string, never> }
|
| { name: 'onboarding_started'; properties: Record<string, never> }
|
||||||
| {
|
| {
|
||||||
name: 'onboarding_provider_selected';
|
name: 'onboarding_provider_selected';
|
||||||
properties: { method: 'api_key' | 'openrouter' | 'tetrate' | 'ollama' | 'other' };
|
properties: { method: 'api_key' | 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'ollama' | 'other' };
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
name: 'onboarding_completed';
|
name: 'onboarding_completed';
|
||||||
@@ -78,7 +78,7 @@ export type AnalyticsEvent =
|
|||||||
| { name: 'onboarding_abandoned'; properties: { step: string; duration_seconds?: number } }
|
| { name: 'onboarding_abandoned'; properties: { step: string; duration_seconds?: number } }
|
||||||
| {
|
| {
|
||||||
name: 'onboarding_setup_failed';
|
name: 'onboarding_setup_failed';
|
||||||
properties: { provider: 'openrouter' | 'tetrate'; error_message?: string };
|
properties: { provider: 'openrouter' | 'tetrate' | 'chatgpt_codex'; error_message?: string };
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
name: 'error_occurred';
|
name: 'error_occurred';
|
||||||
@@ -282,7 +282,7 @@ export function trackOnboardingStarted(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function trackOnboardingProviderSelected(
|
export function trackOnboardingProviderSelected(
|
||||||
method: 'api_key' | 'openrouter' | 'tetrate' | 'ollama' | 'other'
|
method: 'api_key' | 'openrouter' | 'tetrate' | 'chatgpt_codex' | 'ollama' | 'other'
|
||||||
): void {
|
): void {
|
||||||
trackEvent({
|
trackEvent({
|
||||||
name: 'onboarding_provider_selected',
|
name: 'onboarding_provider_selected',
|
||||||
@@ -315,7 +315,7 @@ export function trackOnboardingAbandoned(step: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function trackOnboardingSetupFailed(
|
export function trackOnboardingSetupFailed(
|
||||||
provider: 'openrouter' | 'tetrate',
|
provider: 'openrouter' | 'tetrate' | 'chatgpt_codex',
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
): void {
|
): void {
|
||||||
trackEvent({
|
trackEvent({
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { configureProviderOauth } from '../api';
|
||||||
|
|
||||||
|
export async function startChatGptCodexSetup(): Promise<{ success: boolean; message: string }> {
|
||||||
|
try {
|
||||||
|
await configureProviderOauth({
|
||||||
|
path: { name: 'chatgpt_codex' },
|
||||||
|
throwOnError: true,
|
||||||
|
});
|
||||||
|
return { success: true, message: 'ChatGPT Codex setup completed' };
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `Failed to start ChatGPT Codex setup: ${e}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user