mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 22:54:24 +01:00
feat: deprecate jetbrains extension in favor of public one (#2589)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use goose_mcp::{
|
||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
||||
TutorialRouter,
|
||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, MemoryRouter, TutorialRouter,
|
||||
};
|
||||
use mcp_server::router::RouterService;
|
||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||
@@ -26,7 +25,6 @@ pub async fn run_server(name: &str) -> Result<()> {
|
||||
let router: Option<Box<dyn BoundedService>> = match name {
|
||||
"developer" => Some(Box::new(RouterService(DeveloperRouter::new()))),
|
||||
"computercontroller" => Some(Box::new(RouterService(ComputerControllerRouter::new()))),
|
||||
"jetbrains" => Some(Box::new(RouterService(JetBrainsRouter::new()))),
|
||||
"google_drive" | "googledrive" => {
|
||||
let router = GoogleDriveRouter::new().await;
|
||||
Some(Box::new(RouterService(router)))
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
mod proxy;
|
||||
|
||||
use anyhow::Result;
|
||||
use mcp_core::{
|
||||
handler::{PromptError, ResourceError, ToolError},
|
||||
prompt::Prompt,
|
||||
protocol::{JsonRpcMessage, ServerCapabilities},
|
||||
resource::Resource,
|
||||
role::Role,
|
||||
tool::Tool,
|
||||
};
|
||||
use mcp_server::router::CapabilitiesBuilder;
|
||||
use mcp_server::Router;
|
||||
use rmcp::model::Content;
|
||||
use serde_json::Value;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tracing::error;
|
||||
|
||||
use self::proxy::JetBrainsProxy;
|
||||
|
||||
pub struct JetBrainsRouter {
|
||||
tools: Arc<Mutex<Vec<Tool>>>,
|
||||
proxy: Arc<JetBrainsProxy>,
|
||||
instructions: String,
|
||||
}
|
||||
|
||||
impl Default for JetBrainsRouter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl JetBrainsRouter {
|
||||
pub fn new() -> Self {
|
||||
let tools = Arc::new(Mutex::new(Vec::new()));
|
||||
let proxy = Arc::new(JetBrainsProxy::new());
|
||||
let instructions = "JetBrains IDE integration".to_string();
|
||||
|
||||
// Initialize the proxy
|
||||
let proxy_clone = Arc::clone(&proxy);
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = proxy_clone.start().await {
|
||||
error!("Failed to start JetBrains proxy: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Start the background task to update tools
|
||||
let tools_clone = Arc::clone(&tools);
|
||||
let proxy_clone = Arc::clone(&proxy);
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
match proxy_clone.list_tools().await {
|
||||
Ok(new_tools) => {
|
||||
let mut tools = tools_clone.lock().await;
|
||||
*tools = new_tools;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to update tools: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
tools,
|
||||
proxy,
|
||||
instructions,
|
||||
}
|
||||
}
|
||||
|
||||
async fn call_proxy_tool(
|
||||
&self,
|
||||
tool_name: String,
|
||||
arguments: Value,
|
||||
) -> Result<Vec<Content>, ToolError> {
|
||||
let result = self
|
||||
.proxy
|
||||
.call_tool(&tool_name, arguments)
|
||||
.await
|
||||
.map_err(|e| ToolError::ExecutionError(e.to_string()))?;
|
||||
|
||||
// Create a success message for the assistant
|
||||
let mut contents = vec![
|
||||
Content::text(format!("Tool {} executed successfully", tool_name))
|
||||
.with_audience(vec![Role::Assistant]),
|
||||
];
|
||||
|
||||
// Add the tool's result contents
|
||||
contents.extend(result.content);
|
||||
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
async fn ensure_tools(&self) -> Result<(), ToolError> {
|
||||
let mut retry_count = 0;
|
||||
let max_retries = 50; // 5 second total wait time
|
||||
let retry_delay = Duration::from_millis(100);
|
||||
|
||||
while retry_count < max_retries {
|
||||
let tools = self.tools.lock().await;
|
||||
if !tools.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
drop(tools); // Release the lock before sleeping
|
||||
|
||||
sleep(retry_delay).await;
|
||||
retry_count += 1;
|
||||
}
|
||||
|
||||
Err(ToolError::ExecutionError("Failed to get tools list from IDE. Make sure the IDE is running and the plugin is installed.".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Router for JetBrainsRouter {
|
||||
fn name(&self) -> String {
|
||||
"jetbrains".to_string()
|
||||
}
|
||||
|
||||
fn instructions(&self) -> String {
|
||||
self.instructions.clone()
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> ServerCapabilities {
|
||||
CapabilitiesBuilder::new().with_tools(true).build()
|
||||
}
|
||||
|
||||
fn list_tools(&self) -> Vec<Tool> {
|
||||
// Use block_in_place to avoid blocking the runtime
|
||||
tokio::task::block_in_place(|| {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
rt.block_on(async {
|
||||
let tools = self.tools.lock().await;
|
||||
if tools.is_empty() {
|
||||
drop(tools);
|
||||
if let Err(e) = self.ensure_tools().await {
|
||||
error!("Failed to ensure tools: {}", e);
|
||||
vec![]
|
||||
} else {
|
||||
self.tools.lock().await.clone()
|
||||
}
|
||||
} else {
|
||||
tools.clone()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn call_tool(
|
||||
&self,
|
||||
tool_name: &str,
|
||||
arguments: Value,
|
||||
_notifier: mpsc::Sender<JsonRpcMessage>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Vec<Content>, ToolError>> + Send + 'static>> {
|
||||
let this = self.clone();
|
||||
let tool_name = tool_name.to_string();
|
||||
Box::pin(async move {
|
||||
this.ensure_tools().await?;
|
||||
this.call_proxy_tool(tool_name, arguments).await
|
||||
})
|
||||
}
|
||||
|
||||
fn list_resources(&self) -> Vec<Resource> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn read_resource(
|
||||
&self,
|
||||
_uri: &str,
|
||||
) -> Pin<Box<dyn Future<Output = Result<String, ResourceError>> + Send + 'static>> {
|
||||
Box::pin(async { Err(ResourceError::NotFound("Resource not found".into())) })
|
||||
}
|
||||
|
||||
fn list_prompts(&self) -> Vec<Prompt> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn get_prompt(
|
||||
&self,
|
||||
prompt_name: &str,
|
||||
) -> Pin<Box<dyn Future<Output = Result<String, PromptError>> + Send + 'static>> {
|
||||
let prompt_name = prompt_name.to_string();
|
||||
Box::pin(async move {
|
||||
Err(PromptError::NotFound(format!(
|
||||
"Prompt {} not found",
|
||||
prompt_name
|
||||
)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for JetBrainsRouter {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
tools: Arc::clone(&self.tools),
|
||||
proxy: Arc::clone(&self.proxy),
|
||||
instructions: self.instructions.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
static JETBRAINS_ROUTER: OnceCell<JetBrainsRouter> = OnceCell::const_new();
|
||||
|
||||
async fn get_router() -> &'static JetBrainsRouter {
|
||||
JETBRAINS_ROUTER
|
||||
.get_or_init(|| async { JetBrainsRouter::new() })
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_router_creation() {
|
||||
let router = get_router().await;
|
||||
assert_eq!(router.name(), "jetbrains");
|
||||
assert!(!router.instructions().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_capabilities() {
|
||||
let router = get_router().await;
|
||||
let capabilities = router.capabilities();
|
||||
assert!(capabilities.tools.is_some());
|
||||
}
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use mcp_core::Tool;
|
||||
use reqwest::Client;
|
||||
use rmcp::model::Content;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
const PORT_RANGE_START: u16 = 63342;
|
||||
const PORT_RANGE_END: u16 = 63352;
|
||||
const ENDPOINT_CHECK_INTERVAL: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct IDEResponseOk {
|
||||
status: String,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct IDEResponseErr {
|
||||
status: Option<String>,
|
||||
error: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CallToolResult {
|
||||
pub content: Vec<Content>,
|
||||
pub is_error: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JetBrainsProxy {
|
||||
cached_endpoint: Arc<RwLock<Option<String>>>,
|
||||
previous_response: Arc<RwLock<Option<String>>>,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl JetBrainsProxy {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cached_endpoint: Arc::new(RwLock::new(None)),
|
||||
previous_response: Arc::new(RwLock::new(None)),
|
||||
client: Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_list_tools(&self, endpoint: &str) -> Result<bool> {
|
||||
debug!("Sending test request to {}/mcp/list_tools", endpoint);
|
||||
|
||||
let response = match self
|
||||
.client
|
||||
.get(format!("{}/mcp/list_tools", endpoint))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
debug!("Got response with status: {}", resp.status());
|
||||
resp
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Error testing endpoint {}: {}", endpoint, e);
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
if !response.status().is_success() {
|
||||
debug!("Test request failed with status {}", response.status());
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let current_response = response.text().await?;
|
||||
debug!("Received response: {}", current_response);
|
||||
|
||||
// Try to parse as JSON array to validate format
|
||||
if serde_json::from_str::<Vec<Value>>(¤t_response).is_err() {
|
||||
debug!("Response is not a valid JSON array of tools");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut prev_response = self.previous_response.write().await;
|
||||
if let Some(prev) = prev_response.as_ref() {
|
||||
if prev != ¤t_response {
|
||||
debug!("Response changed since last check");
|
||||
self.send_tools_changed().await;
|
||||
}
|
||||
}
|
||||
*prev_response = Some(current_response);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn find_working_ide_endpoint(&self) -> Result<String> {
|
||||
debug!("Attempting to find working IDE endpoint...");
|
||||
|
||||
// Check IDE_PORT environment variable first
|
||||
if let Ok(port) = env::var("IDE_PORT") {
|
||||
debug!("Found IDE_PORT environment variable: {}", port);
|
||||
let test_endpoint = format!("http://127.0.0.1:{}/api", port);
|
||||
if self.test_list_tools(&test_endpoint).await? {
|
||||
debug!("IDE_PORT {} is working", port);
|
||||
return Ok(test_endpoint);
|
||||
}
|
||||
debug!("IDE_PORT {} is not responding correctly", port);
|
||||
return Err(anyhow!(
|
||||
"Specified IDE_PORT={} is not responding correctly",
|
||||
port
|
||||
));
|
||||
}
|
||||
|
||||
debug!(
|
||||
"No IDE_PORT environment variable, scanning port range {}-{}",
|
||||
PORT_RANGE_START, PORT_RANGE_END
|
||||
);
|
||||
|
||||
// Scan port range
|
||||
for port in PORT_RANGE_START..=PORT_RANGE_END {
|
||||
let candidate_endpoint = format!("http://127.0.0.1:{}/api", port);
|
||||
debug!("Testing port {}...", port);
|
||||
|
||||
if self.test_list_tools(&candidate_endpoint).await? {
|
||||
debug!("Found working IDE endpoint at {}", candidate_endpoint);
|
||||
return Ok(candidate_endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("No working IDE endpoint found in port range");
|
||||
Err(anyhow!(
|
||||
"No working IDE endpoint found in range {}-{}",
|
||||
PORT_RANGE_START,
|
||||
PORT_RANGE_END
|
||||
))
|
||||
}
|
||||
|
||||
async fn update_ide_endpoint(&self) {
|
||||
debug!("Updating IDE endpoint...");
|
||||
match self.find_working_ide_endpoint().await {
|
||||
Ok(endpoint) => {
|
||||
let mut cached = self.cached_endpoint.write().await;
|
||||
*cached = Some(endpoint.clone());
|
||||
debug!("Updated cached endpoint to: {}", endpoint);
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to update IDE endpoint: {}", e);
|
||||
error!("Failed to update IDE endpoint: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_tools(&self) -> Result<Vec<Tool>> {
|
||||
debug!("Listing tools...");
|
||||
let endpoint = {
|
||||
let cached = self.cached_endpoint.read().await;
|
||||
match cached.as_ref() {
|
||||
Some(ep) => {
|
||||
debug!("Using cached endpoint: {}", ep);
|
||||
ep.clone()
|
||||
}
|
||||
None => {
|
||||
debug!("No cached endpoint available");
|
||||
return Ok(vec![]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Sending list_tools request to {}/mcp/list_tools", endpoint);
|
||||
let response = match self
|
||||
.client
|
||||
.get(format!("{}/mcp/list_tools", endpoint))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
debug!("Got response with status: {}", resp.status());
|
||||
resp
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to send request: {}", e);
|
||||
return Err(anyhow!("Failed to send request: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
if !response.status().is_success() {
|
||||
debug!("Request failed with status: {}", response.status());
|
||||
return Err(anyhow!(
|
||||
"Failed to fetch tools with status {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
let response_text = response.text().await?;
|
||||
debug!("Got response text: {}", response_text);
|
||||
|
||||
let tools_response: Value = serde_json::from_str(&response_text).map_err(|e| {
|
||||
debug!("Failed to parse response as JSON: {}", e);
|
||||
anyhow!("Failed to parse response as JSON: {}", e)
|
||||
})?;
|
||||
|
||||
debug!("Parsed JSON response: {:?}", tools_response);
|
||||
|
||||
let tools: Vec<Tool> = tools_response
|
||||
.as_array()
|
||||
.ok_or_else(|| {
|
||||
debug!("Response is not a JSON array");
|
||||
anyhow!("Invalid tools response format: not an array")
|
||||
})?
|
||||
.iter()
|
||||
.filter_map(|t| {
|
||||
if let (Some(name), Some(description)) =
|
||||
(t["name"].as_str(), t["description"].as_str())
|
||||
{
|
||||
// Get just the first sentence of the description
|
||||
let first_sentence = description
|
||||
.split('.')
|
||||
.next()
|
||||
.unwrap_or(description)
|
||||
.trim()
|
||||
.to_string()
|
||||
+ ".";
|
||||
|
||||
// Handle input_schema as either a string or an object
|
||||
let input_schema = match &t["inputSchema"] {
|
||||
Value::String(s) => Value::String(s.clone()),
|
||||
Value::Object(o) => Value::Object(o.clone()),
|
||||
_ => {
|
||||
debug!(
|
||||
"Invalid inputSchema format for tool {}: {:?}",
|
||||
name, t["inputSchema"]
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(Tool {
|
||||
name: name.to_string(),
|
||||
description: first_sentence,
|
||||
input_schema,
|
||||
annotations: None,
|
||||
})
|
||||
} else {
|
||||
debug!("Skipping invalid tool entry: {:?}", t);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!("Collected {} tools", tools.len());
|
||||
Ok(tools)
|
||||
}
|
||||
|
||||
pub async fn call_tool(&self, name: &str, args: Value) -> Result<CallToolResult> {
|
||||
let endpoint = self
|
||||
.cached_endpoint
|
||||
.read()
|
||||
.await
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("No working IDE endpoint available"))?;
|
||||
|
||||
debug!(
|
||||
"ENDPOINT: {} | Tool name: {} | args: {}",
|
||||
endpoint, name, args
|
||||
);
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(format!("{}/mcp/{}", endpoint, name))
|
||||
.json(&args)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
debug!("Response failed with status: {}", response.status());
|
||||
return Err(anyhow!("Response failed: {}", response.status()));
|
||||
}
|
||||
|
||||
let ide_response: Value = response.json().await?;
|
||||
let (is_error, text) = match ide_response {
|
||||
Value::Object(map) => {
|
||||
let status = map.get("status").and_then(|v| v.as_str());
|
||||
let error = map.get("error").and_then(|v| v.as_str());
|
||||
|
||||
match (status, error) {
|
||||
(Some(s), None) => (false, s.to_string()),
|
||||
(None, Some(e)) => (true, e.to_string()),
|
||||
_ => {
|
||||
debug!("Invalid response format from IDE");
|
||||
return Err(anyhow!("Invalid response format from IDE"));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!("Unexpected response type from IDE");
|
||||
return Err(anyhow!("Unexpected response type from IDE"));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CallToolResult {
|
||||
content: vec![Content::text(text)],
|
||||
is_error,
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_tools_changed(&self) {
|
||||
debug!("Sending tools changed notification");
|
||||
// TODO: Implement notification mechanism when needed
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> Result<()> {
|
||||
debug!("Initializing JetBrains Proxy...");
|
||||
info!("Initializing JetBrains Proxy...");
|
||||
|
||||
// Initial endpoint check
|
||||
debug!("Performing initial endpoint check...");
|
||||
self.update_ide_endpoint().await;
|
||||
|
||||
// Schedule periodic endpoint checks
|
||||
let proxy = self.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(ENDPOINT_CHECK_INTERVAL).await;
|
||||
debug!("Performing periodic endpoint check...");
|
||||
proxy.update_ide_endpoint().await;
|
||||
}
|
||||
});
|
||||
|
||||
debug!("JetBrains Proxy running");
|
||||
info!("JetBrains Proxy running");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for JetBrainsProxy {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
cached_endpoint: Arc::clone(&self.cached_endpoint),
|
||||
previous_response: Arc::clone(&self.previous_response),
|
||||
client: Client::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,11 @@ pub static APP_STRATEGY: Lazy<AppStrategyArgs> = Lazy::new(|| AppStrategyArgs {
|
||||
pub mod computercontroller;
|
||||
mod developer;
|
||||
pub mod google_drive;
|
||||
mod jetbrains;
|
||||
mod memory;
|
||||
mod tutorial;
|
||||
|
||||
pub use computercontroller::ComputerControllerRouter;
|
||||
pub use developer::DeveloperRouter;
|
||||
pub use google_drive::GoogleDriveRouter;
|
||||
pub use jetbrains::JetBrainsRouter;
|
||||
pub use memory::MemoryRouter;
|
||||
pub use tutorial::TutorialRouter;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use goose_mcp::{
|
||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
||||
TutorialRouter,
|
||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, MemoryRouter, TutorialRouter,
|
||||
};
|
||||
use mcp_server::router::RouterService;
|
||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||
@@ -15,7 +14,6 @@ pub async fn run(name: &str) -> Result<()> {
|
||||
let router: Option<Box<dyn BoundedService>> = match name {
|
||||
"developer" => Some(Box::new(RouterService(DeveloperRouter::new()))),
|
||||
"computercontroller" => Some(Box::new(RouterService(ComputerControllerRouter::new()))),
|
||||
"jetbrains" => Some(Box::new(RouterService(JetBrainsRouter::new()))),
|
||||
"google_drive" | "googledrive" => {
|
||||
let router = GoogleDriveRouter::new().await;
|
||||
Some(Box::new(RouterService(router)))
|
||||
|
||||
@@ -1827,6 +1827,11 @@
|
||||
"format": "double",
|
||||
"description": "Cost per token for output (optional)",
|
||||
"nullable": true
|
||||
},
|
||||
"supports_cache_control": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this model supports cache control",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -26,15 +26,6 @@
|
||||
"env_keys": [],
|
||||
"timeout": 300
|
||||
},
|
||||
{
|
||||
"id": "jetbrains",
|
||||
"name": "Jetbrains",
|
||||
"description": "Integration with any Jetbrains IDE",
|
||||
"enabled": false,
|
||||
"type": "builtin",
|
||||
"env_keys": [],
|
||||
"timeout": 300
|
||||
},
|
||||
{
|
||||
"id": "tutorial",
|
||||
"name": "Tutorial",
|
||||
|
||||
@@ -32,17 +32,6 @@
|
||||
"timeout": 300,
|
||||
"bundled": true
|
||||
},
|
||||
{
|
||||
"id": "jetbrains",
|
||||
"display_name": "Jetbrains",
|
||||
"name": "jetbrains",
|
||||
"description": "Integration with any Jetbrains IDE",
|
||||
"enabled": false,
|
||||
"type": "builtin",
|
||||
"env_keys": [],
|
||||
"timeout": 300,
|
||||
"bundled": true
|
||||
},
|
||||
{
|
||||
"id": "tutorial",
|
||||
"name": "tutorial",
|
||||
|
||||
Reference in New Issue
Block a user