From 8f10394b19415b605418c7cd94a76760b56faa62 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 11 Aug 2026 16:49:46 +0800 Subject: [PATCH] feat(agent): add public_agents (GET /v1/ai/agents) across all SDKs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit List all publicly available Agents on the platform — the Explore catalog. Unlike `agents`, this endpoint is not scoped to a Workspace: it returns every published, publicly-shared Agent. Reuses the existing Agent / AgentsResponse / GetAgentsOptions types (same page/limit/name query and response shape). Added to Rust (async + blocking), Python (sync + async + .pyi), Node.js (+ regenerated index.d.ts), Java (JNI + SdkNative + AgentContext.publicAgents), C (lb_agent_context_public_agents + regenerated header), and C++. --- CHANGELOG.md | 1 + c/csrc/include/longbridge.h | 11 ++++++ c/src/agent_context/context.rs | 30 ++++++++++++++++ cpp/include/agent_context.hpp | 5 +++ cpp/src/agent_context.cpp | 34 +++++++++++++++++++ .../main/java/com/longbridge/SdkNative.java | 2 ++ .../com/longbridge/agent/AgentContext.java | 15 ++++++++ java/src/agent_context.rs | 19 +++++++++++ nodejs/index.d.ts | 5 +++ nodejs/src/agent/context.rs | 27 +++++++++++++++ python/pysrc/longbridge/openapi.pyi | 34 +++++++++++++++++++ python/src/agent/context.rs | 26 ++++++++++++++ python/src/agent/context_async.rs | 31 +++++++++++++++++ rust/src/agent/context.rs | 25 ++++++++++++++ rust/src/blocking/agent.rs | 9 +++++ 15 files changed, 274 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa4d886c2..0df10ef96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **All languages:** add `AgentContext.public_agents` (`GET /v1/ai/agents`) — list all publicly available Agents on the platform (the Explore catalog). Unlike `agents`, it is not scoped to a Workspace and returns every published, publicly-shared Agent. Takes the same optional `page` / `limit` / `name` parameters and returns the existing `AgentsResponse` - **All languages:** add optional `parent_message_id` parameter to the AI Agent `conversation` and `conversation_streamed` methods — pass the `message_id` from a previous response to attach a follow-up message after the specified one, keeping the message stream in order. Only valid together with `chat_uid`; must not be set for a new conversation ### Fixed diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index 6bebad651..29c75221f 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -10408,6 +10408,17 @@ void lb_agent_context_agents(const struct lb_agent_context_t *ctx, lb_async_callback_t callback, void *userdata); +/** + * List all publicly available Agents on the platform (the Explore catalog). + * Not scoped to a Workspace. Returns `CAgentsResponse`. + * + * @param[in] opts Options for the request (can be null) + */ +void lb_agent_context_public_agents(const struct lb_agent_context_t *ctx, + const struct lb_get_agents_options_t *opts, + lb_async_callback_t callback, + void *userdata); + /** * Start a conversation with the specified Agent, blocking until the run * succeeds, is interrupted, or fails. Returns `CConversationResponse`. diff --git a/c/src/agent_context/context.rs b/c/src/agent_context/context.rs index 325091716..140c99e11 100644 --- a/c/src/agent_context/context.rs +++ b/c/src/agent_context/context.rs @@ -123,6 +123,36 @@ pub unsafe extern "C" fn lb_agent_context_agents( }); } +/// List all publicly available Agents on the platform (the Explore catalog). +/// Not scoped to a Workspace. Returns `CAgentsResponse`. +/// +/// @param[in] opts Options for the request (can be null) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_agent_context_public_agents( + ctx: *const CAgentContext, + opts: *const CGetAgentsOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let mut opts2 = GetAgentsOptions::new(); + if !opts.is_null() { + if !(*opts).page.is_null() { + opts2 = opts2.page(*(*opts).page); + } + if !(*opts).limit.is_null() { + opts2 = opts2.limit(*(*opts).limit); + } + if !(*opts).name.is_null() { + opts2 = opts2.name(cstr_to_rust((*opts).name)); + } + } + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new(ctx_inner.public_agents(opts2).await?); + Ok(resp) + }); +} + /// Start a conversation with the specified Agent, blocking until the run /// succeeds, is interrupted, or fails. Returns `CConversationResponse`. /// diff --git a/cpp/include/agent_context.hpp b/cpp/include/agent_context.hpp index 1bf810bd8..afbc571e4 100644 --- a/cpp/include/agent_context.hpp +++ b/cpp/include/agent_context.hpp @@ -45,6 +45,11 @@ class AgentContext const std::optional& opts, AsyncCallback callback) const; + /// List all publicly available Agents on the platform (the Explore catalog). + /// Not scoped to a Workspace. + void public_agents(const std::optional& opts, + AsyncCallback callback) const; + /// Start a conversation with the specified Agent, blocking until the run /// succeeds, is interrupted, or fails. /// diff --git a/cpp/src/agent_context.cpp b/cpp/src/agent_context.cpp index 6e205d37c..d59b91f6c 100644 --- a/cpp/src/agent_context.cpp +++ b/cpp/src/agent_context.cpp @@ -159,6 +159,40 @@ AgentContext::agents( new AsyncCallback(callback)); } +void +AgentContext::public_agents( + const std::optional& opts, + AsyncCallback callback) const +{ + lb_get_agents_options_t opts2 = { nullptr, nullptr, nullptr }; + if (opts) { + opts2.page = opts->page ? &opts->page.value() : nullptr; + opts2.limit = opts->limit ? &opts->limit.value() : nullptr; + opts2.name = opts->name ? opts->name->c_str() : nullptr; + } + + lb_agent_context_public_agents( + ctx_, + &opts2, + [](auto res) { + auto callback_ptr = + callback::get_async_callback( + res->userdata); + AgentContext ctx((const lb_agent_context_t*)res->ctx); + Status status(res->error); + + if (status) { + AgentsResponse resp = convert((const lb_agents_response_t*)res->data); + (*callback_ptr)(AsyncResult( + ctx, std::move(status), &resp)); + } else { + (*callback_ptr)(AsyncResult( + ctx, std::move(status), nullptr)); + } + }, + new AsyncCallback(callback)); +} + void AgentContext::conversation( const std::string& agent_id, diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 3c1efc817..ce502bdad 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -484,6 +484,8 @@ public static native void quoteContextUpdatePinned(long context, Object req, public static native void agentContextWorkspaces(long context, AsyncCallback callback); public static native void agentContextAgents(long context, String workspaceId, Object opts, AsyncCallback callback); + public static native void agentContextPublicAgents(long context, Object opts, + AsyncCallback callback); public static native void agentContextConversation(long context, String agentId, String query, String chatUid, String parentMessageId, AsyncCallback callback); public static native void agentContextContinueConversation(long context, String agentId, String chatUid, diff --git a/java/javasrc/src/main/java/com/longbridge/agent/AgentContext.java b/java/javasrc/src/main/java/com/longbridge/agent/AgentContext.java index 633cccbb8..f15d11479 100644 --- a/java/javasrc/src/main/java/com/longbridge/agent/AgentContext.java +++ b/java/javasrc/src/main/java/com/longbridge/agent/AgentContext.java @@ -77,6 +77,21 @@ public CompletableFuture agents(String workspaceId, GetAgentsOpt }); } + /** + * List all publicly available Agents on the platform (the Explore catalog). + * Not scoped to a Workspace. + * + * @param opts Options for this request, may be {@code null} + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture publicAgents(GetAgentsOptions opts) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.agentContextPublicAgents(raw(), opts, callback); + }); + } + /** * Start a conversation with the specified Agent, blocking until the run * succeeds, is interrupted, or fails. diff --git a/java/src/agent_context.rs b/java/src/agent_context.rs index df7be8866..5f156eb88 100644 --- a/java/src/agent_context.rs +++ b/java/src/agent_context.rs @@ -197,6 +197,25 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_agentContextAgents( }) } +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_agentContextPublicAgents( + mut env: JNIEnv, + _class: JClass, + context: i64, + opts: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let __owned_ctx = context.ctx.clone(); + let opts = read_get_agents_options(env, &opts)?; + async_util::execute(env, callback, async move { + Ok(__owned_ctx.public_agents(opts).await?) + })?; + Ok(()) + }) +} + #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_com_longbridge_SdkNative_agentContextConversation( mut env: JNIEnv, diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index df7cf209c..9874284f9 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -69,6 +69,11 @@ export declare class AgentContext { * ``` */ agents(workspaceId: string, page?: number | undefined | null, limit?: number | undefined | null, name?: string | undefined | null): Promise + /** + * List all publicly available Agents on the platform (the Explore + * catalog). Not scoped to a Workspace. + */ + publicAgents(page?: number | undefined | null, limit?: number | undefined | null, name?: string | undefined | null): Promise /** * Start a conversation with the specified Agent, blocking until the run * succeeds, is interrupted, or fails. diff --git a/nodejs/src/agent/context.rs b/nodejs/src/agent/context.rs index 0915f48db..a3968979f 100644 --- a/nodejs/src/agent/context.rs +++ b/nodejs/src/agent/context.rs @@ -79,6 +79,33 @@ impl AgentContext { .into()) } + /// List all publicly available Agents on the platform (the Explore + /// catalog). Not scoped to a Workspace. + #[napi] + pub async fn public_agents( + &self, + page: Option, + limit: Option, + name: Option, + ) -> Result { + let mut opts = longbridge::agent::GetAgentsOptions::new(); + if let Some(page) = page { + opts = opts.page(page); + } + if let Some(limit) = limit { + opts = opts.limit(limit); + } + if let Some(name) = name { + opts = opts.name(name); + } + Ok(self + .ctx + .public_agents(opts) + .await + .map_err(ErrorNewType)? + .into()) + } + /// Start a conversation with the specified Agent, blocking until the run /// succeeds, is interrupted, or fails. /// diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index b1e0b2ca7..8c9008159 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -14096,6 +14096,23 @@ class AgentContext: """ ... + def public_agents( + self, + page: int | None = None, + limit: int | None = None, + name: str | None = None, + ) -> AgentsResponse: + """ + List all publicly available Agents on the platform (the Explore + catalog). Not scoped to a Workspace. + + Args: + page: Page number, starts at 1 + limit: Page size + name: Fuzzy search by Agent name + """ + ... + def conversation( self, agent_id: str, @@ -14262,6 +14279,23 @@ class AsyncAgentContext: """ ... + def public_agents( + self, + page: int | None = None, + limit: int | None = None, + name: str | None = None, + ) -> Awaitable[AgentsResponse]: + """ + List all publicly available Agents on the platform (the Explore + catalog). Not scoped to a Workspace. Returns awaitable. + + Args: + page: Page number, starts at 1 + limit: Page size + name: Fuzzy search by Agent name + """ + ... + def conversation( self, agent_id: str, diff --git a/python/src/agent/context.rs b/python/src/agent/context.rs index cf0c8d745..13b7a2cb1 100644 --- a/python/src/agent/context.rs +++ b/python/src/agent/context.rs @@ -63,6 +63,32 @@ impl AgentContext { .into()) } + /// List all publicly available Agents on the platform. + #[pyo3(signature = (page = None, limit = None, name = None))] + fn public_agents( + &self, + py: Python<'_>, + page: Option, + limit: Option, + name: Option, + ) -> PyResult { + let mut opts = GetAgentsOptions::new(); + if let Some(page) = page { + opts = opts.page(page); + } + if let Some(limit) = limit { + opts = opts.limit(limit); + } + if let Some(name) = name { + opts = opts.name(name); + } + + Ok(py + .detach(|| self.0.public_agents(Some(opts))) + .map_err(ErrorNewType)? + .into()) + } + /// Start a conversation with the specified Agent, blocking until the run /// succeeds, is interrupted, or fails. #[pyo3(signature = (agent_id, query, chat_uid = None, parent_message_id = None))] diff --git a/python/src/agent/context_async.rs b/python/src/agent/context_async.rs index f65b5016a..3d0bf363a 100644 --- a/python/src/agent/context_async.rs +++ b/python/src/agent/context_async.rs @@ -83,6 +83,37 @@ impl AsyncAgentContext { .map(|b| b.unbind()) } + /// List all publicly available Agents on the platform. Returns awaitable. + #[pyo3(signature = (page = None, limit = None, name = None))] + fn public_agents( + &self, + py: Python<'_>, + page: Option, + limit: Option, + name: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + let mut opts = GetAgentsOptions::new(); + if let Some(page) = page { + opts = opts.page(page); + } + if let Some(limit) = limit { + opts = opts.limit(limit); + } + if let Some(name) = name { + opts = opts.name(name); + } + pyo3_async_runtimes::tokio::future_into_py(py, async move { + let resp: AgentsResponse = ctx + .public_agents(Some(opts)) + .await + .map_err(ErrorNewType)? + .into(); + Ok(resp) + }) + .map(|b| b.unbind()) + } + /// Start a conversation with the specified Agent, blocking until the run /// succeeds, is interrupted, or fails. Returns awaitable. #[pyo3(signature = (agent_id, query, chat_uid = None, parent_message_id = None))] diff --git a/rust/src/agent/context.rs b/rust/src/agent/context.rs index af84c84ae..d5683237d 100644 --- a/rust/src/agent/context.rs +++ b/rust/src/agent/context.rs @@ -196,6 +196,31 @@ impl AgentContext { .0) } + /// List all publicly available Agents on the platform — the same catalog + /// shown on the Explore page. + /// + /// Unlike [`agents`](Self::agents), this endpoint is not scoped to a + /// Workspace: it returns every Agent that is published and publicly shared. + /// The returned [`Agent::uid`] is used as the path parameter of + /// [`conversation`](Self::conversation). + /// + /// Path: `GET /v1/ai/agents` + pub async fn public_agents( + &self, + opts: impl Into>, + ) -> Result { + Ok(self + .0 + .http_cli + .request(Method::GET, "/v1/ai/agents") + .query_params(opts.into().unwrap_or_default()) + .response::>() + .send() + .with_subscriber(self.0.log_subscriber.clone()) + .await? + .0) + } + /// Start a conversation with the specified Agent, blocking until the run /// succeeds, is interrupted, or fails. /// diff --git a/rust/src/blocking/agent.rs b/rust/src/blocking/agent.rs index 6218a5c09..0f80321a9 100644 --- a/rust/src/blocking/agent.rs +++ b/rust/src/blocking/agent.rs @@ -45,6 +45,15 @@ impl AgentContextSync { .call(move |ctx| async move { ctx.agents(workspace_id, opts).await }) } + /// List all publicly available Agents on the platform. + pub fn public_agents( + &self, + opts: impl Into> + Send + 'static, + ) -> Result { + self.rt + .call(move |ctx| async move { ctx.public_agents(opts).await }) + } + /// Start a conversation with the specified Agent, blocking until the run /// succeeds, is interrupted, or fails. pub fn conversation(