Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/language_models/src/language_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::provider::open_ai::OpenAiLanguageModelProvider;
use crate::provider::open_ai_compatible::OpenAiCompatibleLanguageModelProvider;
use crate::provider::open_router::OpenRouterLanguageModelProvider;
use crate::provider::opencode::OpenCodeLanguageModelProvider;
use crate::provider::opencode_go::OpenCodeGoLanguageModelProvider;
use crate::provider::vercel::VercelLanguageModelProvider;
use crate::provider::vercel_ai_gateway::VercelAiGatewayLanguageModelProvider;
use crate::provider::x_ai::XAiLanguageModelProvider;
Expand Down Expand Up @@ -345,6 +346,14 @@ fn register_language_model_providers(
);
registry.register_provider(
Arc::new(OpenCodeLanguageModelProvider::new(
client.http_client(),
credentials_provider.clone(),
cx,
)),
cx,
);
registry.register_provider(
Arc::new(OpenCodeGoLanguageModelProvider::new(
client.http_client(),
credentials_provider,
cx,
Expand Down
1 change: 1 addition & 0 deletions crates/language_models/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod open_ai;
pub mod open_ai_compatible;
pub mod open_router;
pub mod opencode;
pub mod opencode_go;

pub mod vercel;
pub mod vercel_ai_gateway;
Expand Down
73 changes: 68 additions & 5 deletions crates/language_models/src/provider/opencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub struct OpenCodeLanguageModelProvider {
pub struct State {
api_key_state: ApiKeyState,
credentials_provider: Arc<dyn CredentialsProvider>,
http_client: Arc<dyn HttpClient>,
available_model_ids: Vec<String>,
fetch_models_task: Option<Task<Result<(), LanguageModelCompletionError>>>,
}

impl State {
Expand All @@ -67,12 +70,59 @@ impl State {
fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {
let credentials_provider = self.credentials_provider.clone();
let api_url = OpenCodeLanguageModelProvider::api_url(cx);
self.api_key_state.load_if_needed(
let task = self.api_key_state.load_if_needed(
api_url,
|this| &mut this.api_key_state,
credentials_provider,
cx,
)
);

cx.spawn(async move |this, cx| {
let result = task.await;
this.update(cx, |this, cx| this.restart_fetch_models_task(cx))
.ok();
result
})
}

fn fetch_models(
&mut self,
cx: &mut Context<Self>,
) -> Task<Result<(), LanguageModelCompletionError>> {
let http_client = self.http_client.clone();
let api_url = OpenCodeLanguageModelProvider::api_url(cx);
let Some(api_key) = self.api_key_state.key(&api_url) else {
return Task::ready(Err(LanguageModelCompletionError::NoApiKey {
provider: PROVIDER_NAME,
}));
};
cx.spawn(async move |this, cx| {
let model_ids = opencode::list_model_ids(http_client.as_ref(), &api_url, &api_key)
.await
.map_err(|e| {
LanguageModelCompletionError::Other(anyhow::anyhow!(
"OpenCode Zen error: {:?}",
e
))
})?;

this.update(cx, |this, cx| {
this.available_model_ids = model_ids;
cx.notify();
})
.map_err(|e| LanguageModelCompletionError::Other(e))?;

Ok(())
})
}

fn restart_fetch_models_task(&mut self, cx: &mut Context<Self>) {
if self.is_authenticated() {
let task = self.fetch_models(cx);
self.fetch_models_task.replace(task);
} else {
self.available_model_ids = Vec::new();
}
}
}

Expand All @@ -98,10 +148,16 @@ impl OpenCodeLanguageModelProvider {
State {
api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),
credentials_provider,
http_client: http_client.clone(),
available_model_ids: Vec::new(),
fetch_models_task: None,
}
});

Self { http_client, state }
let this = Self { http_client, state };
this.state
.update(cx, |state, cx| state.restart_fetch_models_task(cx));
this
}

fn create_language_model(&self, model: opencode::Model) -> Arc<dyn LanguageModel> {
Expand Down Expand Up @@ -160,8 +216,15 @@ impl LanguageModelProvider for OpenCodeLanguageModelProvider {
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models = BTreeMap::default();

let available_model_ids = self.state.read(cx).available_model_ids.clone();

for model in opencode::Model::iter() {
if !matches!(model, opencode::Model::Custom { .. }) {
if matches!(model, opencode::Model::Custom { .. }) {
continue;
}
if available_model_ids.is_empty()
|| available_model_ids.contains(&model.id().to_string())
{
models.insert(model.id().to_string(), model);
}
}
Expand All @@ -172,7 +235,7 @@ impl LanguageModelProvider for OpenCodeLanguageModelProvider {
"openai_responses" => ApiProtocol::OpenAiResponses,
"openai_chat" => ApiProtocol::OpenAiChat,
"google" => ApiProtocol::Google,
_ => ApiProtocol::OpenAiChat, // default fallback
_ => ApiProtocol::OpenAiChat,
};
models.insert(
model.name.clone(),
Expand Down
Loading