M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
@@ -0,0 +1,634 @@
|
||||
use std::{borrow::Borrow, fmt, ops::Deref};
|
||||
|
||||
use agent_client_protocol as acp;
|
||||
use derive_more::From;
|
||||
use serde::{Deserialize, Serialize, ser::SerializeStruct};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use crate::common::AcpResult;
|
||||
|
||||
pub use self::{
|
||||
agent::{AcpAgentMessage, AcpAgentMessageBox, AcpAgentMessageGeneric},
|
||||
client::{AcpClientMessage, AcpClientMessageBox, AcpClientMessageGeneric},
|
||||
};
|
||||
|
||||
/// Marker trait representing one side of the ACP connection.
|
||||
pub trait AcpSide {
|
||||
/// What does this side receive.
|
||||
type InMessage: AcpMethod + fmt::Debug;
|
||||
/// What does this side send.
|
||||
type OutMessage: AcpMethod + fmt::Debug;
|
||||
/// Marker type for the other side.
|
||||
type OtherSide: AcpSide;
|
||||
/// Display name for this side.
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
/// Marker type representing the agent's view of the ACP connection (as one side of that connection).
|
||||
impl AcpSide for acp::AgentSide {
|
||||
type InMessage = AcpAgentMessage; // inbound messages = messages meant *for* the agent
|
||||
type OutMessage = AcpClientMessage; // outbound messages = messages meant *for* the client
|
||||
type OtherSide = acp::ClientSide;
|
||||
const NAME: &'static str = "agent";
|
||||
}
|
||||
|
||||
/// Marker type representing the agent's view of the ACP connection (as one side of that connection).
|
||||
impl AcpSide for acp::ClientSide {
|
||||
type InMessage = AcpClientMessage; // inbound messages = messages meant *for* the client
|
||||
type OutMessage = AcpAgentMessage; // outbound messages = messages meant *for* the agent
|
||||
type OtherSide = acp::AgentSide;
|
||||
const NAME: &'static str = "client";
|
||||
}
|
||||
|
||||
/// Extends each request/response type pair with the side marker type and schema method name.
|
||||
pub trait AcpMethod {
|
||||
fn method_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// Connect together ACP request and response types for each rpc method.
|
||||
pub trait AcpRequest: Clone + fmt::Debug + Serialize + AcpMethod {
|
||||
type Response: Clone + fmt::Debug + Serialize;
|
||||
}
|
||||
|
||||
/// Contains an ACP request and a oneshot channel where the response of a matching type can be sent.
|
||||
pub struct AcpArgsGeneric<T: AcpRequest, S: StorageMarker> {
|
||||
pub request: S::Type<T>,
|
||||
pub response_tx: oneshot::Sender<AcpResult<T::Response>>,
|
||||
}
|
||||
|
||||
impl<T: AcpRequest, S: StorageMarker> Deref for AcpArgsGeneric<T, S> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.request.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AcpRequest, S: StorageMarker> fmt::Debug for AcpArgsGeneric<T, S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.request.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AcpRequest, S: StorageMarker> AcpMethod for AcpArgsGeneric<T, S> {
|
||||
fn method_name(&self) -> &'static str {
|
||||
self.request.borrow().method_name()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type AcpArgs<T: AcpRequest> = AcpArgsGeneric<T, Unboxed>;
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type AcpArgsBox<T: AcpRequest> = AcpArgsGeneric<T, Boxed>;
|
||||
|
||||
impl<T: AcpRequest> AcpArgs<T> {
|
||||
pub fn boxed(self) -> AcpArgsBox<T> {
|
||||
AcpArgsBox {
|
||||
request: Box::new(self.request),
|
||||
response_tx: self.response_tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! acp_define_request_response {
|
||||
($request:ty, $response:ty, $method:expr $(,)?) => {
|
||||
impl AcpRequest for $request {
|
||||
type Response = $response;
|
||||
}
|
||||
|
||||
impl AcpMethod for $request {
|
||||
fn method_name(&self) -> &'static str {
|
||||
$method
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
acp_define_request_response!(acp::ExtRequest, acp::ExtResponse, "ext_method");
|
||||
acp_define_request_response!(acp::ExtNotification, (), "ext_notification");
|
||||
|
||||
pub trait StorageMarker: fmt::Debug + Clone + Copy {
|
||||
type Type<T>: Borrow<T> + From<T>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Unboxed;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Boxed;
|
||||
|
||||
impl StorageMarker for Unboxed {
|
||||
type Type<T> = T;
|
||||
}
|
||||
|
||||
impl StorageMarker for Boxed {
|
||||
type Type<T> = Box<T>;
|
||||
}
|
||||
|
||||
mod client {
|
||||
use futures::{FutureExt as _, future::LocalBoxFuture};
|
||||
|
||||
use super::*;
|
||||
|
||||
acp_define_request_response!(
|
||||
acp::RequestPermissionRequest,
|
||||
acp::RequestPermissionResponse,
|
||||
acp::CLIENT_METHOD_NAMES.session_request_permission,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::ReadTextFileRequest,
|
||||
acp::ReadTextFileResponse,
|
||||
acp::CLIENT_METHOD_NAMES.fs_read_text_file,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::WriteTextFileRequest,
|
||||
acp::WriteTextFileResponse,
|
||||
acp::CLIENT_METHOD_NAMES.fs_write_text_file,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::SessionNotification,
|
||||
(),
|
||||
acp::CLIENT_METHOD_NAMES.session_update,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::CreateTerminalRequest,
|
||||
acp::CreateTerminalResponse,
|
||||
acp::CLIENT_METHOD_NAMES.terminal_create,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::TerminalOutputRequest,
|
||||
acp::TerminalOutputResponse,
|
||||
acp::CLIENT_METHOD_NAMES.terminal_output,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::ReleaseTerminalRequest,
|
||||
acp::ReleaseTerminalResponse,
|
||||
acp::CLIENT_METHOD_NAMES.terminal_release,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::WaitForTerminalExitRequest,
|
||||
acp::WaitForTerminalExitResponse,
|
||||
acp::CLIENT_METHOD_NAMES.terminal_wait_for_exit,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::KillTerminalRequest,
|
||||
acp::KillTerminalResponse,
|
||||
acp::CLIENT_METHOD_NAMES.terminal_kill,
|
||||
);
|
||||
|
||||
/// ACP messages meant *for* the client.
|
||||
#[derive(Debug, From)]
|
||||
pub enum AcpClientMessageGeneric<S: StorageMarker> {
|
||||
RequestPermission(AcpArgsGeneric<acp::RequestPermissionRequest, S>),
|
||||
ReadTextFile(AcpArgsGeneric<acp::ReadTextFileRequest, S>),
|
||||
WriteTextFile(AcpArgsGeneric<acp::WriteTextFileRequest, S>),
|
||||
SessionNotification(AcpArgsGeneric<acp::SessionNotification, S>),
|
||||
CreateTerminal(AcpArgsGeneric<acp::CreateTerminalRequest, S>),
|
||||
TerminalOutput(AcpArgsGeneric<acp::TerminalOutputRequest, S>),
|
||||
ReleaseTerminal(AcpArgsGeneric<acp::ReleaseTerminalRequest, S>),
|
||||
WaitForTerminalExit(AcpArgsGeneric<acp::WaitForTerminalExitRequest, S>),
|
||||
KillTerminalCommand(AcpArgsGeneric<acp::KillTerminalRequest, S>),
|
||||
ExtMethod(AcpArgsGeneric<acp::ExtRequest, S>),
|
||||
ExtNotification(AcpArgsGeneric<acp::ExtNotification, S>),
|
||||
}
|
||||
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type AcpClientMessage = AcpClientMessageGeneric<Unboxed>;
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type AcpClientMessageBox = AcpClientMessageGeneric<Boxed>;
|
||||
|
||||
impl<S: StorageMarker> AcpMethod for AcpClientMessageGeneric<S> {
|
||||
fn method_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::RequestPermission(a) => a.method_name(),
|
||||
Self::ReadTextFile(a) => a.method_name(),
|
||||
Self::WriteTextFile(a) => a.method_name(),
|
||||
Self::SessionNotification(a) => a.method_name(),
|
||||
Self::CreateTerminal(a) => a.method_name(),
|
||||
Self::TerminalOutput(a) => a.method_name(),
|
||||
Self::ReleaseTerminal(a) => a.method_name(),
|
||||
Self::WaitForTerminalExit(a) => a.method_name(),
|
||||
Self::KillTerminalCommand(a) => a.method_name(),
|
||||
Self::ExtMethod(a) => a.method_name(),
|
||||
Self::ExtNotification(a) => a.method_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AcpClientMessage {
|
||||
pub fn boxed(self) -> AcpClientMessageBox {
|
||||
match self {
|
||||
Self::RequestPermission(args) => {
|
||||
AcpClientMessageBox::RequestPermission(args.boxed())
|
||||
}
|
||||
Self::ReadTextFile(args) => AcpClientMessageBox::ReadTextFile(args.boxed()),
|
||||
Self::WriteTextFile(args) => AcpClientMessageBox::WriteTextFile(args.boxed()),
|
||||
Self::SessionNotification(args) => {
|
||||
AcpClientMessageBox::SessionNotification(args.boxed())
|
||||
}
|
||||
Self::CreateTerminal(args) => AcpClientMessageBox::CreateTerminal(args.boxed()),
|
||||
Self::TerminalOutput(args) => AcpClientMessageBox::TerminalOutput(args.boxed()),
|
||||
Self::ReleaseTerminal(args) => AcpClientMessageBox::ReleaseTerminal(args.boxed()),
|
||||
Self::WaitForTerminalExit(args) => {
|
||||
AcpClientMessageBox::WaitForTerminalExit(args.boxed())
|
||||
}
|
||||
Self::KillTerminalCommand(args) => {
|
||||
AcpClientMessageBox::KillTerminalCommand(args.boxed())
|
||||
}
|
||||
Self::ExtMethod(args) => AcpClientMessageBox::ExtMethod(args.boxed()),
|
||||
Self::ExtNotification(args) => AcpClientMessageBox::ExtNotification(args.boxed()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn route_to_client(
|
||||
self,
|
||||
client: impl acp::Client + 'static, // note: acp::Client is auto-implemented for Rc/Arc
|
||||
spawn: impl Fn(LocalBoxFuture<'static, ()>) + 'static,
|
||||
) {
|
||||
match self {
|
||||
AcpClientMessage::RequestPermission(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.request_permission(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::ReadTextFile(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.read_text_file(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::WriteTextFile(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.write_text_file(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::SessionNotification(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.session_notification(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::CreateTerminal(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.create_terminal(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::TerminalOutput(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.terminal_output(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::ReleaseTerminal(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.release_terminal(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::WaitForTerminalExit(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.wait_for_terminal_exit(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::KillTerminalCommand(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.kill_terminal(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::ExtMethod(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.ext_method(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpClientMessage::ExtNotification(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(client.ext_notification(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod agent {
|
||||
use futures::{FutureExt as _, future::LocalBoxFuture};
|
||||
|
||||
use super::*;
|
||||
|
||||
acp_define_request_response!(
|
||||
acp::InitializeRequest,
|
||||
acp::InitializeResponse,
|
||||
acp::AGENT_METHOD_NAMES.initialize,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::AuthenticateRequest,
|
||||
acp::AuthenticateResponse,
|
||||
acp::AGENT_METHOD_NAMES.authenticate,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::NewSessionRequest,
|
||||
acp::NewSessionResponse,
|
||||
acp::AGENT_METHOD_NAMES.session_new,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::LoadSessionRequest,
|
||||
acp::LoadSessionResponse,
|
||||
acp::AGENT_METHOD_NAMES.session_load,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::SetSessionModeRequest,
|
||||
acp::SetSessionModeResponse,
|
||||
acp::AGENT_METHOD_NAMES.session_set_mode,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::PromptRequest,
|
||||
acp::PromptResponse,
|
||||
acp::AGENT_METHOD_NAMES.session_prompt,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::CancelNotification,
|
||||
(),
|
||||
acp::AGENT_METHOD_NAMES.session_cancel,
|
||||
);
|
||||
acp_define_request_response!(
|
||||
acp::SetSessionModelRequest,
|
||||
acp::SetSessionModelResponse,
|
||||
acp::AGENT_METHOD_NAMES.session_set_model,
|
||||
);
|
||||
|
||||
/// ACP messages meant *for* the agent.
|
||||
#[derive(Debug, From)]
|
||||
pub enum AcpAgentMessageGeneric<S: StorageMarker> {
|
||||
Initialize(AcpArgsGeneric<acp::InitializeRequest, S>),
|
||||
Authenticate(AcpArgsGeneric<acp::AuthenticateRequest, S>),
|
||||
NewSession(AcpArgsGeneric<acp::NewSessionRequest, S>),
|
||||
LoadSession(AcpArgsGeneric<acp::LoadSessionRequest, S>),
|
||||
SetSessionMode(AcpArgsGeneric<acp::SetSessionModeRequest, S>),
|
||||
Prompt(AcpArgsGeneric<acp::PromptRequest, S>),
|
||||
Cancel(AcpArgsGeneric<acp::CancelNotification, S>),
|
||||
ExtMethod(AcpArgsGeneric<acp::ExtRequest, S>),
|
||||
ExtNotification(AcpArgsGeneric<acp::ExtNotification, S>),
|
||||
SetSessionModel(AcpArgsGeneric<acp::SetSessionModelRequest, S>),
|
||||
}
|
||||
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type AcpAgentMessage = AcpAgentMessageGeneric<Unboxed>;
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type AcpAgentMessageBox = AcpAgentMessageGeneric<Boxed>;
|
||||
|
||||
impl<S: StorageMarker> AcpMethod for AcpAgentMessageGeneric<S> {
|
||||
fn method_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Initialize(a) => a.method_name(),
|
||||
Self::Authenticate(a) => a.method_name(),
|
||||
Self::NewSession(a) => a.method_name(),
|
||||
Self::LoadSession(a) => a.method_name(),
|
||||
Self::SetSessionMode(a) => a.method_name(),
|
||||
Self::Prompt(a) => a.method_name(),
|
||||
Self::Cancel(a) => a.method_name(),
|
||||
Self::ExtMethod(a) => a.method_name(),
|
||||
Self::ExtNotification(a) => a.method_name(),
|
||||
Self::SetSessionModel(a) => a.method_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: StorageMarker> Serialize for AcpAgentMessageGeneric<S> {
|
||||
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
|
||||
where
|
||||
Ser: serde::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("AcpAgentMessage", 2)?;
|
||||
state.serialize_field("method_name", self.method_name())?;
|
||||
match self {
|
||||
Self::Initialize(args) => {
|
||||
state.serialize_field("request", args.request.borrow())?
|
||||
}
|
||||
Self::Authenticate(args) => {
|
||||
state.serialize_field("request", args.request.borrow())?
|
||||
}
|
||||
Self::NewSession(args) => {
|
||||
state.serialize_field("request", args.request.borrow())?
|
||||
}
|
||||
Self::LoadSession(args) => {
|
||||
state.serialize_field("request", args.request.borrow())?
|
||||
}
|
||||
Self::SetSessionMode(args) => {
|
||||
state.serialize_field("request", args.request.borrow())?
|
||||
}
|
||||
Self::Prompt(args) => state.serialize_field("request", args.request.borrow())?,
|
||||
Self::Cancel(args) => state.serialize_field("request", args.request.borrow())?,
|
||||
Self::ExtMethod(args) => state.serialize_field("request", args.request.borrow())?,
|
||||
Self::ExtNotification(args) => {
|
||||
state.serialize_field("request", args.request.borrow())?
|
||||
}
|
||||
Self::SetSessionModel(args) => {
|
||||
state.serialize_field("request", args.request.borrow())?
|
||||
}
|
||||
}
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AcpAgentMessage {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct RawMessage {
|
||||
method_name: String,
|
||||
request: serde_json::Value,
|
||||
}
|
||||
|
||||
let raw = RawMessage::deserialize(deserializer)?;
|
||||
let method = raw.method_name.as_str();
|
||||
|
||||
macro_rules! parse {
|
||||
($variant:ident) => {{
|
||||
let (response_tx, _) = oneshot::channel();
|
||||
Ok(Self::$variant(AcpArgs {
|
||||
request: serde_json::from_value(raw.request)
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
response_tx,
|
||||
}))
|
||||
}};
|
||||
}
|
||||
|
||||
if method == acp::AGENT_METHOD_NAMES.initialize {
|
||||
parse!(Initialize)
|
||||
} else if method == acp::AGENT_METHOD_NAMES.authenticate {
|
||||
parse!(Authenticate)
|
||||
} else if method == acp::AGENT_METHOD_NAMES.session_new {
|
||||
parse!(NewSession)
|
||||
} else if method == acp::AGENT_METHOD_NAMES.session_load {
|
||||
parse!(LoadSession)
|
||||
} else if method == acp::AGENT_METHOD_NAMES.session_set_mode {
|
||||
parse!(SetSessionMode)
|
||||
} else if method == acp::AGENT_METHOD_NAMES.session_prompt {
|
||||
parse!(Prompt)
|
||||
} else if method == acp::AGENT_METHOD_NAMES.session_cancel {
|
||||
parse!(Cancel)
|
||||
} else if method == acp::AGENT_METHOD_NAMES.session_set_model {
|
||||
parse!(SetSessionModel)
|
||||
} else if method == "ext_method" {
|
||||
parse!(ExtMethod)
|
||||
} else if method == "ext_notification" {
|
||||
parse!(ExtNotification)
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
"Unknown method name: {method}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AcpAgentMessage {
|
||||
pub fn boxed(self) -> AcpAgentMessageBox {
|
||||
match self {
|
||||
Self::Initialize(args) => AcpAgentMessageBox::Initialize(args.boxed()),
|
||||
Self::Authenticate(args) => AcpAgentMessageBox::Authenticate(args.boxed()),
|
||||
Self::NewSession(args) => AcpAgentMessageBox::NewSession(args.boxed()),
|
||||
Self::LoadSession(args) => AcpAgentMessageBox::LoadSession(args.boxed()),
|
||||
Self::SetSessionMode(args) => AcpAgentMessageBox::SetSessionMode(args.boxed()),
|
||||
Self::Prompt(args) => AcpAgentMessageBox::Prompt(args.boxed()),
|
||||
Self::Cancel(args) => AcpAgentMessageBox::Cancel(args.boxed()),
|
||||
Self::ExtMethod(args) => AcpAgentMessageBox::ExtMethod(args.boxed()),
|
||||
Self::ExtNotification(args) => AcpAgentMessageBox::ExtNotification(args.boxed()),
|
||||
Self::SetSessionModel(args) => AcpAgentMessageBox::SetSessionModel(args.boxed()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn route_to_agent(
|
||||
self,
|
||||
agent: impl acp::Agent + 'static, // note: acp::Agent is auto-implemented for Rc/Arc
|
||||
spawn: impl Fn(LocalBoxFuture<'static, ()>) + 'static,
|
||||
) {
|
||||
match self {
|
||||
AcpAgentMessage::Initialize(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.initialize(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::Authenticate(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.authenticate(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::NewSession(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.new_session(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::LoadSession(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.load_session(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::SetSessionMode(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.set_session_mode(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::Prompt(args) => spawn(
|
||||
async move {
|
||||
_ = args.response_tx.send(agent.prompt(args.request).await).ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::Cancel(args) => spawn(
|
||||
async move {
|
||||
_ = args.response_tx.send(agent.cancel(args.request).await).ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::ExtMethod(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.ext_method(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::ExtNotification(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.ext_notification(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
AcpAgentMessage::SetSessionModel(args) => spawn(
|
||||
async move {
|
||||
_ = args
|
||||
.response_tx
|
||||
.send(agent.set_session_model(args.request).await)
|
||||
.ok();
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user