This commit is contained in:
159
api/proto/agent/v1/agent.proto
Normal file
159
api/proto/agent/v1/agent.proto
Normal file
@@ -0,0 +1,159 @@
|
||||
// Package agent is the Agent Orchestration bounded context: the agentic
|
||||
// run loop (prompt -> vLLM -> tool calls -> final reply), the chat-run
|
||||
// quota/concurrency guard, and the in-flight/just-finished run registry
|
||||
// that used to live in internal/handlers/chat.go's StreamManager.
|
||||
//
|
||||
// This is a saga coordinator, not a data owner: it holds no SQL table of
|
||||
// its own. It calls billing (usage quota), conversation (persisting
|
||||
// turns) and knowledge (the search_codebase tool) over gRPC, and
|
||||
// workspace (every filesystem/bash tool) over gRPC, exactly replacing
|
||||
// internal/llm/agent.go + internal/llm/tools.go's direct, in-process
|
||||
// calls into internal/db and the local filesystem.
|
||||
//
|
||||
// Rendering (Markdown, syntax highlighting, deliverable cards) stays out
|
||||
// of this service on purpose — see internal/render's doc comment in the
|
||||
// gateway service — so this API deals in plain text and structured tool
|
||||
// results, never HTML.
|
||||
syntax = "proto3";
|
||||
|
||||
package enkly.agent.v1;
|
||||
|
||||
option go_package = "enkly/gen/agent/v1;agentv1";
|
||||
|
||||
service AgentService {
|
||||
// StartTurn replaces the synchronous prefix of Handlers.Chat: the
|
||||
// per-user throttle (ChatLimiter), the billing quota check, loading
|
||||
// the conversation, persisting the user's prompt, auto-titling, and
|
||||
// registering a new run (StreamManager.create) before handing off to
|
||||
// a background goroutine. Returns immediately with a stream_id; the
|
||||
// reply itself arrives via StreamTurn.
|
||||
rpc StartTurn(StartTurnRequest) returns (StartTurnResponse);
|
||||
|
||||
// StreamTurn replaces Handlers.StreamChat: a server-streaming view of
|
||||
// one run's progress. resume_from_tool_index mirrors the monolith's
|
||||
// SSE Last-Event-ID handling, so a reconnecting client doesn't see
|
||||
// tool cards it already has.
|
||||
rpc StreamTurn(StreamTurnRequest) returns (stream TurnEvent);
|
||||
|
||||
// StopTurn replaces Handlers.StopStream.
|
||||
rpc StopTurn(StopTurnRequest) returns (StopTurnResponse);
|
||||
}
|
||||
|
||||
enum StartTurnOutcome {
|
||||
START_TURN_OUTCOME_UNSPECIFIED = 0;
|
||||
START_TURN_OUTCOME_OK = 1;
|
||||
START_TURN_OUTCOME_CONVERSATION_NOT_FOUND = 2;
|
||||
START_TURN_OUTCOME_CONVERSATION_BUSY = 3;
|
||||
START_TURN_OUTCOME_TOO_MANY_STREAMS = 4;
|
||||
START_TURN_OUTCOME_RATE_LIMITED = 5;
|
||||
START_TURN_OUTCOME_QUOTA_EXCEEDED = 6;
|
||||
START_TURN_OUTCOME_INTERNAL_ERROR = 7;
|
||||
}
|
||||
|
||||
// SystemEntry is a mid-conversation instruction/permission/budget
|
||||
// update — see agentrun.SystemEntry (services/agent/internal/
|
||||
// infrastructure/agentrun/effort.go) for what each kind means and how
|
||||
// it's folded into the system prompt.
|
||||
message SystemEntry {
|
||||
enum Kind {
|
||||
KIND_UNSPECIFIED = 0;
|
||||
KIND_INSTRUCTION = 1;
|
||||
KIND_PERMISSION = 2;
|
||||
KIND_BUDGET = 3;
|
||||
}
|
||||
Kind kind = 1;
|
||||
string content = 2;
|
||||
}
|
||||
|
||||
message StartTurnRequest {
|
||||
int64 user_id = 1;
|
||||
int64 conversation_id = 2;
|
||||
string prompt = 3;
|
||||
|
||||
// --- Fields below this line are the TARGET structured API surface
|
||||
// for effort control, fast mode and live system-entry updates — see
|
||||
// pkg/turnopts' package doc comment for why they aren't wired up yet
|
||||
// (this environment has no protoc/go toolchain to regenerate
|
||||
// gen/agent/v1 from this file) and what the working fallback is
|
||||
// today (leading /effort, /fast, /system directive lines parsed out
|
||||
// of `prompt` itself, by pkg/turnopts.Parse — see
|
||||
// application.Service.StartTurn). Once `make proto` is run against
|
||||
// this file, a follow-up change points StartTurn at these fields
|
||||
// directly, with turnopts kept on as the fallback path for any
|
||||
// client that still sends plain-text directives instead. Proto3
|
||||
// fields are purely additive and backward compatible to add, so
|
||||
// declaring them now costs nothing even before that regeneration —
|
||||
// every field below defaults to its zero value for any client built
|
||||
// against today's compiled stubs.
|
||||
|
||||
// effort selects one of pkg/effort's five levels ("low", "medium",
|
||||
// "high", "extra", "max") for this turn; empty means "use the
|
||||
// deployment's configured default" (see agentrun.Agent.Effort).
|
||||
string effort = 4;
|
||||
// fast_mode requests fast mode for this turn (see
|
||||
// application.Service.FastAgent) — available on every plan, unlike
|
||||
// dynamic_workflows below.
|
||||
bool fast_mode = 5;
|
||||
// system_entries are folded into the system prompt for this turn
|
||||
// onward (see agentrun.WithSystemEntries) — appended, never
|
||||
// replacing anything, so earlier turns' prefix stays stable.
|
||||
repeated SystemEntry system_entries = 6;
|
||||
}
|
||||
|
||||
message StartTurnResponse {
|
||||
StartTurnOutcome outcome = 1;
|
||||
string message = 2; // user-facing text for any non-OK outcome
|
||||
string stream_id = 3; // set when outcome == OK
|
||||
}
|
||||
|
||||
message StreamTurnRequest {
|
||||
int64 user_id = 1;
|
||||
string stream_id = 2;
|
||||
int32 resume_from_tool_index = 3;
|
||||
}
|
||||
|
||||
message ToolCall {
|
||||
string name = 1;
|
||||
string arguments_json = 2;
|
||||
string result = 3;
|
||||
bool is_error = 4;
|
||||
}
|
||||
|
||||
// ThinkingRound is one agent round's own reasoning trace, standalone
|
||||
// rather than folded into the running total thinking_delta carries —
|
||||
// the same per-step granularity Claude's interleaved thinking exposes
|
||||
// (one distinct thinking block per tool-use step) rather than one
|
||||
// cumulative blob for the whole turn. round is 0-indexed and matches
|
||||
// the round the agent service's internal run loop was on when this
|
||||
// trace was produced; a client can use it to render reasoning as
|
||||
// discrete, collapsible steps next to the tool call each one preceded,
|
||||
// instead of a single growing "thinking" pane.
|
||||
message ThinkingRound {
|
||||
int32 round = 1;
|
||||
string thinking = 2;
|
||||
}
|
||||
|
||||
message TurnEvent {
|
||||
oneof event {
|
||||
string thinking_delta = 1; // cumulative reasoning text so far, plain
|
||||
string token_delta = 2; // cumulative reply text so far, plain
|
||||
ToolCall tool_call = 3; // one completed tool call
|
||||
TurnDone done = 4;
|
||||
ThinkingRound thinking_round = 5; // one round's own reasoning trace, standalone
|
||||
}
|
||||
}
|
||||
|
||||
message TurnDone {
|
||||
string final_text = 1; // plain Markdown source, not yet rendered
|
||||
// every tool call made during the turn, successful or not — the
|
||||
// gateway uses this to build deliverable (download) cards exactly
|
||||
// like internal/handlers/deliverables.go.attachDeliverables.
|
||||
repeated ToolCall tool_calls = 2;
|
||||
}
|
||||
|
||||
message StopTurnRequest {
|
||||
int64 user_id = 1;
|
||||
string stream_id = 2;
|
||||
}
|
||||
|
||||
message StopTurnResponse {}
|
||||
153
api/proto/billing/v1/billing.proto
Normal file
153
api/proto/billing/v1/billing.proto
Normal file
@@ -0,0 +1,153 @@
|
||||
// Package billing is the Billing & Subscription bounded context: plans,
|
||||
// Stripe checkout/portal, webhook ingestion, and the per-plan daily
|
||||
// message quota. System of record for `subscriptions`, `stripe_events`
|
||||
// and `usage_daily` (see services/billing/migrations/0001_init.sql).
|
||||
//
|
||||
// Mirrors internal/handlers/billing.go + internal/billing/*.go +
|
||||
// internal/db/subscriptions.go.
|
||||
syntax = "proto3";
|
||||
|
||||
package enkly.billing.v1;
|
||||
|
||||
option go_package = "enkly/gen/billing/v1;billingv1";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service BillingService {
|
||||
// GetOrCreateSubscription replaces db.GetOrCreateSubscription: lazily
|
||||
// inserts the default free-plan row the first time a user's
|
||||
// subscription is looked up.
|
||||
rpc GetOrCreateSubscription(GetOrCreateSubscriptionRequest) returns (Subscription);
|
||||
|
||||
// GetPlanCatalog replaces billing.Catalog + billing.PlanPriceIDs for
|
||||
// rendering /subscription — returns every plan and whether its Stripe
|
||||
// price is currently configured (so an unconfigured plan renders as
|
||||
// "Unavailable", same as today).
|
||||
rpc GetPlanCatalog(GetPlanCatalogRequest) returns (GetPlanCatalogResponse);
|
||||
|
||||
// CreateCheckoutSession replaces Handlers.CreateCheckout.
|
||||
rpc CreateCheckoutSession(CreateCheckoutSessionRequest) returns (CreateCheckoutSessionResponse);
|
||||
|
||||
// SyncCheckoutSession replaces Handlers.syncCheckoutSession, the
|
||||
// fallback path SubscriptionSuccess uses when the webhook hasn't
|
||||
// landed yet.
|
||||
rpc SyncCheckoutSession(SyncCheckoutSessionRequest) returns (Subscription);
|
||||
|
||||
// CreateBillingPortalSession replaces Handlers.BillingPortal.
|
||||
rpc CreateBillingPortalSession(CreateBillingPortalSessionRequest) returns (CreateBillingPortalSessionResponse);
|
||||
|
||||
// IngestStripeWebhook replaces Handlers.StripeWebhook /
|
||||
// applyStripeEvent. The gateway still terminates the public
|
||||
// POST /webhooks/stripe route (so the URL Stripe is configured with
|
||||
// never changes) but forwards the raw body and signature header here
|
||||
// untouched — signature verification needs STRIPE_WEBHOOK_SECRET,
|
||||
// which only billing holds.
|
||||
rpc IngestStripeWebhook(IngestStripeWebhookRequest) returns (IngestStripeWebhookResponse);
|
||||
|
||||
// CheckAndIncrementUsage replaces the daily-cap check + db.
|
||||
// IncrementDailyUsage call in Handlers.Chat. Called by the agent
|
||||
// service once per chat turn, after the message is accepted and
|
||||
// before the agent run starts (same ordering as the monolith).
|
||||
rpc CheckAndIncrementUsage(CheckAndIncrementUsageRequest) returns (CheckAndIncrementUsageResponse);
|
||||
|
||||
// GetDailyUsage replaces db.DailyUsage — a read-only lookup (no
|
||||
// increment) used by the gateway's /subscription page to show
|
||||
// today's count without miscounting it as a sent message.
|
||||
rpc GetDailyUsage(GetDailyUsageRequest) returns (GetDailyUsageResponse);
|
||||
}
|
||||
|
||||
enum Plan {
|
||||
PLAN_UNSPECIFIED = 0;
|
||||
PLAN_FREE = 1;
|
||||
PLAN_BASIC = 2;
|
||||
PLAN_PRO = 3;
|
||||
PLAN_PREMIUM = 4;
|
||||
}
|
||||
|
||||
message PlanInfo {
|
||||
Plan plan = 1;
|
||||
string name = 2;
|
||||
int32 price_eur = 3;
|
||||
int32 daily_message_limit = 4; // 0 = unlimited
|
||||
bool available = 5; // Stripe price configured (always true for free)
|
||||
}
|
||||
|
||||
message Subscription {
|
||||
int64 user_id = 1;
|
||||
Plan plan = 2;
|
||||
string status = 3; // Stripe's subscription status, or "active" for free
|
||||
google.protobuf.Timestamp current_period_end = 4;
|
||||
bool cancel_at_period_end = 5;
|
||||
// has_billing_account reports whether a Stripe customer exists for
|
||||
// this user (so the gateway knows whether to show "Manage billing"),
|
||||
// without exposing the actual customer ID. The raw
|
||||
// stripe_customer_id / stripe_subscription_id / stripe_price_id are
|
||||
// deliberately NOT exposed here, mirroring models.Subscription's
|
||||
// json:"-" — they never need to leave billing.
|
||||
bool has_billing_account = 6;
|
||||
}
|
||||
|
||||
message GetOrCreateSubscriptionRequest {
|
||||
int64 user_id = 1;
|
||||
}
|
||||
|
||||
message GetPlanCatalogRequest {}
|
||||
|
||||
message GetPlanCatalogResponse {
|
||||
repeated PlanInfo plans = 1;
|
||||
string publishable_key = 2; // Stripe publishable key, for Checkout.js
|
||||
}
|
||||
|
||||
message CreateCheckoutSessionRequest {
|
||||
int64 user_id = 1;
|
||||
string email = 2;
|
||||
Plan plan = 3;
|
||||
string success_url = 4;
|
||||
string cancel_url = 5;
|
||||
}
|
||||
|
||||
message CreateCheckoutSessionResponse {
|
||||
string checkout_url = 1;
|
||||
}
|
||||
|
||||
message SyncCheckoutSessionRequest {
|
||||
int64 user_id = 1;
|
||||
string stripe_session_id = 2;
|
||||
}
|
||||
|
||||
message CreateBillingPortalSessionRequest {
|
||||
int64 user_id = 1;
|
||||
string return_url = 2;
|
||||
}
|
||||
|
||||
message CreateBillingPortalSessionResponse {
|
||||
string portal_url = 1;
|
||||
}
|
||||
|
||||
message IngestStripeWebhookRequest {
|
||||
bytes payload = 1;
|
||||
string signature_header = 2;
|
||||
}
|
||||
|
||||
message IngestStripeWebhookResponse {
|
||||
bool accepted = 1;
|
||||
}
|
||||
|
||||
message CheckAndIncrementUsageRequest {
|
||||
int64 user_id = 1;
|
||||
}
|
||||
|
||||
message CheckAndIncrementUsageResponse {
|
||||
bool allowed = 1;
|
||||
int32 used = 2;
|
||||
int32 limit = 3; // 0 = unlimited
|
||||
string plan_name = 4;
|
||||
}
|
||||
|
||||
message GetDailyUsageRequest {
|
||||
int64 user_id = 1;
|
||||
}
|
||||
|
||||
message GetDailyUsageResponse {
|
||||
int32 used = 1;
|
||||
}
|
||||
28
api/proto/common/v1/common.proto
Normal file
28
api/proto/common/v1/common.proto
Normal file
@@ -0,0 +1,28 @@
|
||||
// Package common holds message types shared across every enkly bounded
|
||||
// context's gRPC API, so each context's own .proto only needs to import
|
||||
// this one file instead of depending on each other directly.
|
||||
syntax = "proto3";
|
||||
|
||||
package enkly.common.v1;
|
||||
|
||||
option go_package = "enkly/gen/common/v1;commonv1";
|
||||
|
||||
// Money is a decimal amount in the smallest unit of currency (cents),
|
||||
// matching how internal/billing.Info prices plans today (EUR integer
|
||||
// amounts) without pulling in a floating point rounding bug.
|
||||
message Money {
|
||||
int64 minor_units = 1; // e.g. 500 = 5.00
|
||||
string currency_code = 2; // ISO 4217, e.g. "EUR"
|
||||
}
|
||||
|
||||
// PageRequest / PageResponse: not used by today's list endpoints (the
|
||||
// monolith never paginates conversations or messages), reserved so a
|
||||
// service can add pagination later without a breaking proto change.
|
||||
message PageRequest {
|
||||
int32 page_size = 1;
|
||||
string page_token = 2;
|
||||
}
|
||||
|
||||
message PageResponse {
|
||||
string next_page_token = 1;
|
||||
}
|
||||
136
api/proto/conversation/v1/conversation.proto
Normal file
136
api/proto/conversation/v1/conversation.proto
Normal file
@@ -0,0 +1,136 @@
|
||||
// Package conversation is the Conversation bounded context: chat threads
|
||||
// and their messages/tool-call audit log. System of record for
|
||||
// `conversations`, `messages` and `tool_calls` (see
|
||||
// services/conversation/migrations/0001_init.sql).
|
||||
//
|
||||
// Mirrors internal/db/conversations.go, called both by the gateway
|
||||
// (listing/renaming/deleting threads, loading history to render) and by
|
||||
// the agent service (appending messages as a run proceeds).
|
||||
syntax = "proto3";
|
||||
|
||||
package enkly.conversation.v1;
|
||||
|
||||
option go_package = "enkly/gen/conversation/v1;conversationv1";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service ConversationService {
|
||||
rpc CreateConversation(CreateConversationRequest) returns (Conversation);
|
||||
rpc GetConversation(GetConversationRequest) returns (Conversation);
|
||||
rpc ListConversations(ListConversationsRequest) returns (ListConversationsResponse);
|
||||
rpc MostRecentConversation(MostRecentConversationRequest) returns (Conversation);
|
||||
rpc RenameConversation(RenameConversationRequest) returns (Conversation);
|
||||
// AutoTitleConversation replaces db.AutoTitleConversation: sets the
|
||||
// title from the first prompt only if the conversation still has the
|
||||
// default title (returns retitled=true when it actually changed it).
|
||||
rpc AutoTitleConversation(AutoTitleConversationRequest) returns (AutoTitleConversationResponse);
|
||||
rpc DeleteConversation(DeleteConversationRequest) returns (DeleteConversationResponse);
|
||||
rpc CountMessages(CountMessagesRequest) returns (CountMessagesResponse);
|
||||
|
||||
rpc AddMessage(AddMessageRequest) returns (Message);
|
||||
rpc ListMessages(ListMessagesRequest) returns (ListMessagesResponse);
|
||||
rpc AddToolCall(AddToolCallRequest) returns (AddToolCallResponse);
|
||||
}
|
||||
|
||||
message Conversation {
|
||||
int64 id = 1;
|
||||
int64 user_id = 2;
|
||||
string title = 3;
|
||||
google.protobuf.Timestamp created_at = 4;
|
||||
google.protobuf.Timestamp last_message_at = 5;
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ROLE_UNSPECIFIED = 0;
|
||||
ROLE_USER = 1;
|
||||
ROLE_ASSISTANT = 2;
|
||||
ROLE_SYSTEM = 3;
|
||||
ROLE_TOOL = 4;
|
||||
}
|
||||
|
||||
message Message {
|
||||
int64 id = 1;
|
||||
int64 conversation_id = 2;
|
||||
Role role = 3;
|
||||
string content = 4;
|
||||
string tool_name = 5;
|
||||
google.protobuf.Timestamp created_at = 6;
|
||||
}
|
||||
|
||||
message CreateConversationRequest {
|
||||
int64 user_id = 1;
|
||||
string title = 2;
|
||||
}
|
||||
|
||||
message GetConversationRequest {
|
||||
int64 user_id = 1;
|
||||
int64 conversation_id = 2;
|
||||
}
|
||||
|
||||
message ListConversationsRequest {
|
||||
int64 user_id = 1;
|
||||
}
|
||||
|
||||
message ListConversationsResponse {
|
||||
repeated Conversation conversations = 1;
|
||||
}
|
||||
|
||||
message MostRecentConversationRequest {
|
||||
int64 user_id = 1;
|
||||
}
|
||||
|
||||
message RenameConversationRequest {
|
||||
int64 user_id = 1;
|
||||
int64 conversation_id = 2;
|
||||
string title = 3;
|
||||
}
|
||||
|
||||
message AutoTitleConversationRequest {
|
||||
int64 user_id = 1;
|
||||
int64 conversation_id = 2;
|
||||
string title = 3;
|
||||
}
|
||||
|
||||
message AutoTitleConversationResponse {
|
||||
bool retitled = 1;
|
||||
}
|
||||
|
||||
message DeleteConversationRequest {
|
||||
int64 user_id = 1;
|
||||
int64 conversation_id = 2;
|
||||
}
|
||||
|
||||
message DeleteConversationResponse {}
|
||||
|
||||
message CountMessagesRequest {
|
||||
int64 conversation_id = 1;
|
||||
}
|
||||
|
||||
message CountMessagesResponse {
|
||||
int32 count = 1;
|
||||
}
|
||||
|
||||
message AddMessageRequest {
|
||||
int64 conversation_id = 1;
|
||||
Role role = 2;
|
||||
string content = 3;
|
||||
string tool_name = 4;
|
||||
}
|
||||
|
||||
message ListMessagesRequest {
|
||||
int64 conversation_id = 1;
|
||||
}
|
||||
|
||||
message ListMessagesResponse {
|
||||
repeated Message messages = 1;
|
||||
}
|
||||
|
||||
message AddToolCallRequest {
|
||||
int64 message_id = 1;
|
||||
string tool_name = 2;
|
||||
string arguments_json = 3;
|
||||
string result = 4;
|
||||
bool is_error = 5;
|
||||
}
|
||||
|
||||
message AddToolCallResponse {}
|
||||
165
api/proto/iam/v1/iam.proto
Normal file
165
api/proto/iam/v1/iam.proto
Normal file
@@ -0,0 +1,165 @@
|
||||
// Package iam is the Identity & Access bounded context: accounts,
|
||||
// sessions, email verification and password reset. It is the system of
|
||||
// record for `users`, `sessions`, `email_verification_codes` and
|
||||
// `password_reset_tokens` (see services/iam/migrations/0001_init.sql) —
|
||||
// no other service may read or write those tables directly.
|
||||
//
|
||||
// Mirrors the monolith's internal/handlers/auth.go + internal/db/users.go
|
||||
// + internal/auth/*.go one-for-one; see each rpc's comment for the
|
||||
// handler function it replaces.
|
||||
syntax = "proto3";
|
||||
|
||||
package enkly.iam.v1;
|
||||
|
||||
option go_package = "enkly/gen/iam/v1;iamv1";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service IAMService {
|
||||
// SignUp replaces Handlers.Signup. Validates and normalizes the email,
|
||||
// hashes the password (PBKDF2, see internal/auth/password.go), creates
|
||||
// the account (or silently reuses an existing unverified one — same
|
||||
// anti-enumeration behavior as the monolith), issues a verification
|
||||
// code and publishes an iam.verification_code.issued event for the
|
||||
// notification service to deliver it. Never returns whether the email
|
||||
// already existed.
|
||||
rpc SignUp(SignUpRequest) returns (SignUpResponse);
|
||||
|
||||
// ResendVerification replaces Handlers.ResendVerification.
|
||||
rpc ResendVerification(ResendVerificationRequest) returns (ResendVerificationResponse);
|
||||
|
||||
// VerifyEmail replaces Handlers.VerifyEmail.
|
||||
rpc VerifyEmail(VerifyEmailRequest) returns (VerifyEmailResponse);
|
||||
|
||||
// Login replaces Handlers.Login: verifies credentials (constant-time
|
||||
// against a dummy hash for unknown emails) and, on success, mints a
|
||||
// session and returns its raw token for the gateway to set as a
|
||||
// cookie. Never persists the raw token — only its SHA-256 hash.
|
||||
rpc Login(LoginRequest) returns (LoginResponse);
|
||||
|
||||
// Logout replaces Handlers.Logout: deletes the session row for the
|
||||
// given raw session token.
|
||||
rpc Logout(LogoutRequest) returns (LogoutResponse);
|
||||
|
||||
// ValidateSession replaces the session half of Handlers.RequireAuth /
|
||||
// db.GetSessionUser. Called by the gateway on every authenticated
|
||||
// request.
|
||||
rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse);
|
||||
|
||||
// ForgotPassword replaces Handlers.ForgotPassword.
|
||||
rpc ForgotPassword(ForgotPasswordRequest) returns (ForgotPasswordResponse);
|
||||
|
||||
// CheckPasswordResetToken replaces the GET-time validity check in
|
||||
// Handlers.ResetPasswordPage (db.PasswordResetTokenValid).
|
||||
rpc CheckPasswordResetToken(CheckPasswordResetTokenRequest) returns (CheckPasswordResetTokenResponse);
|
||||
|
||||
// ResetPassword replaces Handlers.ResetPassword. Also revokes every
|
||||
// session for the account (db.UpdatePassword's cascade delete).
|
||||
rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse);
|
||||
|
||||
// GetUser is used by other services (e.g. billing, when it only has a
|
||||
// user_id) to resolve an email address. Never returns PasswordHash.
|
||||
rpc GetUser(GetUserRequest) returns (User);
|
||||
}
|
||||
|
||||
message User {
|
||||
int64 id = 1;
|
||||
string email = 2;
|
||||
bool email_verified = 3;
|
||||
google.protobuf.Timestamp created_at = 4;
|
||||
}
|
||||
|
||||
message SignUpRequest {
|
||||
string email = 1;
|
||||
string password = 2;
|
||||
string password_confirm = 3;
|
||||
string client_ip = 4; // for the shared signup/resend rate limiter
|
||||
}
|
||||
|
||||
message SignUpResponse {
|
||||
// Always true unless a hard infrastructure error occurred — the
|
||||
// monolith never reveals whether the address was already registered.
|
||||
bool check_inbox = 1;
|
||||
string email = 2;
|
||||
// mail_configured is echoed back so the gateway can show the same
|
||||
// "written to the server log instead" notice the monolith shows.
|
||||
bool mail_configured = 3;
|
||||
}
|
||||
|
||||
message ResendVerificationRequest {
|
||||
string email = 1;
|
||||
string client_ip = 2;
|
||||
}
|
||||
|
||||
message ResendVerificationResponse {
|
||||
bool mail_configured = 1;
|
||||
}
|
||||
|
||||
message VerifyEmailRequest {
|
||||
string email = 1;
|
||||
string code = 2;
|
||||
string client_ip = 3;
|
||||
}
|
||||
|
||||
message VerifyEmailResponse {
|
||||
bool verified = 1;
|
||||
}
|
||||
|
||||
message LoginRequest {
|
||||
string email = 1;
|
||||
string password = 2;
|
||||
string client_ip = 3;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
bool ok = 1;
|
||||
// Only one of these is meaningful depending on `ok` / `needs_verification`.
|
||||
bool needs_verification = 2;
|
||||
string session_token = 3; // raw token; gateway stores only this, in a cookie
|
||||
google.protobuf.Timestamp session_expires_at = 4;
|
||||
User user = 5;
|
||||
}
|
||||
|
||||
message LogoutRequest {
|
||||
string session_token = 1;
|
||||
}
|
||||
|
||||
message LogoutResponse {}
|
||||
|
||||
message ValidateSessionRequest {
|
||||
string session_token = 1;
|
||||
}
|
||||
|
||||
message ValidateSessionResponse {
|
||||
bool valid = 1;
|
||||
User user = 2;
|
||||
}
|
||||
|
||||
message ForgotPasswordRequest {
|
||||
string email = 1;
|
||||
string client_ip = 2;
|
||||
}
|
||||
|
||||
message ForgotPasswordResponse {}
|
||||
|
||||
message CheckPasswordResetTokenRequest {
|
||||
string token = 1;
|
||||
}
|
||||
|
||||
message CheckPasswordResetTokenResponse {
|
||||
bool valid = 1;
|
||||
}
|
||||
|
||||
message ResetPasswordRequest {
|
||||
string token = 1;
|
||||
string password = 2;
|
||||
string password_confirm = 3;
|
||||
}
|
||||
|
||||
message ResetPasswordResponse {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
message GetUserRequest {
|
||||
int64 user_id = 1;
|
||||
}
|
||||
75
api/proto/knowledge/v1/knowledge.proto
Normal file
75
api/proto/knowledge/v1/knowledge.proto
Normal file
@@ -0,0 +1,75 @@
|
||||
// Package knowledge is the Code Intelligence (RAG) bounded context:
|
||||
// chunking, embedding (via the knowledge service's vLLM-backed embed client) and
|
||||
// cosine-similarity search over workspace source files. System of
|
||||
// record for the pgvector-backed `documents` table (see
|
||||
// services/knowledge/migrations/0001_init.sql).
|
||||
//
|
||||
// Mirrors internal/rag/*.go. IndexDirectory (cmd/indexer) now walks the
|
||||
// workspace through the workspace service instead of the local
|
||||
// filesystem — see tools/indexer.
|
||||
syntax = "proto3";
|
||||
|
||||
package enkly.knowledge.v1;
|
||||
|
||||
option go_package = "enkly/gen/knowledge/v1;knowledgev1";
|
||||
|
||||
service KnowledgeService {
|
||||
// IndexFile replaces rag.Store.IndexFile: chunks the given file
|
||||
// content, embeds each new/changed chunk, and upserts it.
|
||||
rpc IndexFile(IndexFileRequest) returns (IndexFileResponse);
|
||||
|
||||
// PruneMissing replaces rag.Store.pruneMissing, called by the indexer
|
||||
// after a full directory walk to drop rows for files no longer
|
||||
// present.
|
||||
rpc PruneMissing(PruneMissingRequest) returns (PruneMissingResponse);
|
||||
|
||||
// Search replaces rag.Store.Search — the search_codebase agent tool.
|
||||
rpc Search(SearchRequest) returns (SearchResponse);
|
||||
|
||||
// ListIndexedFiles replaces db.ListIndexedFiles, used by the gateway's
|
||||
// sidebar "indexed files" panel (Handlers.renderConversation).
|
||||
rpc ListIndexedFiles(ListIndexedFilesRequest) returns (ListIndexedFilesResponse);
|
||||
}
|
||||
|
||||
message CodeChunk {
|
||||
int64 id = 1;
|
||||
string file_path = 2;
|
||||
int32 chunk_index = 3;
|
||||
string content = 4;
|
||||
double distance = 5; // cosine distance; lower = more relevant
|
||||
}
|
||||
|
||||
message IndexFileRequest {
|
||||
string file_path = 1;
|
||||
string content = 2;
|
||||
}
|
||||
|
||||
message IndexFileResponse {
|
||||
int32 chunks_indexed = 1;
|
||||
int32 chunks_skipped = 2; // unchanged content_hash
|
||||
}
|
||||
|
||||
message PruneMissingRequest {
|
||||
repeated string keep_file_paths = 1;
|
||||
}
|
||||
|
||||
message PruneMissingResponse {
|
||||
int32 rows_deleted = 1;
|
||||
}
|
||||
|
||||
message SearchRequest {
|
||||
string query = 1;
|
||||
int32 top_k = 2;
|
||||
}
|
||||
|
||||
message SearchResponse {
|
||||
repeated CodeChunk chunks = 1;
|
||||
}
|
||||
|
||||
message ListIndexedFilesRequest {
|
||||
int32 limit = 1; // 0 = default (200)
|
||||
}
|
||||
|
||||
message ListIndexedFilesResponse {
|
||||
repeated string file_paths = 1;
|
||||
}
|
||||
196
api/proto/workspace/v1/workspace.proto
Normal file
196
api/proto/workspace/v1/workspace.proto
Normal file
@@ -0,0 +1,196 @@
|
||||
// Package workspace is the Workspace (agent sandbox / files) bounded
|
||||
// context: the single shared ENKLY_WORKSPACE directory the coding agent
|
||||
// reads, writes and runs commands in, and that the "Files" panel in the
|
||||
// UI lets a human upload into / download from. Every path-jailing rule
|
||||
// from internal/llm/sandbox.go (ResolvePath, the bash allowlist, the
|
||||
// filesystem guards) is enforced here and nowhere else — no other
|
||||
// service is allowed to touch the workspace volume directly.
|
||||
//
|
||||
// Mirrors internal/llm/tools.go (filesystem + bash tools) and
|
||||
// internal/handlers/files.go / ui.go (upload, download, tree).
|
||||
syntax = "proto3";
|
||||
|
||||
package enkly.workspace.v1;
|
||||
|
||||
option go_package = "enkly/gen/workspace/v1;workspacev1";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service WorkspaceService {
|
||||
// --- agent filesystem/bash tools (internal/llm/tools.go, sandbox.go) ---
|
||||
rpc ReadFile(ReadFileRequest) returns (ReadFileResponse);
|
||||
rpc WriteFile(WriteFileRequest) returns (WriteFileResponse);
|
||||
rpc EditFile(EditFileRequest) returns (EditFileResponse);
|
||||
rpc MultiEditFile(MultiEditFileRequest) returns (MultiEditFileResponse);
|
||||
rpc ListDirectory(ListDirectoryRequest) returns (ListDirectoryResponse);
|
||||
rpc GlobSearch(GlobSearchRequest) returns (GlobSearchResponse);
|
||||
rpc Grep(GrepRequest) returns (GrepResponse);
|
||||
rpc RunBashCommand(RunBashCommandRequest) returns (RunBashCommandResponse);
|
||||
|
||||
// --- human-facing files panel (internal/handlers/files.go, ui.go) ---
|
||||
rpc UploadFile(UploadFileRequest) returns (UploadFileResponse);
|
||||
rpc DownloadFile(DownloadFileRequest) returns (DownloadFileResponse);
|
||||
rpc Tree(TreeRequest) returns (TreeResponse);
|
||||
rpc StatFile(StatFileRequest) returns (StatFileResponse); // backs deliverable cards
|
||||
}
|
||||
|
||||
message ReadFileRequest {
|
||||
string path = 1;
|
||||
int32 offset = 2; // 0-based line offset
|
||||
int32 limit = 3; // 0 = default (maxReadLines)
|
||||
}
|
||||
|
||||
message ReadFileResponse {
|
||||
string content = 1;
|
||||
bool truncated = 2;
|
||||
bool is_error = 3;
|
||||
string error_message = 4;
|
||||
}
|
||||
|
||||
message WriteFileRequest {
|
||||
string path = 1;
|
||||
string content = 2;
|
||||
}
|
||||
|
||||
message WriteFileResponse {
|
||||
int64 bytes_written = 1;
|
||||
bool is_error = 2;
|
||||
string error_message = 3;
|
||||
}
|
||||
|
||||
message EditFileRequest {
|
||||
string path = 1;
|
||||
string old_string = 2;
|
||||
string new_string = 3;
|
||||
bool replace_all = 4;
|
||||
}
|
||||
|
||||
message EditFileResponse {
|
||||
string diff_preview = 1;
|
||||
int32 occurrences = 2;
|
||||
bool is_error = 3;
|
||||
string error_message = 4;
|
||||
}
|
||||
|
||||
message TextEdit {
|
||||
string old_string = 1;
|
||||
string new_string = 2;
|
||||
bool replace_all = 3;
|
||||
}
|
||||
|
||||
message MultiEditFileRequest {
|
||||
string path = 1;
|
||||
repeated TextEdit edits = 2;
|
||||
}
|
||||
|
||||
message MultiEditFileResponse {
|
||||
string diff_preview = 1;
|
||||
bool is_error = 2;
|
||||
string error_message = 3;
|
||||
}
|
||||
|
||||
message ListDirectoryRequest {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message ListDirectoryResponse {
|
||||
string listing = 1; // pre-rendered, matching the monolith's tool output format
|
||||
bool is_error = 2;
|
||||
string error_message = 3;
|
||||
}
|
||||
|
||||
message GlobSearchRequest {
|
||||
string pattern = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message GlobSearchResponse {
|
||||
// Pre-rendered exactly like every other tool's text output (a
|
||||
// newline-separated path list, "no files matched that pattern", or a
|
||||
// truncation notice) — deliberately NOT a repeated string, since that
|
||||
// would require guessing which lines are paths vs. a status message,
|
||||
// a lossy transformation the original tool output never needed.
|
||||
string output = 1;
|
||||
bool is_error = 2;
|
||||
string error_message = 3;
|
||||
}
|
||||
|
||||
message GrepRequest {
|
||||
string pattern = 1;
|
||||
string path = 2;
|
||||
string glob = 3;
|
||||
bool case_insensitive = 4;
|
||||
}
|
||||
|
||||
message GrepResponse {
|
||||
string output = 1;
|
||||
bool is_error = 2;
|
||||
string error_message = 3;
|
||||
}
|
||||
|
||||
message RunBashCommandRequest {
|
||||
string command = 1;
|
||||
int32 timeout_seconds = 2; // 0 = service default
|
||||
}
|
||||
|
||||
message RunBashCommandResponse {
|
||||
string output = 1;
|
||||
int32 exit_code = 2;
|
||||
bool is_error = 3;
|
||||
string error_message = 4;
|
||||
}
|
||||
|
||||
message UploadFileRequest {
|
||||
string path = 1; // destination relative path
|
||||
bytes content = 2;
|
||||
}
|
||||
|
||||
message UploadFileResponse {
|
||||
int64 bytes_written = 1;
|
||||
}
|
||||
|
||||
message DownloadFileRequest {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message DownloadFileResponse {
|
||||
bytes content = 1;
|
||||
string filename = 2;
|
||||
}
|
||||
|
||||
message TreeRequest {
|
||||
string path = 1; // "" = workspace root
|
||||
// one_level: list only the immediate children of path (both files
|
||||
// and directories, directories included as entries in their own
|
||||
// right) instead of recursing — backs the gateway's lazy-expanding
|
||||
// file explorer panel (internal/handlers/ui.go's FileTree), where
|
||||
// each folder click fetches just that folder. false (the default)
|
||||
// recurses fully and returns only files, for the flat "Workspace
|
||||
// files" upload/download panel (internal/handlers/files.go).
|
||||
bool one_level = 2;
|
||||
}
|
||||
|
||||
message TreeNode {
|
||||
string name = 1;
|
||||
string rel_path = 2;
|
||||
bool is_dir = 3;
|
||||
int64 size = 4;
|
||||
google.protobuf.Timestamp modified_at = 5;
|
||||
// download_url is left empty by this service — the gateway owns URL
|
||||
// construction (it knows its own public route prefix), not workspace.
|
||||
}
|
||||
|
||||
message TreeResponse {
|
||||
repeated TreeNode nodes = 1;
|
||||
}
|
||||
|
||||
message StatFileRequest {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message StatFileResponse {
|
||||
bool exists = 1;
|
||||
bool is_dir = 2;
|
||||
int64 size = 3;
|
||||
google.protobuf.Timestamp modified_at = 4;
|
||||
}
|
||||
Reference in New Issue
Block a user