From 16b193b8edf72498631a69310e61c03b92c27e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 17:03:22 -0700 Subject: [PATCH 01/56] agent threads: thread messages and grant; agent-db service moves to gRPC AgentThread messages for the durable conversation an agent has with a person, AgentThreadGrant under the agentThread claim, and the AT_ and AST_ id prefixes. The agent-db and thread services are internal gRPC in cloud-protocol, so the AgentDBService Twirp service is removed. A database lives in its project's data region: AgentDB.CreateRequest.region is reserved. --- .changeset/agent-threads.md | 8 + auth/accesstoken.go | 5 + auth/accesstoken_test.go | 24 + auth/grants.go | 71 + auth/grants_test.go | 57 + livekit/agent/livekit_agent_thread.pb.go | 1272 ++++++++++++++ livekit/livekit_agent_simulation.twirp.go | 4 +- livekit/livekit_agentdb.pb.go | 58 +- livekit/livekit_agentdb.twirp.go | 1736 -------------------- livekit/livekit_cloud_agent.twirp.go | 4 +- livekit/livekit_connector.twirp.go | 4 +- livekit/livekit_egress.twirp.go | 4 +- livekit/livekit_ingress.twirp.go | 4 +- livekit/livekit_phone_number.twirp.go | 4 +- livekit/livekit_room.twirp.go | 4 +- livekit/livekit_sip.twirp.go | 4 +- magefile.go | 3 +- packages/javascript/src/index.d.ts | 1 + packages/javascript/src/index.js | 1 + protobufs/agent/livekit_agent_thread.proto | 186 +++ protobufs/livekit_agentdb.proto | 21 +- utils/guid/id.go | 2 + 22 files changed, 1671 insertions(+), 1806 deletions(-) create mode 100644 .changeset/agent-threads.md create mode 100644 livekit/agent/livekit_agent_thread.pb.go delete mode 100644 livekit/livekit_agentdb.twirp.go create mode 100644 protobufs/agent/livekit_agent_thread.proto diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md new file mode 100644 index 000000000..f40055040 --- /dev/null +++ b/.changeset/agent-threads.md @@ -0,0 +1,8 @@ +--- +"github.com/livekit/protocol": minor +"@livekit/protocol": minor +--- + +Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim, and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. + +The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is reserved. diff --git a/auth/accesstoken.go b/auth/accesstoken.go index 5cc7de68d..ae8cba0aa 100644 --- a/auth/accesstoken.go +++ b/auth/accesstoken.go @@ -114,6 +114,11 @@ func (t *AccessToken) SetAgentEndpointGrant(grant *AgentEndpointGrant) *AccessTo return t } +func (t *AccessToken) SetAgentThreadGrant(grant *AgentThreadGrant) *AccessToken { + t.grant.AgentThread = grant + return t +} + func (t *AccessToken) SetMetadata(md string) *AccessToken { t.grant.Metadata = md return t diff --git a/auth/accesstoken_test.go b/auth/accesstoken_test.go index 4a2e7ec45..c712785db 100644 --- a/auth/accesstoken_test.go +++ b/auth/accesstoken_test.go @@ -245,6 +245,30 @@ func TestAgentEndpointGrantRoundTrip(t *testing.T) { require.Equal(t, grant, decoded.AgentEndpoint) } +func TestAgentThreadGrantRoundTrip(t *testing.T) { + t.Parallel() + + apiKey, secret := apiKeypair() + grant := &AgentThreadGrant{AgentName: "my-agent", Sub: "user-1", ThreadIDs: []string{"AT_1"}, Read: true, Send: true} + raw, err := NewAccessToken(apiKey, secret). + SetAgentThreadGrant(grant). + SetValidFor(time.Minute). + ToJWT() + require.NoError(t, err) + + // the claim keys are camelCase + claims := decodeClaims(t, raw) + require.Contains(t, claims, `"agentThread"`) + require.Contains(t, claims, `"agentName"`) + require.Contains(t, claims, `"threadIds"`) + + v, err := ParseAPIToken(raw) + require.NoError(t, err) + _, decoded, err := v.Verify(secret) + require.NoError(t, err) + require.Equal(t, grant, decoded.AgentThread) +} + func decodeClaims(t *testing.T, raw string) string { t.Helper() parts := strings.Split(raw, ".") diff --git a/auth/grants.go b/auth/grants.go index 4f56ad57a..2bc776094 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -169,6 +169,7 @@ type ClaimGrants struct { Inference *InferenceGrant `json:"inference,omitempty"` Observability *ObservabilityGrant `json:"observability,omitempty"` AgentEndpoint *AgentEndpointGrant `json:"agentEndpoint,omitempty"` + AgentThread *AgentThreadGrant `json:"agentThread,omitempty"` // Room configuration to use if this participant initiates the room RoomConfig *RoomConfiguration `json:"roomConfig,omitempty"` // Cloud-only, config preset to use @@ -216,6 +217,7 @@ func (c *ClaimGrants) Clone() *ClaimGrants { clone.Inference = c.Inference.Clone() clone.Observability = c.Observability.Clone() clone.AgentEndpoint = c.AgentEndpoint.Clone() + clone.AgentThread = c.AgentThread.Clone() clone.Attributes = maps.Clone(c.Attributes) clone.RoomConfig = c.RoomConfig.Clone() if len(c.KindDetails) > 0 { @@ -239,6 +241,7 @@ func (c *ClaimGrants) MarshalLogObject(e zapcore.ObjectEncoder) error { e.AddObject("Inference", c.Inference) e.AddObject("Observability", c.Observability) e.AddObject("AgentEndpoint", c.AgentEndpoint) + e.AddObject("AgentThread", c.AgentThread) e.AddObject("RoomConfig", logger.Proto((*livekit.RoomConfiguration)(c.RoomConfig))) e.AddString("RoomPreset", c.RoomPreset) return nil @@ -712,6 +715,74 @@ func (s *AgentEndpointGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // ------------------------------------------------------------------ +// AgentThreadGrant scopes a token to agent threads: the durable +// conversations of one agent, selected by subject or by id. It is minted by +// the application's backend the way a room token is, so the subject rides in +// the signature and a request body never names it. The JWT sub claim stays the +// participant identity; the thread subject is Sub. +type AgentThreadGrant struct { + // AgentName is required: threads, keys and subjects are scoped per agent. + AgentName string `json:"agentName"` + // Sub is the subject whose threads the holder may see and create. + Sub string `json:"sub,omitempty"` + // ThreadIDs restricts the grant to these threads instead. One of Sub and + // ThreadIDs is required; a grant with neither covers no thread. + ThreadIDs []string `json:"threadIds,omitempty"` + List bool `json:"list,omitempty"` + // Create lets a first message create a thread, with Sub as its subject. + Create bool `json:"create,omitempty"` + // Send grants message/send, message/stream and tasks/cancel. + Send bool `json:"send,omitempty"` + // Read grants get, chat_history and background_tasks. + Read bool `json:"read,omitempty"` + Delete bool `json:"delete,omitempty"` +} + +// Covers reports whether a thread is in the grant's scope: it was created by +// the grant's agent, and either its subject equals the grant's Sub or its id is +// listed in ThreadIDs. The action flags are checked by the caller. Matching +// is exact and case-sensitive; an empty Sub or thread id never matches. +func (s *AgentThreadGrant) Covers(agentName, sub, threadID string) bool { + if s == nil || s.AgentName == "" || s.AgentName != agentName { + return false + } + if s.Sub != "" && s.Sub == sub { + return true + } + return threadID != "" && slices.Contains(s.ThreadIDs, threadID) +} + +func (s *AgentThreadGrant) Clone() *AgentThreadGrant { + if s == nil { + return nil + } + + clone := *s + if len(s.ThreadIDs) > 0 { + clone.ThreadIDs = append([]string{}, s.ThreadIDs...) + } + + return &clone +} + +func (s *AgentThreadGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { + if s == nil { + return nil + } + + e.AddString("AgentName", s.AgentName) + e.AddString("Sub", s.Sub) + zap.Strings("ThreadIDs", s.ThreadIDs).AddTo(e) + e.AddBool("List", s.List) + e.AddBool("Create", s.Create) + e.AddBool("Send", s.Send) + e.AddBool("Read", s.Read) + e.AddBool("Delete", s.Delete) + return nil +} + +// ------------------------------------------------------------------ + func sourceToString(source livekit.TrackSource) string { return strings.ToLower(source.String()) } diff --git a/auth/grants_test.go b/auth/grants_test.go index 8755a3c2e..9a2c7a0ac 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -36,6 +36,7 @@ func TestGrants(t *testing.T) { require.Same(t, grants.Inference, clone.Inference) require.Same(t, grants.SIP, clone.SIP) require.Same(t, grants.AgentEndpoint, clone.AgentEndpoint) + require.Same(t, grants.AgentThread, clone.AgentThread) require.True(t, reflect.DeepEqual(grants, clone)) require.True(t, reflect.DeepEqual(grants.Video, clone.Video)) }) @@ -65,6 +66,9 @@ func TestGrants(t *testing.T) { // require AgentEndpoint require.Same(t, grants.AgentEndpoint, clone.AgentEndpoint) require.True(t, reflect.DeepEqual(grants.AgentEndpoint, clone.AgentEndpoint)) + // require AgentThread + require.Same(t, grants.AgentThread, clone.AgentThread) + require.True(t, reflect.DeepEqual(grants.AgentThread, clone.AgentThread)) }) t.Run("clone with video", func(t *testing.T) { @@ -524,3 +528,56 @@ func TestAgentEndpointGrantCloneIndependent(t *testing.T) { require.True(t, grants.AgentEndpoint.Call) require.Equal(t, "a", grants.AgentEndpoint.AgentName) } + +func TestAgentThreadGrantCovers(t *testing.T) { + t.Parallel() + + bySub := &AgentThreadGrant{AgentName: "a", Sub: "u1"} + byID := &AgentThreadGrant{AgentName: "a", ThreadIDs: []string{"AT_1", "AT_2"}} + both := &AgentThreadGrant{AgentName: "a", Sub: "u1", ThreadIDs: []string{"AT_1"}} + + cases := []struct { + name string + grant *AgentThreadGrant + agentName string + sub string + threadID string + want bool + }{ + {"nil grant", nil, "a", "u1", "AT_1", false}, + {"no agent", &AgentThreadGrant{Sub: "u1"}, "", "u1", "AT_1", false}, + {"neither sub nor ids", &AgentThreadGrant{AgentName: "a"}, "a", "", "AT_1", false}, + {"sub match", bySub, "a", "u1", "AT_9", true}, + {"sub mismatch", bySub, "a", "u2", "AT_9", false}, + {"sub empty on thread", bySub, "a", "", "AT_9", false}, + {"agent mismatch", bySub, "b", "u1", "AT_9", false}, + {"agent case differs", bySub, "A", "u1", "AT_9", false}, + {"id listed", byID, "a", "u2", "AT_2", true}, + {"id not listed", byID, "a", "u2", "AT_3", false}, + {"id empty", byID, "a", "u2", "", false}, + {"id listed, agent mismatch", byID, "b", "u2", "AT_2", false}, + {"both: sub", both, "a", "u1", "AT_9", true}, + {"both: id", both, "a", "u2", "AT_1", true}, + {"both: neither", both, "a", "u2", "AT_9", false}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + require.Equal(t, c.want, c.grant.Covers(c.agentName, c.sub, c.threadID)) + }) + } +} + +func TestAgentThreadGrantCloneIndependent(t *testing.T) { + t.Parallel() + + grants := &ClaimGrants{AgentThread: &AgentThreadGrant{AgentName: "a", Sub: "u1", ThreadIDs: []string{"AT_1"}, Send: true}} + clone := grants.Clone() + require.NotSame(t, grants.AgentThread, clone.AgentThread) + require.True(t, reflect.DeepEqual(grants.AgentThread, clone.AgentThread)) + + clone.AgentThread.Send = false + clone.AgentThread.ThreadIDs[0] = "AT_2" + clone.AgentThread.ThreadIDs = append(clone.AgentThread.ThreadIDs, "AT_3") + require.True(t, grants.AgentThread.Send) + require.Equal(t, []string{"AT_1"}, grants.AgentThread.ThreadIDs) +} diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go new file mode 100644 index 000000000..b8600201a --- /dev/null +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -0,0 +1,1272 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc v7.36.2 +// source: agent/livekit_agent_thread.proto + +package agent + +import ( + _ "github.com/livekit/protocol/livekit/logger" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// AgentThread messages: the durable conversation an agent has with a person +// across channels and over time. A thread is named by its "AT_" id and +// optionally by keys, opaque strings an application attaches ("phone:+1...") to +// find it again; a key resolves to at most one thread per agent. The project +// comes from the access token, and the thread's database lives in the project's +// data region. +// +// The service over these messages is internal gRPC (cloud-protocol +// AgentThreads); the public API is public-api-server's ThreadService. Errors are +// gRPC codes: +// +// AlreadyExists a key attached to another thread, without replace +// InvalidArgument a key, sub or attribute over its size limit +// FailedPrecondition the thread was redirected; the caller re-reads at +// Thread.redirect_to +// PermissionDenied a thread, subject or agent outside the token's +// AgentThreadGrant +// NotFound otherwise +// +// Calls: +// +// Create a keyed create is get-or-create: if the key already names a +// thread of this agent, that thread is returned with created = false +// and nothing else in the request is applied. Without a key a new +// thread is always created. +// Get by id, or by (agent_name, key). The lookup by key is reserved to the +// project credential; an AgentThreadGrant may only read by id. +// List the threads of one subject under one agent, newest first. +// Update attributes and idle TTL. Not available to an AgentThreadGrant: +// attributes are written by the agent's tools or the application's +// backend. +// Touch marks the thread active now and pushes expires_at out by its idle +// TTL. +// AddKey a key already attached to another thread of the same agent is +// AlreadyExists unless replace is set, which moves it. +// Delete deletes the thread, its keys and its database. +// EnsureStateDatabase creates the thread's database if it has none and +// records it on the thread. Idempotent. +// Redirect sets redirect_to on a fresh thread after its items were copied into +// the found one. Reserved to the project credential. Refused with +// FailedPrecondition when the row already redirects elsewhere. +// +// Nested so that names like Thread and CreateRequest need not be unique across +// the livekit.agent package. Times throughout are int64 unix seconds. +type AgentThread struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread) Reset() { + *x = AgentThread{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread) ProtoMessage() {} + +func (x *AgentThread) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread.ProtoReflect.Descriptor instead. +func (*AgentThread) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0} +} + +// One key under one agent. Keys are scoped per agent: the same string under +// two agents names two threads. +type AgentThread_AgentKey struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_AgentKey) Reset() { + *x = AgentThread_AgentKey{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_AgentKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_AgentKey) ProtoMessage() {} + +func (x *AgentThread_AgentKey) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_AgentKey.ProtoReflect.Descriptor instead. +func (*AgentThread_AgentKey) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *AgentThread_AgentKey) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *AgentThread_AgentKey) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type AgentThread_CreateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + // Optional. When set the call is get-or-create on (agent_name, key). At + // most 256 bytes. + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // Optional. The subject the thread belongs to, the value List selects on + // and an AgentThreadGrant is scoped by. At most 256 bytes. + Sub string `protobuf:"bytes,3,opt,name=sub,proto3" json:"sub,omitempty"` + Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // 0 takes the server default. The thread expires this long after its last + // activity. + IdleTtlSeconds int64 `protobuf:"varint,5,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3" json:"idle_ttl_seconds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_CreateRequest) Reset() { + *x = AgentThread_CreateRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_CreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_CreateRequest) ProtoMessage() {} + +func (x *AgentThread_CreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_CreateRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_CreateRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *AgentThread_CreateRequest) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *AgentThread_CreateRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *AgentThread_CreateRequest) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *AgentThread_CreateRequest) GetAttributes() map[string]string { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *AgentThread_CreateRequest) GetIdleTtlSeconds() int64 { + if x != nil { + return x.IdleTtlSeconds + } + return 0 +} + +type AgentThread_CreateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Thread *AgentThread_Thread `protobuf:"bytes,1,opt,name=thread,proto3" json:"thread,omitempty"` + // False when a key in the request already named a thread, which is the one + // returned. + Created bool `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_CreateResponse) Reset() { + *x = AgentThread_CreateResponse{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_CreateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_CreateResponse) ProtoMessage() {} + +func (x *AgentThread_CreateResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_CreateResponse.ProtoReflect.Descriptor instead. +func (*AgentThread_CreateResponse) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *AgentThread_CreateResponse) GetThread() *AgentThread_Thread { + if x != nil { + return x.Thread + } + return nil +} + +func (x *AgentThread_CreateResponse) GetCreated() bool { + if x != nil { + return x.Created + } + return false +} + +type AgentThread_GetRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Lookup: + // + // *AgentThread_GetRequest_ThreadId + // *AgentThread_GetRequest_ByKey + Lookup isAgentThread_GetRequest_Lookup `protobuf_oneof:"lookup"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_GetRequest) Reset() { + *x = AgentThread_GetRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_GetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_GetRequest) ProtoMessage() {} + +func (x *AgentThread_GetRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_GetRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_GetRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 3} +} + +func (x *AgentThread_GetRequest) GetLookup() isAgentThread_GetRequest_Lookup { + if x != nil { + return x.Lookup + } + return nil +} + +func (x *AgentThread_GetRequest) GetThreadId() string { + if x != nil { + if x, ok := x.Lookup.(*AgentThread_GetRequest_ThreadId); ok { + return x.ThreadId + } + } + return "" +} + +func (x *AgentThread_GetRequest) GetByKey() *AgentThread_AgentKey { + if x != nil { + if x, ok := x.Lookup.(*AgentThread_GetRequest_ByKey); ok { + return x.ByKey + } + } + return nil +} + +type isAgentThread_GetRequest_Lookup interface { + isAgentThread_GetRequest_Lookup() +} + +type AgentThread_GetRequest_ThreadId struct { + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3,oneof"` +} + +type AgentThread_GetRequest_ByKey struct { + ByKey *AgentThread_AgentKey `protobuf:"bytes,2,opt,name=by_key,json=byKey,proto3,oneof"` +} + +func (*AgentThread_GetRequest_ThreadId) isAgentThread_GetRequest_Lookup() {} + +func (*AgentThread_GetRequest_ByKey) isAgentThread_GetRequest_Lookup() {} + +type AgentThread_ListRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + Sub string `protobuf:"bytes,2,opt,name=sub,proto3" json:"sub,omitempty"` + // Optional. The cursor from the previous page's ListResponse.before; opaque + // to the client. Empty starts from the newest thread. + Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` + // Server-clamped. + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_ListRequest) Reset() { + *x = AgentThread_ListRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_ListRequest) ProtoMessage() {} + +func (x *AgentThread_ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_ListRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_ListRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 4} +} + +func (x *AgentThread_ListRequest) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *AgentThread_ListRequest) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *AgentThread_ListRequest) GetBefore() string { + if x != nil { + return x.Before + } + return "" +} + +func (x *AgentThread_ListRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +type AgentThread_ListResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Threads []*AgentThread_Thread `protobuf:"bytes,1,rep,name=threads,proto3" json:"threads,omitempty"` + // The cursor for the next page; empty at the end. + Before string `protobuf:"bytes,2,opt,name=before,proto3" json:"before,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_ListResponse) Reset() { + *x = AgentThread_ListResponse{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_ListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_ListResponse) ProtoMessage() {} + +func (x *AgentThread_ListResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_ListResponse.ProtoReflect.Descriptor instead. +func (*AgentThread_ListResponse) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 5} +} + +func (x *AgentThread_ListResponse) GetThreads() []*AgentThread_Thread { + if x != nil { + return x.Threads + } + return nil +} + +func (x *AgentThread_ListResponse) GetBefore() string { + if x != nil { + return x.Before + } + return "" +} + +type AgentThread_UpdateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + // Merged into the thread's attributes: a key with an empty value deletes the + // entry, keys not present are left alone. + Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + IdleTtlSeconds *int64 `protobuf:"varint,3,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3,oneof" json:"idle_ttl_seconds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_UpdateRequest) Reset() { + *x = AgentThread_UpdateRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_UpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_UpdateRequest) ProtoMessage() {} + +func (x *AgentThread_UpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_UpdateRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_UpdateRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} +} + +func (x *AgentThread_UpdateRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentThread_UpdateRequest) GetAttributes() map[string]string { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *AgentThread_UpdateRequest) GetIdleTtlSeconds() int64 { + if x != nil && x.IdleTtlSeconds != nil { + return *x.IdleTtlSeconds + } + return 0 +} + +type AgentThread_TouchRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_TouchRequest) Reset() { + *x = AgentThread_TouchRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_TouchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_TouchRequest) ProtoMessage() {} + +func (x *AgentThread_TouchRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_TouchRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_TouchRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} +} + +func (x *AgentThread_TouchRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +type AgentThread_TouchResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExpiresAt int64 `protobuf:"varint,1,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_TouchResponse) Reset() { + *x = AgentThread_TouchResponse{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_TouchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_TouchResponse) ProtoMessage() {} + +func (x *AgentThread_TouchResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_TouchResponse.ProtoReflect.Descriptor instead. +func (*AgentThread_TouchResponse) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} +} + +func (x *AgentThread_TouchResponse) GetExpiresAt() int64 { + if x != nil { + return x.ExpiresAt + } + return 0 +} + +type AgentThread_AddKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + // At most 256 bytes. + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // Optional. The key is dropped this long after it is attached; 0 keeps it + // for the life of the thread. + TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` + // Move the key here when another thread of the same agent holds it. + Replace bool `protobuf:"varint,4,opt,name=replace,proto3" json:"replace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_AddKeyRequest) Reset() { + *x = AgentThread_AddKeyRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_AddKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_AddKeyRequest) ProtoMessage() {} + +func (x *AgentThread_AddKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_AddKeyRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_AddKeyRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} +} + +func (x *AgentThread_AddKeyRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentThread_AddKeyRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *AgentThread_AddKeyRequest) GetTtlSeconds() int64 { + if x != nil { + return x.TtlSeconds + } + return 0 +} + +func (x *AgentThread_AddKeyRequest) GetReplace() bool { + if x != nil { + return x.Replace + } + return false +} + +type AgentThread_RemoveKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_RemoveKeyRequest) Reset() { + *x = AgentThread_RemoveKeyRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_RemoveKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_RemoveKeyRequest) ProtoMessage() {} + +func (x *AgentThread_RemoveKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_RemoveKeyRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_RemoveKeyRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 10} +} + +func (x *AgentThread_RemoveKeyRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentThread_RemoveKeyRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type AgentThread_DeleteRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_DeleteRequest) Reset() { + *x = AgentThread_DeleteRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_DeleteRequest) ProtoMessage() {} + +func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_DeleteRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_DeleteRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 11} +} + +func (x *AgentThread_DeleteRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +type AgentThread_DeleteResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_DeleteResponse) Reset() { + *x = AgentThread_DeleteResponse{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_DeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_DeleteResponse) ProtoMessage() {} + +func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_DeleteResponse.ProtoReflect.Descriptor instead. +func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 12} +} + +type AgentThread_EnsureStateDatabaseRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_EnsureStateDatabaseRequest) Reset() { + *x = AgentThread_EnsureStateDatabaseRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_EnsureStateDatabaseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_EnsureStateDatabaseRequest) ProtoMessage() {} + +func (x *AgentThread_EnsureStateDatabaseRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_EnsureStateDatabaseRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_EnsureStateDatabaseRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 13} +} + +func (x *AgentThread_EnsureStateDatabaseRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +type AgentThread_RedirectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + // The thread the items were copied into. + RedirectTo string `protobuf:"bytes,2,opt,name=redirect_to,json=redirectTo,proto3" json:"redirect_to,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_RedirectRequest) Reset() { + *x = AgentThread_RedirectRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_RedirectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_RedirectRequest) ProtoMessage() {} + +func (x *AgentThread_RedirectRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_RedirectRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_RedirectRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 14} +} + +func (x *AgentThread_RedirectRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentThread_RedirectRequest) GetRedirectTo() string { + if x != nil { + return x.RedirectTo + } + return "" +} + +// One thread, as every read reports it. Keys are never returned, by any call +// and with any token: only their count is. +type AgentThread_Thread struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + // The agent that created the thread. + AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + KeyCount uint32 `protobuf:"varint,3,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` + Sub string `protobuf:"bytes,4,opt,name=sub,proto3" json:"sub,omitempty"` + Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CreatedAt int64 `protobuf:"varint,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + LastActive int64 `protobuf:"varint,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` + ExpiresAt int64 `protobuf:"varint,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + IdleTtlSeconds int64 `protobuf:"varint,9,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3" json:"idle_ttl_seconds,omitempty"` + // The thread's database on agent-db, "DB_..."; empty until the first write + // or EnsureStateDatabase. + DatabaseId string `protobuf:"bytes,10,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` + // Set when this thread was merged into another. Threads report the redirect + // and never follow it: calls on a redirected thread fail with + // FailedPrecondition and the caller re-reads at this id. + RedirectTo string `protobuf:"bytes,11,opt,name=redirect_to,json=redirectTo,proto3" json:"redirect_to,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_Thread) Reset() { + *x = AgentThread_Thread{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_Thread) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_Thread) ProtoMessage() {} + +func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_Thread.ProtoReflect.Descriptor instead. +func (*AgentThread_Thread) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 15} +} + +func (x *AgentThread_Thread) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentThread_Thread) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *AgentThread_Thread) GetKeyCount() uint32 { + if x != nil { + return x.KeyCount + } + return 0 +} + +func (x *AgentThread_Thread) GetSub() string { + if x != nil { + return x.Sub + } + return "" +} + +func (x *AgentThread_Thread) GetAttributes() map[string]string { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *AgentThread_Thread) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *AgentThread_Thread) GetLastActive() int64 { + if x != nil { + return x.LastActive + } + return 0 +} + +func (x *AgentThread_Thread) GetExpiresAt() int64 { + if x != nil { + return x.ExpiresAt + } + return 0 +} + +func (x *AgentThread_Thread) GetIdleTtlSeconds() int64 { + if x != nil { + return x.IdleTtlSeconds + } + return 0 +} + +func (x *AgentThread_Thread) GetDatabaseId() string { + if x != nil { + return x.DatabaseId + } + return "" +} + +func (x *AgentThread_Thread) GetRedirectTo() string { + if x != nil { + return x.RedirectTo + } + return "" +} + +var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor + +const file_agent_livekit_agent_thread_proto_rawDesc = "" + + "\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x14logger/options.proto\"\x8f\x11\n" + + "\vAgentThread\x1a@\n" + + "\bAgentKey\x12\x1d\n" + + "\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x15\n" + + "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x1a\xa4\x02\n" + + "\rCreateRequest\x12\x1d\n" + + "\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x15\n" + + "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x12\x15\n" + + "\x03sub\x18\x03 \x01(\tB\x03\xc0P\x01R\x03sub\x12]\n" + + "\n" + + "attributes\x18\x04 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + + "attributes\x12(\n" + + "\x10idle_ttl_seconds\x18\x05 \x01(\x03R\x0eidleTtlSeconds\x1a=\n" + + "\x0fAttributesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ae\n" + + "\x0eCreateResponse\x129\n" + + "\x06thread\x18\x01 \x01(\v2!.livekit.agent.AgentThread.ThreadR\x06thread\x12\x18\n" + + "\acreated\x18\x02 \x01(\bR\acreated\x1a\x80\x01\n" + + "\n" + + "GetRequest\x12*\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDH\x00R\bthreadId\x12<\n" + + "\x06by_key\x18\x02 \x01(\v2#.livekit.agent.AgentThread.AgentKeyH\x00R\x05byKeyB\b\n" + + "\x06lookup\x1aq\n" + + "\vListRequest\x12\x1d\n" + + "\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x15\n" + + "\x03sub\x18\x02 \x01(\tB\x03\xc0P\x01R\x03sub\x12\x16\n" + + "\x06before\x18\x03 \x01(\tR\x06before\x12\x14\n" + + "\x05limit\x18\x04 \x01(\x05R\x05limit\x1ac\n" + + "\fListResponse\x12;\n" + + "\athreads\x18\x01 \x03(\v2!.livekit.agent.AgentThread.ThreadR\athreads\x12\x16\n" + + "\x06before\x18\x02 \x01(\tR\x06before\x1a\x9b\x02\n" + + "\rUpdateRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12]\n" + + "\n" + + "attributes\x18\x02 \x03(\v28.livekit.agent.AgentThread.UpdateRequest.AttributesEntryB\x03\xc0P\x01R\n" + + "attributes\x12-\n" + + "\x10idle_ttl_seconds\x18\x03 \x01(\x03H\x00R\x0eidleTtlSeconds\x88\x01\x01\x1a=\n" + + "\x0fAttributesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x13\n" + + "\x11_idle_ttl_seconds\x1a8\n" + + "\fTouchRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a.\n" + + "\rTouchResponse\x12\x1d\n" + + "\n" + + "expires_at\x18\x01 \x01(\x03R\texpiresAt\x1a\x8b\x01\n" + + "\rAddKeyRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x15\n" + + "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x12\x1f\n" + + "\vttl_seconds\x18\x03 \x01(\x03R\n" + + "ttlSeconds\x12\x18\n" + + "\areplace\x18\x04 \x01(\bR\areplace\x1aS\n" + + "\x10RemoveKeyRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x15\n" + + "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x1a9\n" + + "\rDeleteRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + + "\x0eDeleteResponse\x1aF\n" + + "\x1aEnsureStateDatabaseRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1ak\n" + + "\x0fRedirectRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12.\n" + + "\vredirect_to\x18\x02 \x01(\tB\r\xbaP\n" + + "redirectToR\n" + + "redirectTo\x1a\xe7\x03\n" + + "\x06Thread\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x1d\n" + + "\n" + + "agent_name\x18\x02 \x01(\tR\tagentName\x12\x1b\n" + + "\tkey_count\x18\x03 \x01(\rR\bkeyCount\x12\x15\n" + + "\x03sub\x18\x04 \x01(\tB\x03\xc0P\x01R\x03sub\x12V\n" + + "\n" + + "attributes\x18\x05 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + + "attributes\x12\x1d\n" + + "\n" + + "created_at\x18\x06 \x01(\x03R\tcreatedAt\x12\x1f\n" + + "\vlast_active\x18\a \x01(\x03R\n" + + "lastActive\x12\x1d\n" + + "\n" + + "expires_at\x18\b \x01(\x03R\texpiresAt\x12(\n" + + "\x10idle_ttl_seconds\x18\t \x01(\x03R\x0eidleTtlSeconds\x12\x1f\n" + + "\vdatabase_id\x18\n" + + " \x01(\tR\n" + + "databaseId\x12\x1f\n" + + "\vredirect_to\x18\v \x01(\tR\n" + + "redirectTo\x1a=\n" + + "\x0fAttributesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" + +var ( + file_agent_livekit_agent_thread_proto_rawDescOnce sync.Once + file_agent_livekit_agent_thread_proto_rawDescData []byte +) + +func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { + file_agent_livekit_agent_thread_proto_rawDescOnce.Do(func() { + file_agent_livekit_agent_thread_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_livekit_agent_thread_proto_rawDesc), len(file_agent_livekit_agent_thread_proto_rawDesc))) + }) + return file_agent_livekit_agent_thread_proto_rawDescData +} + +var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_agent_livekit_agent_thread_proto_goTypes = []any{ + (*AgentThread)(nil), // 0: livekit.agent.AgentThread + (*AgentThread_AgentKey)(nil), // 1: livekit.agent.AgentThread.AgentKey + (*AgentThread_CreateRequest)(nil), // 2: livekit.agent.AgentThread.CreateRequest + (*AgentThread_CreateResponse)(nil), // 3: livekit.agent.AgentThread.CreateResponse + (*AgentThread_GetRequest)(nil), // 4: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 5: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 6: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 7: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_TouchRequest)(nil), // 8: livekit.agent.AgentThread.TouchRequest + (*AgentThread_TouchResponse)(nil), // 9: livekit.agent.AgentThread.TouchResponse + (*AgentThread_AddKeyRequest)(nil), // 10: livekit.agent.AgentThread.AddKeyRequest + (*AgentThread_RemoveKeyRequest)(nil), // 11: livekit.agent.AgentThread.RemoveKeyRequest + (*AgentThread_DeleteRequest)(nil), // 12: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 13: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_EnsureStateDatabaseRequest)(nil), // 14: livekit.agent.AgentThread.EnsureStateDatabaseRequest + (*AgentThread_RedirectRequest)(nil), // 15: livekit.agent.AgentThread.RedirectRequest + (*AgentThread_Thread)(nil), // 16: livekit.agent.AgentThread.Thread + nil, // 17: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 18: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 19: livekit.agent.AgentThread.Thread.AttributesEntry +} +var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ + 17, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry + 16, // 1: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread + 1, // 2: livekit.agent.AgentThread.GetRequest.by_key:type_name -> livekit.agent.AgentThread.AgentKey + 16, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread + 18, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry + 19, // 5: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_agent_livekit_agent_thread_proto_init() } +func file_agent_livekit_agent_thread_proto_init() { + if File_agent_livekit_agent_thread_proto != nil { + return + } + file_agent_livekit_agent_thread_proto_msgTypes[4].OneofWrappers = []any{ + (*AgentThread_GetRequest_ThreadId)(nil), + (*AgentThread_GetRequest_ByKey)(nil), + } + file_agent_livekit_agent_thread_proto_msgTypes[7].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_livekit_agent_thread_proto_rawDesc), len(file_agent_livekit_agent_thread_proto_rawDesc)), + NumEnums: 0, + NumMessages: 20, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_agent_livekit_agent_thread_proto_goTypes, + DependencyIndexes: file_agent_livekit_agent_thread_proto_depIdxs, + MessageInfos: file_agent_livekit_agent_thread_proto_msgTypes, + }.Build() + File_agent_livekit_agent_thread_proto = out.File + file_agent_livekit_agent_thread_proto_goTypes = nil + file_agent_livekit_agent_thread_proto_depIdxs = nil +} diff --git a/livekit/livekit_agent_simulation.twirp.go b/livekit/livekit_agent_simulation.twirp.go index eea1bf242..bf112d0f6 100644 --- a/livekit/livekit_agent_simulation.twirp.go +++ b/livekit/livekit_agent_simulation.twirp.go @@ -2176,7 +2176,7 @@ func (s *agentSimulationServer) serveCreateScenarioFromSessionProtobuf(ctx conte } func (s *agentSimulationServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor10, 0 + return twirpFileDescriptor9, 0 } func (s *agentSimulationServer) ProtocGenTwirpVersion() string { @@ -2190,7 +2190,7 @@ func (s *agentSimulationServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "AgentSimulation") } -var twirpFileDescriptor10 = []byte{ +var twirpFileDescriptor9 = []byte{ // 4045 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7a, 0x4b, 0x6f, 0x1c, 0x49, 0x72, 0xbf, 0xfa, 0xdd, 0x1d, 0xcd, 0x7e, 0x30, 0xf9, 0x50, 0xab, 0x34, 0x1a, 0x69, 0x34, 0xa3, diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 5411c2488..1c41ae5b3 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: livekit_agentdb.proto package livekit @@ -172,8 +172,7 @@ func (*AgentDB) Descriptor() ([]byte, []int) { } type AgentDB_CreateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Region string `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` // Unset: never expires, and expires_at is then 0 everywhere. // Otherwise clamped to the server cap, and never extended. TtlSeconds int64 `protobuf:"varint,2,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` @@ -211,13 +210,6 @@ func (*AgentDB_CreateRequest) Descriptor() ([]byte, []int) { return file_livekit_agentdb_proto_rawDescGZIP(), []int{0, 0} } -func (x *AgentDB_CreateRequest) GetRegion() string { - if x != nil { - return x.Region - } - return "" -} - func (x *AgentDB_CreateRequest) GetTtlSeconds() int64 { if x != nil { return x.TtlSeconds @@ -427,12 +419,13 @@ func (x *AgentDB_ListResponse) GetNextPageToken() string { // One database, as get and list both report it. type AgentDB_AgentDatabase struct { - state protoimpl.MessageState `protogen:"open.v1"` - DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` - Region string `protobuf:"bytes,2,opt,name=region,proto3" json:"region,omitempty"` - CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - ExpiresAt int64 `protobuf:"varint,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` - Tip int64 `protobuf:"varint,5,opt,name=tip,proto3" json:"tip,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` + // The data region the database lives in, the project's at creation. + Region string `protobuf:"bytes,2,opt,name=region,proto3" json:"region,omitempty"` + CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExpiresAt int64 `protobuf:"varint,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + Tip int64 `protobuf:"varint,5,opt,name=tip,proto3" json:"tip,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2105,12 +2098,11 @@ var File_livekit_agentdb_proto protoreflect.FileDescriptor const file_livekit_agentdb_proto_rawDesc = "" + "\n" + - "\x15livekit_agentdb.proto\x12\alivekit\"\xd8\x19\n" + - "\aAgentDB\x1aH\n" + - "\rCreateRequest\x12\x16\n" + - "\x06region\x18\x01 \x01(\tR\x06region\x12\x1f\n" + + "\x15livekit_agentdb.proto\x12\alivekit\"\xce\x19\n" + + "\aAgentDB\x1a>\n" + + "\rCreateRequest\x12\x1f\n" + "\vttl_seconds\x18\x02 \x01(\x03R\n" + - "ttlSeconds\x1aP\n" + + "ttlSecondsJ\x04\b\x01\x10\x02R\x06region\x1aP\n" + "\x0eCreateResponse\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + "databaseId\x12\x1d\n" + @@ -2247,13 +2239,7 @@ const file_livekit_agentdb_proto_rawDesc = "" + "\n" + "\x06DOUBLE\x10\x02\x12\b\n" + "\x04TEXT\x10\x03\x12\b\n" + - "\x04BLOB\x10\x042\x9d\x03\n" + - "\x0eAgentDBService\x12Q\n" + - "\x0eCreateDatabase\x12\x1e.livekit.AgentDB.CreateRequest\x1a\x1f.livekit.AgentDB.CreateResponse\x12J\n" + - "\vGetDatabase\x12\x1b.livekit.AgentDB.GetRequest\x1a\x1e.livekit.AgentDB.AgentDatabase\x12L\n" + - "\rListDatabases\x12\x1c.livekit.AgentDB.ListRequest\x1a\x1d.livekit.AgentDB.ListResponse\x12Q\n" + - "\x0eDeleteDatabase\x12\x1e.livekit.AgentDB.DeleteRequest\x1a\x1f.livekit.AgentDB.DeleteResponse\x12K\n" + - "\fDumpDatabase\x12\x1c.livekit.AgentDB.DumpRequest\x1a\x1d.livekit.AgentDB.DumpResponseBFZ#github.com/livekit/protocol/livekit\xaa\x02\rLiveKit.Proto\xea\x02\x0eLiveKit::Protob\x06proto3" + "\x04BLOB\x10\x04BFZ#github.com/livekit/protocol/livekit\xaa\x02\rLiveKit.Proto\xea\x02\x0eLiveKit::Protob\x06proto3" var ( file_livekit_agentdb_proto_rawDescOnce sync.Once @@ -2328,18 +2314,8 @@ var file_livekit_agentdb_proto_depIdxs = []int32{ 0, // 19: livekit.AgentDB.Wire.Statement.lang:type_name -> livekit.AgentDB.Wire.QueryLang 17, // 20: livekit.AgentDB.Wire.Batch.statements:type_name -> livekit.AgentDB.Wire.Statement 29, // 21: livekit.AgentDB.Wire.ColumnBatch.columns:type_name -> livekit.AgentDB.Wire.Column - 3, // 22: livekit.AgentDBService.CreateDatabase:input_type -> livekit.AgentDB.CreateRequest - 5, // 23: livekit.AgentDBService.GetDatabase:input_type -> livekit.AgentDB.GetRequest - 6, // 24: livekit.AgentDBService.ListDatabases:input_type -> livekit.AgentDB.ListRequest - 9, // 25: livekit.AgentDBService.DeleteDatabase:input_type -> livekit.AgentDB.DeleteRequest - 11, // 26: livekit.AgentDBService.DumpDatabase:input_type -> livekit.AgentDB.DumpRequest - 4, // 27: livekit.AgentDBService.CreateDatabase:output_type -> livekit.AgentDB.CreateResponse - 8, // 28: livekit.AgentDBService.GetDatabase:output_type -> livekit.AgentDB.AgentDatabase - 7, // 29: livekit.AgentDBService.ListDatabases:output_type -> livekit.AgentDB.ListResponse - 10, // 30: livekit.AgentDBService.DeleteDatabase:output_type -> livekit.AgentDB.DeleteResponse - 12, // 31: livekit.AgentDBService.DumpDatabase:output_type -> livekit.AgentDB.DumpResponse - 27, // [27:32] is the sub-list for method output_type - 22, // [22:27] is the sub-list for method input_type + 22, // [22:22] is the sub-list for method output_type + 22, // [22:22] is the sub-list for method input_type 22, // [22:22] is the sub-list for extension type_name 22, // [22:22] is the sub-list for extension extendee 0, // [0:22] is the sub-list for field type_name @@ -2386,7 +2362,7 @@ func file_livekit_agentdb_proto_init() { NumEnums: 2, NumMessages: 32, NumExtensions: 0, - NumServices: 1, + NumServices: 0, }, GoTypes: file_livekit_agentdb_proto_goTypes, DependencyIndexes: file_livekit_agentdb_proto_depIdxs, diff --git a/livekit/livekit_agentdb.twirp.go b/livekit/livekit_agentdb.twirp.go deleted file mode 100644 index 9c7625944..000000000 --- a/livekit/livekit_agentdb.twirp.go +++ /dev/null @@ -1,1736 +0,0 @@ -// Code generated by protoc-gen-twirp v8.1.3, DO NOT EDIT. -// source: livekit_agentdb.proto - -package livekit - -import context "context" -import fmt "fmt" -import http "net/http" -import io "io" -import json "encoding/json" -import strconv "strconv" -import strings "strings" - -import protojson "google.golang.org/protobuf/encoding/protojson" -import proto "google.golang.org/protobuf/proto" -import twirp "github.com/twitchtv/twirp" -import ctxsetters "github.com/twitchtv/twirp/ctxsetters" - -// Version compatibility assertion. -// If the constant is not defined in the package, that likely means -// the package needs to be updated to work with this generated code. -// See https://twitchtv.github.io/twirp/docs/version_matrix.html -const _ = twirp.TwirpPackageMinVersion_8_1_0 - -// ======================== -// AgentDBService Interface -// ======================== - -// AgentDBService is the lifecycle and export API for SQLite databases; queries -// run over the data plane below. The project comes from the access token. -type AgentDBService interface { - CreateDatabase(context.Context, *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) - - GetDatabase(context.Context, *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) - - ListDatabases(context.Context, *AgentDB_ListRequest) (*AgentDB_ListResponse, error) - - // Deletes the stored data, not just the metadata. - DeleteDatabase(context.Context, *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) - - // Exports a consistent SQLite file as a time-limited download URL. - DumpDatabase(context.Context, *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) -} - -// ============================== -// AgentDBService Protobuf Client -// ============================== - -type agentDBServiceProtobufClient struct { - client HTTPClient - urls [5]string - interceptor twirp.Interceptor - opts twirp.ClientOptions -} - -// NewAgentDBServiceProtobufClient creates a Protobuf client that implements the AgentDBService interface. -// It communicates using Protobuf and can be configured with a custom HTTPClient. -func NewAgentDBServiceProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) AgentDBService { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - // Using ReadOpt allows backwards and forwards compatibility with new options in the future - literalURLs := false - _ = clientOpts.ReadOpt("literalURLs", &literalURLs) - var pathPrefix string - if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { - pathPrefix = "/twirp" // default prefix - } - - // Build method URLs: []/./ - serviceURL := sanitizeBaseURL(baseURL) - serviceURL += baseServicePath(pathPrefix, "livekit", "AgentDBService") - urls := [5]string{ - serviceURL + "CreateDatabase", - serviceURL + "GetDatabase", - serviceURL + "ListDatabases", - serviceURL + "DeleteDatabase", - serviceURL + "DumpDatabase", - } - - return &agentDBServiceProtobufClient{ - client: client, - urls: urls, - interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), - opts: clientOpts, - } -} - -func (c *agentDBServiceProtobufClient) CreateDatabase(ctx context.Context, in *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "CreateDatabase") - caller := c.callCreateDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_CreateRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_CreateRequest) when calling interceptor") - } - return c.callCreateDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_CreateResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_CreateResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceProtobufClient) callCreateDatabase(ctx context.Context, in *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - out := new(AgentDB_CreateResponse) - ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceProtobufClient) GetDatabase(ctx context.Context, in *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "GetDatabase") - caller := c.callGetDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_GetRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_GetRequest) when calling interceptor") - } - return c.callGetDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_AgentDatabase) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_AgentDatabase) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceProtobufClient) callGetDatabase(ctx context.Context, in *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - out := new(AgentDB_AgentDatabase) - ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[1], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceProtobufClient) ListDatabases(ctx context.Context, in *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "ListDatabases") - caller := c.callListDatabases - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_ListRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_ListRequest) when calling interceptor") - } - return c.callListDatabases(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_ListResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_ListResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceProtobufClient) callListDatabases(ctx context.Context, in *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - out := new(AgentDB_ListResponse) - ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[2], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceProtobufClient) DeleteDatabase(ctx context.Context, in *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "DeleteDatabase") - caller := c.callDeleteDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DeleteRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DeleteRequest) when calling interceptor") - } - return c.callDeleteDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DeleteResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DeleteResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceProtobufClient) callDeleteDatabase(ctx context.Context, in *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - out := new(AgentDB_DeleteResponse) - ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[3], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceProtobufClient) DumpDatabase(ctx context.Context, in *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "DumpDatabase") - caller := c.callDumpDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DumpRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DumpRequest) when calling interceptor") - } - return c.callDumpDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DumpResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DumpResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceProtobufClient) callDumpDatabase(ctx context.Context, in *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - out := new(AgentDB_DumpResponse) - ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[4], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// ========================== -// AgentDBService JSON Client -// ========================== - -type agentDBServiceJSONClient struct { - client HTTPClient - urls [5]string - interceptor twirp.Interceptor - opts twirp.ClientOptions -} - -// NewAgentDBServiceJSONClient creates a JSON client that implements the AgentDBService interface. -// It communicates using JSON and can be configured with a custom HTTPClient. -func NewAgentDBServiceJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) AgentDBService { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - // Using ReadOpt allows backwards and forwards compatibility with new options in the future - literalURLs := false - _ = clientOpts.ReadOpt("literalURLs", &literalURLs) - var pathPrefix string - if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { - pathPrefix = "/twirp" // default prefix - } - - // Build method URLs: []/./ - serviceURL := sanitizeBaseURL(baseURL) - serviceURL += baseServicePath(pathPrefix, "livekit", "AgentDBService") - urls := [5]string{ - serviceURL + "CreateDatabase", - serviceURL + "GetDatabase", - serviceURL + "ListDatabases", - serviceURL + "DeleteDatabase", - serviceURL + "DumpDatabase", - } - - return &agentDBServiceJSONClient{ - client: client, - urls: urls, - interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), - opts: clientOpts, - } -} - -func (c *agentDBServiceJSONClient) CreateDatabase(ctx context.Context, in *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "CreateDatabase") - caller := c.callCreateDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_CreateRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_CreateRequest) when calling interceptor") - } - return c.callCreateDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_CreateResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_CreateResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceJSONClient) callCreateDatabase(ctx context.Context, in *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - out := new(AgentDB_CreateResponse) - ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceJSONClient) GetDatabase(ctx context.Context, in *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "GetDatabase") - caller := c.callGetDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_GetRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_GetRequest) when calling interceptor") - } - return c.callGetDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_AgentDatabase) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_AgentDatabase) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceJSONClient) callGetDatabase(ctx context.Context, in *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - out := new(AgentDB_AgentDatabase) - ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[1], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceJSONClient) ListDatabases(ctx context.Context, in *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "ListDatabases") - caller := c.callListDatabases - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_ListRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_ListRequest) when calling interceptor") - } - return c.callListDatabases(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_ListResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_ListResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceJSONClient) callListDatabases(ctx context.Context, in *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - out := new(AgentDB_ListResponse) - ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[2], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceJSONClient) DeleteDatabase(ctx context.Context, in *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "DeleteDatabase") - caller := c.callDeleteDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DeleteRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DeleteRequest) when calling interceptor") - } - return c.callDeleteDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DeleteResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DeleteResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceJSONClient) callDeleteDatabase(ctx context.Context, in *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - out := new(AgentDB_DeleteResponse) - ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[3], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -func (c *agentDBServiceJSONClient) DumpDatabase(ctx context.Context, in *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithMethodName(ctx, "DumpDatabase") - caller := c.callDumpDatabase - if c.interceptor != nil { - caller = func(ctx context.Context, req *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DumpRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DumpRequest) when calling interceptor") - } - return c.callDumpDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DumpResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DumpResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *agentDBServiceJSONClient) callDumpDatabase(ctx context.Context, in *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - out := new(AgentDB_DumpResponse) - ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[4], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// ============================= -// AgentDBService Server Handler -// ============================= - -type agentDBServiceServer struct { - AgentDBService - interceptor twirp.Interceptor - hooks *twirp.ServerHooks - pathPrefix string // prefix for routing - jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response - jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names -} - -// NewAgentDBServiceServer builds a TwirpServer that can be used as an http.Handler to handle -// HTTP requests that are routed to the right method in the provided svc implementation. -// The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). -func NewAgentDBServiceServer(svc AgentDBService, opts ...interface{}) TwirpServer { - serverOpts := newServerOpts(opts) - - // Using ReadOpt allows backwards and forwards compatibility with new options in the future - jsonSkipDefaults := false - _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) - jsonCamelCase := false - _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) - var pathPrefix string - if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { - pathPrefix = "/twirp" // default prefix - } - - return &agentDBServiceServer{ - AgentDBService: svc, - hooks: serverOpts.Hooks, - interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), - pathPrefix: pathPrefix, - jsonSkipDefaults: jsonSkipDefaults, - jsonCamelCase: jsonCamelCase, - } -} - -// writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. -// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) -func (s *agentDBServiceServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { - writeError(ctx, resp, err, s.hooks) -} - -// handleRequestBodyError is used to handle error when the twirp server cannot read request -func (s *agentDBServiceServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { - if context.Canceled == ctx.Err() { - s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) - return - } - if context.DeadlineExceeded == ctx.Err() { - s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) - return - } - s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) -} - -// AgentDBServicePathPrefix is a convenience constant that may identify URL paths. -// Should be used with caution, it only matches routes generated by Twirp Go clients, -// with the default "/twirp" prefix and default CamelCase service and method names. -// More info: https://twitchtv.github.io/twirp/docs/routing.html -const AgentDBServicePathPrefix = "/twirp/livekit.AgentDBService/" - -func (s *agentDBServiceServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - ctx := req.Context() - ctx = ctxsetters.WithPackageName(ctx, "livekit") - ctx = ctxsetters.WithServiceName(ctx, "AgentDBService") - ctx = ctxsetters.WithResponseWriter(ctx, resp) - - var err error - ctx, err = callRequestReceived(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - if req.Method != "POST" { - msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } - - // Verify path format: []/./ - prefix, pkgService, method := parseTwirpPath(req.URL.Path) - if pkgService != "livekit.AgentDBService" { - msg := fmt.Sprintf("no handler for path %q", req.URL.Path) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } - if prefix != s.pathPrefix { - msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } - - switch method { - case "CreateDatabase": - s.serveCreateDatabase(ctx, resp, req) - return - case "GetDatabase": - s.serveGetDatabase(ctx, resp, req) - return - case "ListDatabases": - s.serveListDatabases(ctx, resp, req) - return - case "DeleteDatabase": - s.serveDeleteDatabase(ctx, resp, req) - return - case "DumpDatabase": - s.serveDumpDatabase(ctx, resp, req) - return - default: - msg := fmt.Sprintf("no handler for path %q", req.URL.Path) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } -} - -func (s *agentDBServiceServer) serveCreateDatabase(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveCreateDatabaseJSON(ctx, resp, req) - case "application/protobuf": - s.serveCreateDatabaseProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *agentDBServiceServer) serveCreateDatabaseJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "CreateDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - d := json.NewDecoder(req.Body) - rawReqBody := json.RawMessage{} - if err := d.Decode(&rawReqBody); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - reqContent := new(AgentDB_CreateRequest) - unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} - if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - - handler := s.AgentDBService.CreateDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_CreateRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_CreateRequest) when calling interceptor") - } - return s.AgentDBService.CreateDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_CreateResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_CreateResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_CreateResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_CreateResponse and nil error while calling CreateDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} - respBytes, err := marshaler.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveCreateDatabaseProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "CreateDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := io.ReadAll(req.Body) - if err != nil { - s.handleRequestBodyError(ctx, resp, "failed to read request body", err) - return - } - reqContent := new(AgentDB_CreateRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - handler := s.AgentDBService.CreateDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_CreateRequest) (*AgentDB_CreateResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_CreateRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_CreateRequest) when calling interceptor") - } - return s.AgentDBService.CreateDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_CreateResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_CreateResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_CreateResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_CreateResponse and nil error while calling CreateDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveGetDatabase(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveGetDatabaseJSON(ctx, resp, req) - case "application/protobuf": - s.serveGetDatabaseProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *agentDBServiceServer) serveGetDatabaseJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "GetDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - d := json.NewDecoder(req.Body) - rawReqBody := json.RawMessage{} - if err := d.Decode(&rawReqBody); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - reqContent := new(AgentDB_GetRequest) - unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} - if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - - handler := s.AgentDBService.GetDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_GetRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_GetRequest) when calling interceptor") - } - return s.AgentDBService.GetDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_AgentDatabase) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_AgentDatabase) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_AgentDatabase - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_AgentDatabase and nil error while calling GetDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} - respBytes, err := marshaler.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveGetDatabaseProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "GetDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := io.ReadAll(req.Body) - if err != nil { - s.handleRequestBodyError(ctx, resp, "failed to read request body", err) - return - } - reqContent := new(AgentDB_GetRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - handler := s.AgentDBService.GetDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_GetRequest) (*AgentDB_AgentDatabase, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_GetRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_GetRequest) when calling interceptor") - } - return s.AgentDBService.GetDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_AgentDatabase) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_AgentDatabase) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_AgentDatabase - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_AgentDatabase and nil error while calling GetDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveListDatabases(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveListDatabasesJSON(ctx, resp, req) - case "application/protobuf": - s.serveListDatabasesProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *agentDBServiceServer) serveListDatabasesJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "ListDatabases") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - d := json.NewDecoder(req.Body) - rawReqBody := json.RawMessage{} - if err := d.Decode(&rawReqBody); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - reqContent := new(AgentDB_ListRequest) - unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} - if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - - handler := s.AgentDBService.ListDatabases - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_ListRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_ListRequest) when calling interceptor") - } - return s.AgentDBService.ListDatabases(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_ListResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_ListResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_ListResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_ListResponse and nil error while calling ListDatabases. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} - respBytes, err := marshaler.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveListDatabasesProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "ListDatabases") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := io.ReadAll(req.Body) - if err != nil { - s.handleRequestBodyError(ctx, resp, "failed to read request body", err) - return - } - reqContent := new(AgentDB_ListRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - handler := s.AgentDBService.ListDatabases - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_ListRequest) (*AgentDB_ListResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_ListRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_ListRequest) when calling interceptor") - } - return s.AgentDBService.ListDatabases(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_ListResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_ListResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_ListResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_ListResponse and nil error while calling ListDatabases. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveDeleteDatabase(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveDeleteDatabaseJSON(ctx, resp, req) - case "application/protobuf": - s.serveDeleteDatabaseProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *agentDBServiceServer) serveDeleteDatabaseJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "DeleteDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - d := json.NewDecoder(req.Body) - rawReqBody := json.RawMessage{} - if err := d.Decode(&rawReqBody); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - reqContent := new(AgentDB_DeleteRequest) - unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} - if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - - handler := s.AgentDBService.DeleteDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DeleteRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DeleteRequest) when calling interceptor") - } - return s.AgentDBService.DeleteDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DeleteResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DeleteResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_DeleteResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_DeleteResponse and nil error while calling DeleteDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} - respBytes, err := marshaler.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveDeleteDatabaseProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "DeleteDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := io.ReadAll(req.Body) - if err != nil { - s.handleRequestBodyError(ctx, resp, "failed to read request body", err) - return - } - reqContent := new(AgentDB_DeleteRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - handler := s.AgentDBService.DeleteDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_DeleteRequest) (*AgentDB_DeleteResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DeleteRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DeleteRequest) when calling interceptor") - } - return s.AgentDBService.DeleteDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DeleteResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DeleteResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_DeleteResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_DeleteResponse and nil error while calling DeleteDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveDumpDatabase(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveDumpDatabaseJSON(ctx, resp, req) - case "application/protobuf": - s.serveDumpDatabaseProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *agentDBServiceServer) serveDumpDatabaseJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "DumpDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - d := json.NewDecoder(req.Body) - rawReqBody := json.RawMessage{} - if err := d.Decode(&rawReqBody); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - reqContent := new(AgentDB_DumpRequest) - unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} - if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - - handler := s.AgentDBService.DumpDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DumpRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DumpRequest) when calling interceptor") - } - return s.AgentDBService.DumpDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DumpResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DumpResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_DumpResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_DumpResponse and nil error while calling DumpDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} - respBytes, err := marshaler.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) serveDumpDatabaseProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "DumpDatabase") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := io.ReadAll(req.Body) - if err != nil { - s.handleRequestBodyError(ctx, resp, "failed to read request body", err) - return - } - reqContent := new(AgentDB_DumpRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - handler := s.AgentDBService.DumpDatabase - if s.interceptor != nil { - handler = func(ctx context.Context, req *AgentDB_DumpRequest) (*AgentDB_DumpResponse, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AgentDB_DumpRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AgentDB_DumpRequest) when calling interceptor") - } - return s.AgentDBService.DumpDatabase(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AgentDB_DumpResponse) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AgentDB_DumpResponse) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AgentDB_DumpResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AgentDB_DumpResponse and nil error while calling DumpDatabase. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *agentDBServiceServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor2, 0 -} - -func (s *agentDBServiceServer) ProtocGenTwirpVersion() string { - return "v8.1.3" -} - -// PathPrefix returns the base service path, in the form: "//./" -// that is everything in a Twirp route except for the . This can be used for routing, -// for example to identify the requests that are targeted to this service in a mux. -func (s *agentDBServiceServer) PathPrefix() string { - return baseServicePath(s.pathPrefix, "livekit", "AgentDBService") -} - -var twirpFileDescriptor2 = []byte{ - // 1513 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0xcb, 0x6e, 0xdb, 0x46, - 0x17, 0x96, 0x4c, 0xea, 0xc2, 0x23, 0xc9, 0xbf, 0x30, 0xf9, 0x1b, 0xa8, 0x4c, 0x1c, 0x3b, 0x4e, - 0x51, 0xb8, 0x8b, 0x3a, 0x81, 0x8d, 0x24, 0x68, 0x10, 0xa4, 0xb0, 0x6c, 0x27, 0x72, 0x23, 0x27, - 0xf6, 0xd8, 0xe9, 0x6d, 0x51, 0x82, 0x22, 0x27, 0x32, 0x61, 0x8a, 0x54, 0xc8, 0x91, 0xe3, 0xe4, - 0x05, 0xfa, 0x0a, 0xdd, 0xf4, 0x05, 0xfa, 0x02, 0xdd, 0xf6, 0x05, 0xba, 0x2d, 0xba, 0xec, 0xba, - 0x4f, 0x51, 0x9c, 0x33, 0x43, 0xca, 0x8e, 0x2e, 0xce, 0x8a, 0x33, 0x67, 0xbe, 0x6f, 0x2e, 0xe7, - 0x7c, 0x73, 0xe6, 0x10, 0x3e, 0x09, 0x83, 0x33, 0x71, 0x1a, 0x48, 0xc7, 0xed, 0x8b, 0x48, 0xfa, - 0xbd, 0xf5, 0x61, 0x12, 0xcb, 0x98, 0x55, 0xb4, 0x79, 0xf5, 0xef, 0x4f, 0xa1, 0xb2, 0x85, 0x43, - 0x3b, 0x6d, 0xbb, 0x03, 0x8d, 0xed, 0x44, 0xb8, 0x52, 0x70, 0xf1, 0x66, 0x24, 0x52, 0xc9, 0xae, - 0x43, 0x39, 0x11, 0xfd, 0x20, 0x8e, 0x5a, 0xc5, 0x95, 0xe2, 0x9a, 0xc5, 0x75, 0x8f, 0x2d, 0x43, - 0x4d, 0xca, 0xd0, 0x49, 0x85, 0x17, 0x47, 0x7e, 0xda, 0x5a, 0x58, 0x29, 0xae, 0x19, 0x1c, 0xa4, - 0x0c, 0x8f, 0x94, 0xc5, 0x3e, 0x80, 0xc5, 0x6c, 0xa6, 0x74, 0x18, 0x47, 0xa9, 0x40, 0x8a, 0xef, - 0x4a, 0xb7, 0xe7, 0xa6, 0xc2, 0x09, 0x7c, 0x3d, 0x1f, 0x64, 0xa6, 0x3d, 0x9f, 0x2d, 0x01, 0x88, - 0xf3, 0x61, 0x90, 0x88, 0xd4, 0x71, 0xa5, 0x9e, 0xd2, 0xd2, 0x96, 0x2d, 0x69, 0x7f, 0x09, 0xf0, - 0x4c, 0xc8, 0x6c, 0x63, 0x57, 0xcd, 0x66, 0xef, 0x41, 0xad, 0x1b, 0xa4, 0x39, 0xfe, 0x06, 0x58, - 0x43, 0xb7, 0x2f, 0x9c, 0x34, 0x78, 0x2f, 0x08, 0x5d, 0xe2, 0x55, 0x34, 0x1c, 0x05, 0xef, 0x05, - 0xae, 0x4c, 0x83, 0x32, 0x3e, 0x15, 0x11, 0xad, 0x6c, 0x71, 0x82, 0x1f, 0xa3, 0xc1, 0x96, 0x50, - 0x57, 0x53, 0xe9, 0x93, 0x3c, 0x06, 0x2b, 0x5b, 0x28, 0x6d, 0x15, 0x57, 0x8c, 0xb5, 0xda, 0xc6, - 0xad, 0x75, 0xed, 0xce, 0x75, 0xed, 0x4a, 0xfd, 0xd5, 0x30, 0x3e, 0x26, 0xb0, 0xcf, 0xe1, 0x7f, - 0x91, 0x38, 0x97, 0xce, 0xc4, 0x8a, 0x0d, 0x34, 0x1f, 0xe4, 0xab, 0xfe, 0x52, 0x84, 0xc6, 0xa5, - 0x49, 0xae, 0xf6, 0xe0, 0x38, 0x5a, 0x0b, 0x97, 0xa2, 0xb5, 0x04, 0xe0, 0x51, 0x30, 0x7c, 0xf4, - 0xac, 0xa1, 0x3c, 0xab, 0x2d, 0x5b, 0xf2, 0x03, 0xc7, 0x9b, 0x1f, 0x38, 0x9e, 0x35, 0xc1, 0x90, - 0xc1, 0xb0, 0x55, 0x22, 0x3b, 0x36, 0xed, 0x7b, 0xd0, 0xd8, 0x11, 0xa1, 0x18, 0xcb, 0xe4, 0xca, - 0x68, 0x34, 0x61, 0x31, 0x63, 0x28, 0x27, 0xda, 0xeb, 0x50, 0xdb, 0x19, 0x0d, 0x86, 0x1f, 0x3d, - 0x43, 0x0f, 0xea, 0x0a, 0xaf, 0x83, 0x70, 0x1b, 0xea, 0x7e, 0xfc, 0x36, 0x0a, 0x63, 0xd7, 0x77, - 0x46, 0x49, 0xa8, 0x19, 0xb5, 0xcc, 0xf6, 0x2a, 0x09, 0xaf, 0x10, 0x54, 0x76, 0x2e, 0x63, 0x7c, - 0xae, 0x3f, 0xaf, 0x81, 0xf9, 0x5d, 0x90, 0x08, 0xfb, 0x1f, 0x13, 0x1a, 0xdb, 0x61, 0x20, 0x22, - 0xb9, 0x2f, 0xd2, 0xd4, 0xed, 0x93, 0x44, 0x12, 0xb5, 0xd5, 0x6c, 0x7b, 0x0d, 0x6e, 0x69, 0xcb, - 0x9e, 0xcf, 0x36, 0xa1, 0x74, 0x22, 0xc2, 0x30, 0xa6, 0x55, 0x6a, 0x1b, 0x37, 0x26, 0xe4, 0x80, - 0xd3, 0xae, 0x77, 0x10, 0xd2, 0x29, 0x70, 0x85, 0x65, 0xf7, 0xc1, 0x14, 0xe7, 0xc2, 0xa3, 0x1d, - 0xd4, 0x36, 0x96, 0xa7, 0x73, 0x8e, 0xa4, 0x2b, 0xc5, 0x40, 0x44, 0xb2, 0x53, 0xe0, 0x04, 0x67, - 0x0f, 0xa1, 0xf4, 0x66, 0x24, 0x92, 0x77, 0x14, 0xa9, 0x8f, 0xe2, 0x29, 0x3c, 0x6e, 0xb2, 0xe7, - 0x4a, 0xef, 0x84, 0x42, 0x39, 0x73, 0x93, 0x6d, 0x84, 0x20, 0x89, 0xb0, 0x44, 0x12, 0xfd, 0x20, - 0x6a, 0x95, 0xe7, 0x92, 0x10, 0x42, 0x24, 0x6c, 0xb0, 0x07, 0x50, 0xf6, 0xe2, 0xc1, 0x20, 0x90, - 0xad, 0x0a, 0xb1, 0x6e, 0x4e, 0x67, 0x6d, 0x13, 0xa6, 0x53, 0xe0, 0x1a, 0xcd, 0x1e, 0x43, 0x35, - 0x89, 0xc3, 0xb0, 0xe7, 0x7a, 0xa7, 0xad, 0x2a, 0x31, 0x6f, 0x4d, 0x67, 0x72, 0x8d, 0xea, 0x14, - 0x78, 0xce, 0xa0, 0x55, 0xdd, 0xc8, 0x13, 0x61, 0xcb, 0x9a, 0xbb, 0x2a, 0x61, 0x68, 0x55, 0x6a, - 0x11, 0x2f, 0x11, 0x7e, 0x20, 0x5b, 0x30, 0x97, 0x47, 0x18, 0xe2, 0x51, 0x8b, 0xdd, 0x03, 0x73, - 0x18, 0x44, 0xfd, 0x56, 0x8d, 0x58, 0xf6, 0x74, 0xd6, 0x41, 0x10, 0xf5, 0x31, 0x74, 0x88, 0x6c, - 0x5b, 0x50, 0x19, 0x28, 0x41, 0xd9, 0x7f, 0x19, 0xd0, 0x38, 0x12, 0xc9, 0x99, 0x48, 0x3e, 0x52, - 0x62, 0x8f, 0xa0, 0x4a, 0xb2, 0x71, 0xe2, 0x53, 0xad, 0xb2, 0xa5, 0x39, 0x2a, 0x7b, 0x89, 0xae, - 0xa9, 0x9c, 0xa8, 0x26, 0xfb, 0x0a, 0x2a, 0x5e, 0x1c, 0x8e, 0x06, 0x51, 0xaa, 0xc5, 0xb6, 0x34, - 0x2b, 0x20, 0x04, 0x42, 0xaa, 0xc6, 0xb3, 0xa7, 0x50, 0x57, 0x4d, 0x47, 0x69, 0x47, 0x89, 0xee, - 0xf6, 0x3c, 0x7e, 0xa6, 0xa0, 0x9a, 0x37, 0xee, 0xb2, 0x6d, 0xa8, 0xa1, 0x7a, 0x9d, 0x44, 0xa4, - 0xa3, 0x50, 0x6a, 0x09, 0xae, 0x4c, 0x9f, 0x66, 0xf7, 0x5c, 0x78, 0x9c, 0x70, 0x9d, 0x02, 0x07, - 0x91, 0xf7, 0xd0, 0xe3, 0x7e, 0x1c, 0x09, 0xad, 0xc5, 0x19, 0x1e, 0xdf, 0x89, 0x23, 0x81, 0x1e, - 0x47, 0x24, 0xca, 0x57, 0x24, 0x49, 0x9c, 0x68, 0x21, 0xce, 0x90, 0xef, 0x2e, 0x42, 0x50, 0xbe, - 0x84, 0xa5, 0xc0, 0xc6, 0x51, 0x5f, 0x4b, 0x70, 0x56, 0x60, 0x63, 0x1d, 0xd8, 0xf8, 0x72, 0x60, - 0x7f, 0x2f, 0x42, 0xe9, 0x5b, 0x37, 0x1c, 0x61, 0xbe, 0x86, 0x68, 0x14, 0x86, 0xce, 0x19, 0xf6, - 0x28, 0xa0, 0xd5, 0x4e, 0x81, 0x5b, 0x68, 0x53, 0x80, 0x25, 0xb0, 0x82, 0x48, 0xea, 0x71, 0xca, - 0x4f, 0xa8, 0xe7, 0x20, 0x92, 0x6a, 0xf8, 0x0e, 0xa6, 0xb8, 0x51, 0x2f, 0x14, 0x1a, 0x81, 0xa1, - 0x2b, 0xa2, 0x5f, 0x95, 0x35, 0x5f, 0x44, 0xe2, 0x73, 0xa2, 0x20, 0x18, 0x1d, 0x0b, 0x17, 0x41, - 0x5b, 0x0e, 0xe8, 0x85, 0x71, 0x4f, 0x03, 0xd0, 0xef, 0x75, 0x04, 0xa0, 0x8d, 0x00, 0xed, 0x0a, - 0x94, 0x68, 0xcc, 0xfe, 0xb9, 0x08, 0x56, 0x9e, 0x36, 0x30, 0x3d, 0xa6, 0x6f, 0xb2, 0xbc, 0x8a, - 0x4d, 0xb6, 0x09, 0xe5, 0xa1, 0x9b, 0xb8, 0x03, 0x7c, 0xef, 0x8d, 0xd9, 0xce, 0xa4, 0x59, 0xb9, - 0x86, 0xb2, 0x4d, 0x30, 0x43, 0x37, 0xea, 0xd3, 0xe6, 0x17, 0x67, 0x25, 0xab, 0x43, 0xcc, 0x4f, - 0x5d, 0x37, 0xea, 0x73, 0x02, 0xdb, 0x1d, 0x28, 0x29, 0xd5, 0x7c, 0x0d, 0x90, 0x66, 0x3b, 0xca, - 0xde, 0xda, 0xab, 0x12, 0x1e, 0xbf, 0x40, 0xb1, 0x2b, 0x50, 0xa2, 0xdc, 0x64, 0x57, 0xa1, 0xac, - 0xd2, 0x8d, 0x0d, 0x50, 0xcd, 0xd2, 0x07, 0x59, 0x29, 0x09, 0xd8, 0xab, 0x50, 0x56, 0x17, 0x9c, - 0xb5, 0xa0, 0x42, 0x52, 0xa7, 0xc7, 0x1d, 0x2f, 0x61, 0xd6, 0xb5, 0xbf, 0x00, 0x13, 0xaf, 0x33, - 0xbe, 0x3d, 0x32, 0x18, 0x88, 0x54, 0xba, 0x83, 0xa1, 0x33, 0x50, 0x30, 0x83, 0xd7, 0x72, 0xdb, - 0x7e, 0x6a, 0xff, 0x04, 0x26, 0x0a, 0x84, 0x6d, 0xc2, 0xf5, 0xd0, 0x4d, 0xa5, 0x83, 0xd7, 0xdf, - 0x99, 0x42, 0xba, 0x86, 0xa3, 0x38, 0xe9, 0xf1, 0x98, 0x3c, 0x31, 0xff, 0xc2, 0xe4, 0xfc, 0x4f, - 0xa0, 0x44, 0xf7, 0x9c, 0xfd, 0x1f, 0x4a, 0xaa, 0x88, 0x50, 0x81, 0x52, 0x9d, 0x0f, 0x9f, 0xd3, - 0x85, 0x89, 0xe7, 0x74, 0x00, 0x15, 0x9d, 0x27, 0xb2, 0x77, 0xb0, 0x98, 0xbf, 0x83, 0x6c, 0x0d, - 0x9a, 0xb4, 0xdf, 0x20, 0x92, 0x22, 0x39, 0x73, 0xc3, 0x6c, 0x0f, 0x0d, 0xbe, 0x88, 0xf6, 0x3d, - 0x6d, 0xde, 0xa7, 0x62, 0x26, 0x3f, 0x59, 0x3c, 0x92, 0x08, 0x34, 0x08, 0xd8, 0x18, 0xea, 0x33, - 0xc5, 0x23, 0xb9, 0x9f, 0xda, 0xcb, 0x50, 0xd1, 0xb9, 0x05, 0x37, 0x1c, 0xb9, 0x03, 0x5d, 0x39, - 0x59, 0x5c, 0x75, 0xec, 0x3f, 0x8a, 0x18, 0x1f, 0x44, 0xd0, 0x89, 0xde, 0x0d, 0xb5, 0xf7, 0xeb, - 0x5c, 0x75, 0x18, 0x03, 0x33, 0x40, 0x0d, 0xa0, 0xf4, 0x18, 0xa7, 0x36, 0x46, 0x4a, 0x5d, 0x05, - 0x5c, 0xd5, 0x58, 0x2b, 0xf2, 0xac, 0x8b, 0xe5, 0x1e, 0xdd, 0x0a, 0x3c, 0x31, 0x5d, 0x8a, 0x3a, - 0xaf, 0xa2, 0x01, 0x6b, 0xa9, 0x7c, 0x50, 0x60, 0xe9, 0x5a, 0x5a, 0x31, 0xd6, 0x1a, 0x6a, 0x70, - 0x37, 0xf2, 0x89, 0x49, 0xd7, 0x85, 0x98, 0x65, 0xc5, 0x44, 0x43, 0xc6, 0xa4, 0x41, 0x62, 0x56, - 0x14, 0x13, 0x0d, 0xc8, 0xb4, 0x7f, 0x80, 0xda, 0x85, 0xfc, 0xc7, 0x1e, 0x8c, 0x73, 0xae, 0xd2, - 0xed, 0xcd, 0x79, 0x39, 0x73, 0x9c, 0x70, 0x19, 0x98, 0x49, 0xfc, 0x36, 0x73, 0x38, 0xb5, 0xed, - 0x00, 0x60, 0x9c, 0x13, 0xd9, 0x1d, 0x68, 0xa0, 0xd5, 0x71, 0x5f, 0xbf, 0x16, 0x9e, 0x14, 0xbe, - 0x0e, 0x5d, 0x1d, 0x8d, 0x5b, 0xda, 0xc6, 0x3e, 0x83, 0x45, 0x12, 0x5e, 0x10, 0xa5, 0x22, 0x91, - 0x99, 0x08, 0x0c, 0x5e, 0x47, 0xeb, 0x1e, 0x19, 0xf7, 0xfc, 0x29, 0x35, 0xd0, 0x43, 0x30, 0x31, - 0x81, 0x4e, 0x51, 0xc5, 0x12, 0x80, 0x8c, 0xa5, 0x1b, 0x3a, 0xf9, 0xf6, 0x4c, 0x6e, 0x91, 0x85, - 0xe3, 0x1e, 0xef, 0x43, 0x89, 0xd2, 0x28, 0x1e, 0xc0, 0x8b, 0x7d, 0xa1, 0x05, 0x49, 0x6d, 0x8c, - 0x94, 0xce, 0x8f, 0x5a, 0x8b, 0x59, 0x77, 0xd5, 0x06, 0x2b, 0xbf, 0xfd, 0xac, 0x02, 0xc6, 0xd1, - 0x61, 0xb7, 0x59, 0x58, 0x35, 0xab, 0xc5, 0x66, 0x71, 0xf5, 0x09, 0x58, 0x94, 0x4c, 0x8e, 0xdf, - 0x0d, 0x05, 0xab, 0x82, 0xf9, 0xe2, 0x55, 0xb7, 0xdb, 0x2c, 0x20, 0x6a, 0xef, 0xc5, 0x71, 0xb3, - 0xc8, 0x00, 0xca, 0x3b, 0x2f, 0x5f, 0xb5, 0xbb, 0xbb, 0xcd, 0x05, 0x1c, 0x3e, 0xde, 0xfd, 0xfe, - 0xb8, 0x69, 0x60, 0xab, 0xdd, 0x7d, 0xd9, 0x6e, 0x9a, 0x1b, 0xbf, 0x1a, 0xb0, 0xa8, 0x7d, 0x8d, - 0x4f, 0x6d, 0xe0, 0x09, 0x76, 0x98, 0xfd, 0x97, 0xe4, 0x55, 0xf5, 0x64, 0x85, 0x71, 0xe9, 0x17, - 0xc8, 0x5e, 0x9e, 0x39, 0xae, 0x2b, 0xd1, 0x6f, 0xa0, 0xf6, 0x4c, 0x8c, 0xab, 0xf4, 0xc9, 0xac, - 0x38, 0xfe, 0x6d, 0xb1, 0xaf, 0xf8, 0x4f, 0x60, 0x5d, 0x68, 0xe0, 0xaf, 0xc6, 0x4e, 0xfe, 0xb7, - 0x30, 0x29, 0x9a, 0x0b, 0x7f, 0x35, 0xf6, 0xd2, 0x8c, 0x51, 0xbd, 0xb3, 0xc3, 0xac, 0xea, 0x9e, - 0x73, 0xd8, 0x4b, 0x85, 0xfc, 0x94, 0xc3, 0x5e, 0x2e, 0xdb, 0xd9, 0x73, 0x55, 0x86, 0xe7, 0x13, - 0x4e, 0xee, 0xef, 0x42, 0x55, 0x3f, 0x65, 0x7f, 0x17, 0x6b, 0xf8, 0xf6, 0xd3, 0x1f, 0xef, 0xf4, - 0x03, 0x79, 0x32, 0xea, 0xad, 0x7b, 0xf1, 0xe0, 0xae, 0x86, 0xde, 0xa5, 0xff, 0x53, 0x2f, 0x0e, - 0x33, 0xc3, 0x6f, 0x0b, 0x8d, 0x6e, 0x70, 0x26, 0x9e, 0x07, 0x72, 0xfd, 0x00, 0x87, 0xfe, 0x5d, - 0x58, 0xd4, 0xfd, 0x47, 0x8f, 0xc8, 0xd0, 0x2b, 0x13, 0x65, 0xf3, 0xbf, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xc5, 0x9a, 0x43, 0xe8, 0xeb, 0x0e, 0x00, 0x00, -} diff --git a/livekit/livekit_cloud_agent.twirp.go b/livekit/livekit_cloud_agent.twirp.go index 689ae25bc..27ce9a912 100644 --- a/livekit/livekit_cloud_agent.twirp.go +++ b/livekit/livekit_cloud_agent.twirp.go @@ -5245,7 +5245,7 @@ func (s *cloudAgentServer) serveGetPrivateLinkStatusProtobuf(ctx context.Context } func (s *cloudAgentServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor7, 0 + return twirpFileDescriptor6, 0 } func (s *cloudAgentServer) ProtocGenTwirpVersion() string { @@ -5259,7 +5259,7 @@ func (s *cloudAgentServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "CloudAgent") } -var twirpFileDescriptor7 = []byte{ +var twirpFileDescriptor6 = []byte{ // 2600 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5a, 0x5d, 0x6f, 0x1b, 0x4b, 0xf9, 0xcf, 0xfa, 0x2d, 0xf6, 0xe3, 0xb8, 0x71, 0x26, 0x69, 0xea, 0x38, 0x49, 0x9b, 0x6c, 0xdb, diff --git a/livekit/livekit_connector.twirp.go b/livekit/livekit_connector.twirp.go index 472bc090a..d88f01c00 100644 --- a/livekit/livekit_connector.twirp.go +++ b/livekit/livekit_connector.twirp.go @@ -1618,7 +1618,7 @@ func (s *connectorServer) serveConnectTwilioCallProtobuf(ctx context.Context, re } func (s *connectorServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor9, 0 + return twirpFileDescriptor8, 0 } func (s *connectorServer) ProtocGenTwirpVersion() string { @@ -1632,7 +1632,7 @@ func (s *connectorServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "Connector") } -var twirpFileDescriptor9 = []byte{ +var twirpFileDescriptor8 = []byte{ // 313 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0x41, 0x4b, 0xf3, 0x30, 0x1c, 0xc6, 0xdf, 0xb7, 0xc2, 0xd4, 0xbf, 0x4e, 0x6b, 0x04, 0x85, 0x1e, 0x64, 0x5b, 0x45, 0xc1, diff --git a/livekit/livekit_egress.twirp.go b/livekit/livekit_egress.twirp.go index a13f58701..acfc9cfad 100644 --- a/livekit/livekit_egress.twirp.go +++ b/livekit/livekit_egress.twirp.go @@ -3018,7 +3018,7 @@ func (s *egressServer) serveStartTrackEgressProtobuf(ctx context.Context, resp h } func (s *egressServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor3, 0 + return twirpFileDescriptor2, 0 } func (s *egressServer) ProtocGenTwirpVersion() string { @@ -3032,7 +3032,7 @@ func (s *egressServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "Egress") } -var twirpFileDescriptor3 = []byte{ +var twirpFileDescriptor2 = []byte{ // 4219 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x3b, 0x4b, 0x70, 0x1b, 0x5b, 0x56, 0xee, 0xd6, 0xff, 0xe8, 0xd7, 0xbe, 0x76, 0x1c, 0xc5, 0x99, 0x4c, 0x32, 0xca, 0xbc, 0x97, diff --git a/livekit/livekit_ingress.twirp.go b/livekit/livekit_ingress.twirp.go index acb4d95a2..fa7244522 100644 --- a/livekit/livekit_ingress.twirp.go +++ b/livekit/livekit_ingress.twirp.go @@ -1341,7 +1341,7 @@ func (s *ingressServer) serveDeleteIngressProtobuf(ctx context.Context, resp htt } func (s *ingressServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor4, 0 + return twirpFileDescriptor3, 0 } func (s *ingressServer) ProtocGenTwirpVersion() string { @@ -1355,7 +1355,7 @@ func (s *ingressServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "Ingress") } -var twirpFileDescriptor4 = []byte{ +var twirpFileDescriptor3 = []byte{ // 1620 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x6f, 0xdb, 0xcc, 0x11, 0x36, 0xf5, 0xad, 0x91, 0x25, 0x2b, 0x6b, 0xf9, 0x7d, 0x15, 0x7f, 0xb4, 0x82, 0x92, 0x22, diff --git a/livekit/livekit_phone_number.twirp.go b/livekit/livekit_phone_number.twirp.go index b1cdd5a54..786c5280e 100644 --- a/livekit/livekit_phone_number.twirp.go +++ b/livekit/livekit_phone_number.twirp.go @@ -1904,7 +1904,7 @@ func (s *phoneNumberServiceServer) serveReleasePhoneNumbersProtobuf(ctx context. } func (s *phoneNumberServiceServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor8, 0 + return twirpFileDescriptor7, 0 } func (s *phoneNumberServiceServer) ProtocGenTwirpVersion() string { @@ -1918,7 +1918,7 @@ func (s *phoneNumberServiceServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "PhoneNumberService") } -var twirpFileDescriptor8 = []byte{ +var twirpFileDescriptor7 = []byte{ // 1393 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0xcf, 0x6e, 0xdb, 0x46, 0x13, 0xd7, 0x4a, 0xb6, 0x63, 0x8d, 0xfe, 0x58, 0xde, 0x38, 0x31, 0x4d, 0xc7, 0xb1, 0x4c, 0x7f, diff --git a/livekit/livekit_room.twirp.go b/livekit/livekit_room.twirp.go index 122269f8a..eae205ebf 100644 --- a/livekit/livekit_room.twirp.go +++ b/livekit/livekit_room.twirp.go @@ -4152,7 +4152,7 @@ func (s *roomServiceServer) servePerformRpcProtobuf(ctx context.Context, resp ht } func (s *roomServiceServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor5, 0 + return twirpFileDescriptor4, 0 } func (s *roomServiceServer) ProtocGenTwirpVersion() string { @@ -4166,7 +4166,7 @@ func (s *roomServiceServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "RoomService") } -var twirpFileDescriptor5 = []byte{ +var twirpFileDescriptor4 = []byte{ // 1612 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x5b, 0x6f, 0x13, 0xd7, 0x16, 0x3e, 0x4e, 0x6c, 0xc7, 0x5e, 0xce, 0xcd, 0x3b, 0x01, 0x26, 0x13, 0x2e, 0x66, 0xc2, 0x39, diff --git a/livekit/livekit_sip.twirp.go b/livekit/livekit_sip.twirp.go index d0c943eb9..04202ef10 100644 --- a/livekit/livekit_sip.twirp.go +++ b/livekit/livekit_sip.twirp.go @@ -4687,7 +4687,7 @@ func (s *sIPServer) serveTransferSIPParticipantProtobuf(ctx context.Context, res } func (s *sIPServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor6, 0 + return twirpFileDescriptor5, 0 } func (s *sIPServer) ProtocGenTwirpVersion() string { @@ -4701,7 +4701,7 @@ func (s *sIPServer) PathPrefix() string { return baseServicePath(s.pathPrefix, "livekit", "SIP") } -var twirpFileDescriptor6 = []byte{ +var twirpFileDescriptor5 = []byte{ // 5979 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x3c, 0x5b, 0x8c, 0x23, 0xd9, 0x55, 0x53, 0x2e, 0x77, 0xb7, 0xfb, 0xba, 0xdb, 0x5d, 0x7d, 0xbb, 0xa7, 0xd7, 0xd3, 0xf3, 0x5c, diff --git a/magefile.go b/magefile.go index 5b42cfd6d..b55d1d004 100644 --- a/magefile.go +++ b/magefile.go @@ -60,7 +60,6 @@ func Proto() error { twirpProtoFiles := []string{ "cloud_replay.proto", "livekit_agent_dispatch.proto", - "livekit_agentdb.proto", "livekit_egress.proto", "livekit_ingress.proto", "livekit_room.proto", @@ -78,10 +77,12 @@ func Proto() error { "agent/livekit_agent_session.proto", "agent/livekit_agent_dev.proto", "agent/livekit_agent_inference.proto", + "agent/livekit_agent_thread.proto", } protoFiles := []string{ "livekit_agent.proto", + "livekit_agentdb.proto", "livekit_analytics.proto", "livekit_internal.proto", "livekit_models.proto", diff --git a/packages/javascript/src/index.d.ts b/packages/javascript/src/index.d.ts index 150b0958a..fb505f671 100644 --- a/packages/javascript/src/index.d.ts +++ b/packages/javascript/src/index.d.ts @@ -1,6 +1,7 @@ import { protoInt64, Timestamp } from "@bufbuild/protobuf"; export { protoInt64, Timestamp }; export * as AgentSession from "./gen/agent/livekit_agent_session_pb.js"; +export * as AgentThread from "./gen/agent/livekit_agent_thread_pb.js"; export * as AgentInference from "./gen/agent/livekit_agent_inference_pb.js"; export * as AgentText from "./gen/agent/livekit_agent_text_pb.js"; export * as AgentDev from "./gen/agent/livekit_agent_dev_pb.js"; diff --git a/packages/javascript/src/index.js b/packages/javascript/src/index.js index d1c654f8c..ef3f0a37c 100644 --- a/packages/javascript/src/index.js +++ b/packages/javascript/src/index.js @@ -2,6 +2,7 @@ import { protoInt64, Timestamp } from "@bufbuild/protobuf"; export { protoInt64, Timestamp }; export * as AgentSession from "./gen/agent/livekit_agent_session_pb.js"; +export * as AgentThread from "./gen/agent/livekit_agent_thread_pb.js"; export * as AgentInference from "./gen/agent/livekit_agent_inference_pb.js"; export * as AgentText from "./gen/agent/livekit_agent_text_pb.js"; export * as AgentDev from "./gen/agent/livekit_agent_dev_pb.js"; diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto new file mode 100644 index 000000000..720a9e11e --- /dev/null +++ b/protobufs/agent/livekit_agent_thread.proto @@ -0,0 +1,186 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package livekit.agent; + +option go_package = "github.com/livekit/protocol/livekit/agent"; + +import "logger/options.proto"; + +// AgentThread messages: the durable conversation an agent has with a person +// across channels and over time. A thread is named by its "AT_" id and +// optionally by keys, opaque strings an application attaches ("phone:+1...") to +// find it again; a key resolves to at most one thread per agent. The project +// comes from the access token, and the thread's database lives in the project's +// data region. +// +// The service over these messages is internal gRPC (cloud-protocol +// AgentThreads); the public API is public-api-server's ThreadService. Errors are +// gRPC codes: +// AlreadyExists a key attached to another thread, without replace +// InvalidArgument a key, sub or attribute over its size limit +// FailedPrecondition the thread was redirected; the caller re-reads at +// Thread.redirect_to +// PermissionDenied a thread, subject or agent outside the token's +// AgentThreadGrant +// NotFound otherwise +// +// Calls: +// Create a keyed create is get-or-create: if the key already names a +// thread of this agent, that thread is returned with created = false +// and nothing else in the request is applied. Without a key a new +// thread is always created. +// Get by id, or by (agent_name, key). The lookup by key is reserved to the +// project credential; an AgentThreadGrant may only read by id. +// List the threads of one subject under one agent, newest first. +// Update attributes and idle TTL. Not available to an AgentThreadGrant: +// attributes are written by the agent's tools or the application's +// backend. +// Touch marks the thread active now and pushes expires_at out by its idle +// TTL. +// AddKey a key already attached to another thread of the same agent is +// AlreadyExists unless replace is set, which moves it. +// Delete deletes the thread, its keys and its database. +// EnsureStateDatabase creates the thread's database if it has none and +// records it on the thread. Idempotent. +// Redirect sets redirect_to on a fresh thread after its items were copied into +// the found one. Reserved to the project credential. Refused with +// FailedPrecondition when the row already redirects elsewhere. +// +// Nested so that names like Thread and CreateRequest need not be unique across +// the livekit.agent package. Times throughout are int64 unix seconds. +message AgentThread { + // One key under one agent. Keys are scoped per agent: the same string under + // two agents names two threads. + message AgentKey { + string agent_name = 1; + string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + } + + message CreateRequest { + string agent_name = 1; + // Optional. When set the call is get-or-create on (agent_name, key). At + // most 256 bytes. + string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + // Optional. The subject the thread belongs to, the value List selects on + // and an AgentThreadGrant is scoped by. At most 256 bytes. + string sub = 3 [(logger.sensitivity) = SENSITIVITY_PII]; + map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; + // 0 takes the server default. The thread expires this long after its last + // activity. + int64 idle_ttl_seconds = 5; + } + + message CreateResponse { + Thread thread = 1; + // False when a key in the request already named a thread, which is the one + // returned. + bool created = 2; + } + + message GetRequest { + oneof lookup { + string thread_id = 1 [(logger.name) = "threadID"]; + AgentKey by_key = 2; + } + } + + message ListRequest { + string agent_name = 1; + string sub = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + // Optional. The cursor from the previous page's ListResponse.before; opaque + // to the client. Empty starts from the newest thread. + string before = 3; + // Server-clamped. + int32 limit = 4; + } + + message ListResponse { + repeated Thread threads = 1; + // The cursor for the next page; empty at the end. + string before = 2; + } + + message UpdateRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + // Merged into the thread's attributes: a key with an empty value deletes the + // entry, keys not present are left alone. + map attributes = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + optional int64 idle_ttl_seconds = 3; + } + + message TouchRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + } + + message TouchResponse { + int64 expires_at = 1; + } + + message AddKeyRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + // At most 256 bytes. + string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + // Optional. The key is dropped this long after it is attached; 0 keeps it + // for the life of the thread. + int64 ttl_seconds = 3; + // Move the key here when another thread of the same agent holds it. + bool replace = 4; + } + + message RemoveKeyRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + } + + message DeleteRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + } + + message DeleteResponse {} + + message EnsureStateDatabaseRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + } + + message RedirectRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + // The thread the items were copied into. + string redirect_to = 2 [(logger.name) = "redirectTo"]; + } + + // One thread, as every read reports it. Keys are never returned, by any call + // and with any token: only their count is. + message Thread { + string thread_id = 1 [(logger.name) = "threadID"]; + // The agent that created the thread. + string agent_name = 2; + uint32 key_count = 3; + string sub = 4 [(logger.sensitivity) = SENSITIVITY_PII]; + map attributes = 5 [(logger.sensitivity) = SENSITIVITY_PII]; + int64 created_at = 6; + int64 last_active = 7; + int64 expires_at = 8; + int64 idle_ttl_seconds = 9; + // The thread's database on agent-db, "DB_..."; empty until the first write + // or EnsureStateDatabase. + string database_id = 10; + // Set when this thread was merged into another. Threads report the redirect + // and never follow it: calls on a redirected thread fail with + // FailedPrecondition and the caller re-reads at this id. + string redirect_to = 11; + } +} diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index db4a837af..e506624ef 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -21,23 +21,19 @@ option go_package = "github.com/livekit/protocol/livekit"; option csharp_namespace = "LiveKit.Proto"; option ruby_package = "LiveKit::Proto"; -// AgentDBService is the lifecycle and export API for SQLite databases; queries -// run over the data plane below. The project comes from the access token. -service AgentDBService { - rpc CreateDatabase(AgentDB.CreateRequest) returns (AgentDB.CreateResponse); - rpc GetDatabase(AgentDB.GetRequest) returns (AgentDB.AgentDatabase); - rpc ListDatabases(AgentDB.ListRequest) returns (AgentDB.ListResponse); - // Deletes the stored data, not just the metadata. - rpc DeleteDatabase(AgentDB.DeleteRequest) returns (AgentDB.DeleteResponse); - // Exports a consistent SQLite file as a time-limited download URL. - rpc DumpDatabase(AgentDB.DumpRequest) returns (AgentDB.DumpResponse); -} +// AgentDB messages: the lifecycle and export of SQLite databases, whose queries +// run over the data plane below. The project comes from the access token, and a +// database lives in the project's data region. The service over the lifecycle +// messages is internal gRPC (cloud-protocol AgentDB); the public API is +// public-api-server's DatabaseService. // Nested so that names like Value and Ping need not be unique across the // livekit package. `tip` throughout is the latest durable commit sequence. message AgentDB { message CreateRequest { - string region = 1; + // The project's data region decides where a database lives. + reserved 1; + reserved "region"; // Unset: never expires, and expires_at is then 0 everywhere. // Otherwise clamped to the server cap, and never extended. int64 ttl_seconds = 2; @@ -65,6 +61,7 @@ message AgentDB { // One database, as get and list both report it. message AgentDatabase { string database_id = 1; + // The data region the database lives in, the project's at creation. string region = 2; int64 created_at = 3; int64 expires_at = 4; diff --git a/utils/guid/id.go b/utils/guid/id.go index b01dd380f..bc7a7d0db 100644 --- a/utils/guid/id.go +++ b/utils/guid/id.go @@ -72,6 +72,8 @@ const ( PhoneNumberPrefix = "PN_" ScenarioPrefix = "SCN_" ScenarioGroupPrefix = "SCNG_" + AgentThreadPrefix = "AT_" + AgentStreamPrefix = "AST_" // ST_ is SIPTrunkPrefix ) const ( From 3b581243c1febd9a159efcfbfc5a655bbb797cfc Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:04:40 +0000 Subject: [PATCH 02/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- livekit/livekit_agentdb.pb.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index b8600201a..b67219495 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 1c41ae5b3..45286b3a3 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: livekit_agentdb.proto package livekit From 45962ce0954555395df4bc27b18015a8a6976837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 18:31:45 -0700 Subject: [PATCH 03/56] agent threads: shorter comments, keys are not PII Keys stay readable in logs, which is what debugging a lookup needs. --- auth/grants.go | 36 ++---- livekit/agent/livekit_agent_thread.pb.go | 133 +++++++-------------- livekit/livekit_agentdb.pb.go | 13 +- protobufs/agent/livekit_agent_thread.proto | 97 ++++----------- protobufs/livekit_agentdb.proto | 10 +- 5 files changed, 84 insertions(+), 205 deletions(-) diff --git a/auth/grants.go b/auth/grants.go index 2bc776094..3704e64cd 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -715,33 +715,21 @@ func (s *AgentEndpointGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // ------------------------------------------------------------------ -// AgentThreadGrant scopes a token to agent threads: the durable -// conversations of one agent, selected by subject or by id. It is minted by -// the application's backend the way a room token is, so the subject rides in -// the signature and a request body never names it. The JWT sub claim stays the -// participant identity; the thread subject is Sub. +// AgentThreadGrant scopes a token to one agent's threads, selected by subject +// or by id, with per-action flags. type AgentThreadGrant struct { - // AgentName is required: threads, keys and subjects are scoped per agent. - AgentName string `json:"agentName"` - // Sub is the subject whose threads the holder may see and create. - Sub string `json:"sub,omitempty"` - // ThreadIDs restricts the grant to these threads instead. One of Sub and - // ThreadIDs is required; a grant with neither covers no thread. + AgentName string `json:"agentName"` + Sub string `json:"sub,omitempty"` ThreadIDs []string `json:"threadIds,omitempty"` List bool `json:"list,omitempty"` - // Create lets a first message create a thread, with Sub as its subject. - Create bool `json:"create,omitempty"` - // Send grants message/send, message/stream and tasks/cancel. - Send bool `json:"send,omitempty"` - // Read grants get, chat_history and background_tasks. - Read bool `json:"read,omitempty"` - Delete bool `json:"delete,omitempty"` -} - -// Covers reports whether a thread is in the grant's scope: it was created by -// the grant's agent, and either its subject equals the grant's Sub or its id is -// listed in ThreadIDs. The action flags are checked by the caller. Matching -// is exact and case-sensitive; an empty Sub or thread id never matches. + Create bool `json:"create,omitempty"` + Send bool `json:"send,omitempty"` + Read bool `json:"read,omitempty"` + Delete bool `json:"delete,omitempty"` +} + +// Covers reports whether a thread of agentName is in scope, by subject or by +// id. An empty Sub or thread id never matches; the caller checks the flags. func (s *AgentThreadGrant) Covers(agentName, sub, threadID string) bool { if s == nil || s.AgentName == "" || s.AgentName != agentName { return false diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index b67219495..1d16050f4 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -36,50 +36,10 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// AgentThread messages: the durable conversation an agent has with a person -// across channels and over time. A thread is named by its "AT_" id and -// optionally by keys, opaque strings an application attaches ("phone:+1...") to -// find it again; a key resolves to at most one thread per agent. The project -// comes from the access token, and the thread's database lives in the project's -// data region. -// -// The service over these messages is internal gRPC (cloud-protocol -// AgentThreads); the public API is public-api-server's ThreadService. Errors are -// gRPC codes: -// -// AlreadyExists a key attached to another thread, without replace -// InvalidArgument a key, sub or attribute over its size limit -// FailedPrecondition the thread was redirected; the caller re-reads at -// Thread.redirect_to -// PermissionDenied a thread, subject or agent outside the token's -// AgentThreadGrant -// NotFound otherwise -// -// Calls: -// -// Create a keyed create is get-or-create: if the key already names a -// thread of this agent, that thread is returned with created = false -// and nothing else in the request is applied. Without a key a new -// thread is always created. -// Get by id, or by (agent_name, key). The lookup by key is reserved to the -// project credential; an AgentThreadGrant may only read by id. -// List the threads of one subject under one agent, newest first. -// Update attributes and idle TTL. Not available to an AgentThreadGrant: -// attributes are written by the agent's tools or the application's -// backend. -// Touch marks the thread active now and pushes expires_at out by its idle -// TTL. -// AddKey a key already attached to another thread of the same agent is -// AlreadyExists unless replace is set, which moves it. -// Delete deletes the thread, its keys and its database. -// EnsureStateDatabase creates the thread's database if it has none and -// records it on the thread. Idempotent. -// Redirect sets redirect_to on a fresh thread after its items were copied into -// the found one. Reserved to the project credential. Refused with -// FailedPrecondition when the row already redirects elsewhere. -// -// Nested so that names like Thread and CreateRequest need not be unique across -// the livekit.agent package. Times throughout are int64 unix seconds. +// A thread is the durable conversation an agent has with a person across +// channels. It is named by its "AT_" id and optionally by keys an application +// attaches to find it again; a key resolves to at most one thread per agent. +// Times are unix seconds. type AgentThread struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -116,8 +76,6 @@ func (*AgentThread) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0} } -// One key under one agent. Keys are scoped per agent: the same string under -// two agents names two threads. type AgentThread_AgentKey struct { state protoimpl.MessageState `protogen:"open.v1"` AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` @@ -173,15 +131,12 @@ func (x *AgentThread_AgentKey) GetKey() string { type AgentThread_CreateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` - // Optional. When set the call is get-or-create on (agent_name, key). At - // most 256 bytes. + // When set, create is get-or-create on (agent_name, key). Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - // Optional. The subject the thread belongs to, the value List selects on - // and an AgentThreadGrant is scoped by. At most 256 bytes. + // The subject the thread belongs to, as scoped by an AgentThreadGrant. Sub string `protobuf:"bytes,3,opt,name=sub,proto3" json:"sub,omitempty"` Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // 0 takes the server default. The thread expires this long after its last - // activity. + // 0 takes the server default. IdleTtlSeconds int64 `protobuf:"varint,5,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3" json:"idle_ttl_seconds,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -255,8 +210,7 @@ func (x *AgentThread_CreateRequest) GetIdleTtlSeconds() int64 { type AgentThread_CreateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Thread *AgentThread_Thread `protobuf:"bytes,1,opt,name=thread,proto3" json:"thread,omitempty"` - // False when a key in the request already named a thread, which is the one - // returned. + // False when the key already named a thread, which is returned as is. Created bool `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -388,15 +342,14 @@ func (*AgentThread_GetRequest_ThreadId) isAgentThread_GetRequest_Lookup() {} func (*AgentThread_GetRequest_ByKey) isAgentThread_GetRequest_Lookup() {} +// The threads of one subject under one agent, newest first. type AgentThread_ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` Sub string `protobuf:"bytes,2,opt,name=sub,proto3" json:"sub,omitempty"` - // Optional. The cursor from the previous page's ListResponse.before; opaque - // to the client. Empty starts from the newest thread. - Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` - // Server-clamped. - Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + // Cursor from the previous ListResponse; empty for the first page. + Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -462,7 +415,7 @@ func (x *AgentThread_ListRequest) GetLimit() int32 { type AgentThread_ListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Threads []*AgentThread_Thread `protobuf:"bytes,1,rep,name=threads,proto3" json:"threads,omitempty"` - // The cursor for the next page; empty at the end. + // Empty at the end. Before string `protobuf:"bytes,2,opt,name=before,proto3" json:"before,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -515,8 +468,7 @@ func (x *AgentThread_ListResponse) GetBefore() string { type AgentThread_UpdateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // Merged into the thread's attributes: a key with an empty value deletes the - // entry, keys not present are left alone. + // Merged; an empty value deletes the entry. Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` IdleTtlSeconds *int64 `protobuf:"varint,3,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3,oneof" json:"idle_ttl_seconds,omitempty"` unknownFields protoimpl.UnknownFields @@ -665,10 +617,8 @@ func (x *AgentThread_TouchResponse) GetExpiresAt() int64 { type AgentThread_AddKeyRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // At most 256 bytes. - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - // Optional. The key is dropped this long after it is attached; 0 keeps it - // for the life of the thread. + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // 0 keeps the key for the life of the thread. TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` // Move the key here when another thread of the same agent holds it. Replace bool `protobuf:"varint,4,opt,name=replace,proto3" json:"replace,omitempty"` @@ -866,6 +816,7 @@ func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 12} } +// Creates the thread's database if it has none. Idempotent. type AgentThread_EnsureStateDatabaseRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` @@ -910,11 +861,11 @@ func (x *AgentThread_EnsureStateDatabaseRequest) GetThreadId() string { return "" } +// Marks a thread as merged into redirect_to. type AgentThread_RedirectRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // The thread the items were copied into. - RedirectTo string `protobuf:"bytes,2,opt,name=redirect_to,json=redirectTo,proto3" json:"redirect_to,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + RedirectTo string `protobuf:"bytes,2,opt,name=redirect_to,json=redirectTo,proto3" json:"redirect_to,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -963,13 +914,11 @@ func (x *AgentThread_RedirectRequest) GetRedirectTo() string { return "" } -// One thread, as every read reports it. Keys are never returned, by any call -// and with any token: only their count is. type AgentThread_Thread struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // The agent that created the thread. - AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + // Keys themselves are never returned. KeyCount uint32 `protobuf:"varint,3,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` Sub string `protobuf:"bytes,4,opt,name=sub,proto3" json:"sub,omitempty"` Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` @@ -977,12 +926,10 @@ type AgentThread_Thread struct { LastActive int64 `protobuf:"varint,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` ExpiresAt int64 `protobuf:"varint,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` IdleTtlSeconds int64 `protobuf:"varint,9,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3" json:"idle_ttl_seconds,omitempty"` - // The thread's database on agent-db, "DB_..."; empty until the first write - // or EnsureStateDatabase. + // "DB_..."; empty until the first write or EnsureStateDatabase. DatabaseId string `protobuf:"bytes,10,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` - // Set when this thread was merged into another. Threads report the redirect - // and never follow it: calls on a redirected thread fail with - // FailedPrecondition and the caller re-reads at this id. + // Set when merged into another thread; calls on it then fail with + // FailedPrecondition. RedirectTo string `protobuf:"bytes,11,opt,name=redirect_to,json=redirectTo,proto3" json:"redirect_to,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -1099,16 +1046,16 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x14logger/options.proto\"\x8f\x11\n" + - "\vAgentThread\x1a@\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x14logger/options.proto\"\xfb\x10\n" + + "\vAgentThread\x1a;\n" + "\bAgentKey\x12\x1d\n" + "\n" + - "agent_name\x18\x01 \x01(\tR\tagentName\x12\x15\n" + - "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x1a\xa4\x02\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x1a\x9f\x02\n" + "\rCreateRequest\x12\x1d\n" + "\n" + - "agent_name\x18\x01 \x01(\tR\tagentName\x12\x15\n" + - "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x12\x15\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x15\n" + "\x03sub\x18\x03 \x01(\tB\x03\xc0P\x01R\x03sub\x12]\n" + "\n" + "attributes\x18\x04 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + @@ -1148,16 +1095,16 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a.\n" + "\rTouchResponse\x12\x1d\n" + "\n" + - "expires_at\x18\x01 \x01(\x03R\texpiresAt\x1a\x8b\x01\n" + + "expires_at\x18\x01 \x01(\x03R\texpiresAt\x1a\x86\x01\n" + "\rAddKeyRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x15\n" + - "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x12\x1f\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x1f\n" + "\vttl_seconds\x18\x03 \x01(\x03R\n" + "ttlSeconds\x12\x18\n" + - "\areplace\x18\x04 \x01(\bR\areplace\x1aS\n" + + "\areplace\x18\x04 \x01(\bR\areplace\x1aN\n" + "\x10RemoveKeyRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x15\n" + - "\x03key\x18\x02 \x01(\tB\x03\xc0P\x01R\x03key\x1a9\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x1a9\n" + "\rDeleteRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + "\x0eDeleteResponse\x1aF\n" + diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 45286b3a3..0f52f5b9c 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -419,13 +419,12 @@ func (x *AgentDB_ListResponse) GetNextPageToken() string { // One database, as get and list both report it. type AgentDB_AgentDatabase struct { - state protoimpl.MessageState `protogen:"open.v1"` - DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` - // The data region the database lives in, the project's at creation. - Region string `protobuf:"bytes,2,opt,name=region,proto3" json:"region,omitempty"` - CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - ExpiresAt int64 `protobuf:"varint,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` - Tip int64 `protobuf:"varint,5,opt,name=tip,proto3" json:"tip,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` + Region string `protobuf:"bytes,2,opt,name=region,proto3" json:"region,omitempty"` + CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExpiresAt int64 `protobuf:"varint,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + Tip int64 `protobuf:"varint,5,opt,name=tip,proto3" json:"tip,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 720a9e11e..d87048273 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -20,74 +20,30 @@ option go_package = "github.com/livekit/protocol/livekit/agent"; import "logger/options.proto"; -// AgentThread messages: the durable conversation an agent has with a person -// across channels and over time. A thread is named by its "AT_" id and -// optionally by keys, opaque strings an application attaches ("phone:+1...") to -// find it again; a key resolves to at most one thread per agent. The project -// comes from the access token, and the thread's database lives in the project's -// data region. -// -// The service over these messages is internal gRPC (cloud-protocol -// AgentThreads); the public API is public-api-server's ThreadService. Errors are -// gRPC codes: -// AlreadyExists a key attached to another thread, without replace -// InvalidArgument a key, sub or attribute over its size limit -// FailedPrecondition the thread was redirected; the caller re-reads at -// Thread.redirect_to -// PermissionDenied a thread, subject or agent outside the token's -// AgentThreadGrant -// NotFound otherwise -// -// Calls: -// Create a keyed create is get-or-create: if the key already names a -// thread of this agent, that thread is returned with created = false -// and nothing else in the request is applied. Without a key a new -// thread is always created. -// Get by id, or by (agent_name, key). The lookup by key is reserved to the -// project credential; an AgentThreadGrant may only read by id. -// List the threads of one subject under one agent, newest first. -// Update attributes and idle TTL. Not available to an AgentThreadGrant: -// attributes are written by the agent's tools or the application's -// backend. -// Touch marks the thread active now and pushes expires_at out by its idle -// TTL. -// AddKey a key already attached to another thread of the same agent is -// AlreadyExists unless replace is set, which moves it. -// Delete deletes the thread, its keys and its database. -// EnsureStateDatabase creates the thread's database if it has none and -// records it on the thread. Idempotent. -// Redirect sets redirect_to on a fresh thread after its items were copied into -// the found one. Reserved to the project credential. Refused with -// FailedPrecondition when the row already redirects elsewhere. -// -// Nested so that names like Thread and CreateRequest need not be unique across -// the livekit.agent package. Times throughout are int64 unix seconds. +// A thread is the durable conversation an agent has with a person across +// channels. It is named by its "AT_" id and optionally by keys an application +// attaches to find it again; a key resolves to at most one thread per agent. +// Times are unix seconds. message AgentThread { - // One key under one agent. Keys are scoped per agent: the same string under - // two agents names two threads. message AgentKey { string agent_name = 1; - string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + string key = 2; } message CreateRequest { string agent_name = 1; - // Optional. When set the call is get-or-create on (agent_name, key). At - // most 256 bytes. - string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; - // Optional. The subject the thread belongs to, the value List selects on - // and an AgentThreadGrant is scoped by. At most 256 bytes. + // When set, create is get-or-create on (agent_name, key). + string key = 2; + // The subject the thread belongs to, as scoped by an AgentThreadGrant. string sub = 3 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; - // 0 takes the server default. The thread expires this long after its last - // activity. + // 0 takes the server default. int64 idle_ttl_seconds = 5; } message CreateResponse { Thread thread = 1; - // False when a key in the request already named a thread, which is the one - // returned. + // False when the key already named a thread, which is returned as is. bool created = 2; } @@ -98,26 +54,24 @@ message AgentThread { } } + // The threads of one subject under one agent, newest first. message ListRequest { string agent_name = 1; string sub = 2 [(logger.sensitivity) = SENSITIVITY_PII]; - // Optional. The cursor from the previous page's ListResponse.before; opaque - // to the client. Empty starts from the newest thread. + // Cursor from the previous ListResponse; empty for the first page. string before = 3; - // Server-clamped. int32 limit = 4; } message ListResponse { repeated Thread threads = 1; - // The cursor for the next page; empty at the end. + // Empty at the end. string before = 2; } message UpdateRequest { string thread_id = 1 [(logger.name) = "threadID"]; - // Merged into the thread's attributes: a key with an empty value deletes the - // entry, keys not present are left alone. + // Merged; an empty value deletes the entry. map attributes = 2 [(logger.sensitivity) = SENSITIVITY_PII]; optional int64 idle_ttl_seconds = 3; } @@ -132,10 +86,8 @@ message AgentThread { message AddKeyRequest { string thread_id = 1 [(logger.name) = "threadID"]; - // At most 256 bytes. - string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; - // Optional. The key is dropped this long after it is attached; 0 keeps it - // for the life of the thread. + string key = 2; + // 0 keeps the key for the life of the thread. int64 ttl_seconds = 3; // Move the key here when another thread of the same agent holds it. bool replace = 4; @@ -143,7 +95,7 @@ message AgentThread { message RemoveKeyRequest { string thread_id = 1 [(logger.name) = "threadID"]; - string key = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + string key = 2; } message DeleteRequest { @@ -152,22 +104,21 @@ message AgentThread { message DeleteResponse {} + // Creates the thread's database if it has none. Idempotent. message EnsureStateDatabaseRequest { string thread_id = 1 [(logger.name) = "threadID"]; } + // Marks a thread as merged into redirect_to. message RedirectRequest { string thread_id = 1 [(logger.name) = "threadID"]; - // The thread the items were copied into. string redirect_to = 2 [(logger.name) = "redirectTo"]; } - // One thread, as every read reports it. Keys are never returned, by any call - // and with any token: only their count is. message Thread { string thread_id = 1 [(logger.name) = "threadID"]; - // The agent that created the thread. string agent_name = 2; + // Keys themselves are never returned. uint32 key_count = 3; string sub = 4 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 5 [(logger.sensitivity) = SENSITIVITY_PII]; @@ -175,12 +126,10 @@ message AgentThread { int64 last_active = 7; int64 expires_at = 8; int64 idle_ttl_seconds = 9; - // The thread's database on agent-db, "DB_..."; empty until the first write - // or EnsureStateDatabase. + // "DB_..."; empty until the first write or EnsureStateDatabase. string database_id = 10; - // Set when this thread was merged into another. Threads report the redirect - // and never follow it: calls on a redirected thread fail with - // FailedPrecondition and the caller re-reads at this id. + // Set when merged into another thread; calls on it then fail with + // FailedPrecondition. string redirect_to = 11; } } diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index e506624ef..174d9c1ba 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -21,17 +21,14 @@ option go_package = "github.com/livekit/protocol/livekit"; option csharp_namespace = "LiveKit.Proto"; option ruby_package = "LiveKit::Proto"; -// AgentDB messages: the lifecycle and export of SQLite databases, whose queries -// run over the data plane below. The project comes from the access token, and a -// database lives in the project's data region. The service over the lifecycle -// messages is internal gRPC (cloud-protocol AgentDB); the public API is -// public-api-server's DatabaseService. +// Lifecycle and export of SQLite databases; queries run over the data plane +// below. The service is cloud-protocol's AgentDB. // Nested so that names like Value and Ping need not be unique across the // livekit package. `tip` throughout is the latest durable commit sequence. message AgentDB { message CreateRequest { - // The project's data region decides where a database lives. + // A database lives in its project's data region. reserved 1; reserved "region"; // Unset: never expires, and expires_at is then 0 everywhere. @@ -61,7 +58,6 @@ message AgentDB { // One database, as get and list both report it. message AgentDatabase { string database_id = 1; - // The data region the database lives in, the project's at creation. string region = 2; int64 created_at = 3; int64 expires_at = 4; From baf08585a9f6541c797d76e53622608e8b18cf39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 18:32:24 -0700 Subject: [PATCH 04/56] agent threads: explain AgentThread and AgentDB at the top of their protos --- livekit/agent/livekit_agent_thread.pb.go | 12 ++++++++---- livekit/livekit_agentdb.pb.go | 8 +++++++- protobufs/agent/livekit_agent_thread.proto | 10 +++++++--- protobufs/livekit_agentdb.proto | 8 +++++--- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 1d16050f4..283a0c8c2 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -12,10 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +// An AgentThread is the durable conversation an agent has with a person, +// continuing across channels (voice, SMS, chat, email) and over time. It is +// named by an "AT_" id and optionally by keys an application attaches to find +// it again, such as a phone number; a key resolves to at most one thread per +// agent. A thread carries a subject, attributes, an idle TTL and the AgentDB +// database that holds its state. Its service is cloud-protocol's AgentThreads. + // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -36,9 +43,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// A thread is the durable conversation an agent has with a person across -// channels. It is named by its "AT_" id and optionally by keys an application -// attaches to find it again; a key resolves to at most one thread per agent. // Times are unix seconds. type AgentThread struct { state protoimpl.MessageState `protogen:"open.v1"` diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 0f52f5b9c..26e6e18b2 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -12,10 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +// AgentDB gives each agent its own SQLite databases. A database lives in its +// project's data region, on object storage with a replicated hot tier for +// recent commits, and has one writer at a time. Queries and transactions run +// over the WebSocket data plane (AgentDB.Wire); creating, listing, deleting and +// exporting databases go through cloud-protocol's AgentDB gRPC service. + // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: livekit_agentdb.proto package livekit diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index d87048273..9566c4cfc 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -12,6 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +// An AgentThread is the durable conversation an agent has with a person, +// continuing across channels (voice, SMS, chat, email) and over time. It is +// named by an "AT_" id and optionally by keys an application attaches to find +// it again, such as a phone number; a key resolves to at most one thread per +// agent. A thread carries a subject, attributes, an idle TTL and the AgentDB +// database that holds its state. Its service is cloud-protocol's AgentThreads. + syntax = "proto3"; package livekit.agent; @@ -20,9 +27,6 @@ option go_package = "github.com/livekit/protocol/livekit/agent"; import "logger/options.proto"; -// A thread is the durable conversation an agent has with a person across -// channels. It is named by its "AT_" id and optionally by keys an application -// attaches to find it again; a key resolves to at most one thread per agent. // Times are unix seconds. message AgentThread { message AgentKey { diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index 174d9c1ba..35403519f 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// AgentDB gives each agent its own SQLite databases. A database lives in its +// project's data region, on object storage with a replicated hot tier for +// recent commits, and has one writer at a time. Queries and transactions run +// over the WebSocket data plane (AgentDB.Wire); creating, listing, deleting and +// exporting databases go through cloud-protocol's AgentDB gRPC service. syntax = "proto3"; @@ -21,9 +26,6 @@ option go_package = "github.com/livekit/protocol/livekit"; option csharp_namespace = "LiveKit.Proto"; option ruby_package = "LiveKit::Proto"; -// Lifecycle and export of SQLite databases; queries run over the data plane -// below. The service is cloud-protocol's AgentDB. - // Nested so that names like Value and Ping need not be unique across the // livekit package. `tip` throughout is the latest durable commit sequence. message AgentDB { From 7f11c3a1151c794c91cac4bc1ded35f94ba78bcb Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:33:06 +0000 Subject: [PATCH 05/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- livekit/livekit_agentdb.pb.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 283a0c8c2..cb81bf5fc 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -22,7 +22,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 26e6e18b2..396ed795f 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: livekit_agentdb.proto package livekit From 6961016011bbd60f0154e8b5ac4b21643e63d794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 18:37:30 -0700 Subject: [PATCH 06/56] agent threads: tighter proto headers --- protobufs/agent/livekit_agent_thread.proto | 11 +++++------ protobufs/livekit_agentdb.proto | 8 +++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 9566c4cfc..68f1f71c7 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// An AgentThread is the durable conversation an agent has with a person, -// continuing across channels (voice, SMS, chat, email) and over time. It is -// named by an "AT_" id and optionally by keys an application attaches to find -// it again, such as a phone number; a key resolves to at most one thread per -// agent. A thread carries a subject, attributes, an idle TTL and the AgentDB -// database that holds its state. Its service is cloud-protocol's AgentThreads. +// AgentThread: the durable conversation between an agent and a person, across +// channels and over time. Identified by an "AT_" id; keys (such as a phone +// number) find it again, at most one thread per key and agent. Holds a subject, +// attributes, an idle TTL and the AgentDB database with its state. Served by +// cloud-protocol's AgentThreads. syntax = "proto3"; diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index 35403519f..8824e9805 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// AgentDB gives each agent its own SQLite databases. A database lives in its -// project's data region, on object storage with a replicated hot tier for -// recent commits, and has one writer at a time. Queries and transactions run -// over the WebSocket data plane (AgentDB.Wire); creating, listing, deleting and -// exporting databases go through cloud-protocol's AgentDB gRPC service. +// AgentDB: per-agent SQLite databases. Each lives in its project's data region +// on object storage, with one writer at a time. Queries run over the WebSocket +// data plane (AgentDB.Wire); lifecycle is cloud-protocol's AgentDB service. syntax = "proto3"; From b2587545dcb2235e372cc405267c2ed8b6d95d6f Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:38:27 +0000 Subject: [PATCH 07/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 11 +++++------ livekit/livekit_agentdb.pb.go | 8 +++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index cb81bf5fc..798764571 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// An AgentThread is the durable conversation an agent has with a person, -// continuing across channels (voice, SMS, chat, email) and over time. It is -// named by an "AT_" id and optionally by keys an application attaches to find -// it again, such as a phone number; a key resolves to at most one thread per -// agent. A thread carries a subject, attributes, an idle TTL and the AgentDB -// database that holds its state. Its service is cloud-protocol's AgentThreads. +// AgentThread: the durable conversation between an agent and a person, across +// channels and over time. Identified by an "AT_" id; keys (such as a phone +// number) find it again, at most one thread per key and agent. Holds a subject, +// attributes, an idle TTL and the AgentDB database with its state. Served by +// cloud-protocol's AgentThreads. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 396ed795f..fae84af93 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// AgentDB gives each agent its own SQLite databases. A database lives in its -// project's data region, on object storage with a replicated hot tier for -// recent commits, and has one writer at a time. Queries and transactions run -// over the WebSocket data plane (AgentDB.Wire); creating, listing, deleting and -// exporting databases go through cloud-protocol's AgentDB gRPC service. +// AgentDB: per-agent SQLite databases. Each lives in its project's data region +// on object storage, with one writer at a time. Queries run over the WebSocket +// data plane (AgentDB.Wire); lifecycle is cloud-protocol's AgentDB service. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: From 5e166e4f0cfc1cc02434a184f731d61818007f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 19:19:04 -0700 Subject: [PATCH 08/56] auth: AgentThreadGrant can limit the agent's endpoints One agent can expose several endpoints (sms, chat, voice). Endpoints names the ones the holder may call; empty allows all. Threads stay per agent, so every endpoint continues the same thread. --- .changeset/agent-threads.md | 2 +- auth/grants.go | 17 ++++++++++++++++- auth/grants_test.go | 11 +++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index f40055040..cccced593 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim, and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim (optionally limited to some of the agent's endpoints), and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is reserved. diff --git a/auth/grants.go b/auth/grants.go index 3704e64cd..945bb6058 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -716,11 +716,13 @@ func (s *AgentEndpointGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // ------------------------------------------------------------------ // AgentThreadGrant scopes a token to one agent's threads, selected by subject -// or by id, with per-action flags. +// or by id, with per-action flags. Endpoints limits which of the agent's +// endpoints the holder may call; empty allows all of them. type AgentThreadGrant struct { AgentName string `json:"agentName"` Sub string `json:"sub,omitempty"` ThreadIDs []string `json:"threadIds,omitempty"` + Endpoints []string `json:"endpoints,omitempty"` List bool `json:"list,omitempty"` Create bool `json:"create,omitempty"` Send bool `json:"send,omitempty"` @@ -740,6 +742,15 @@ func (s *AgentThreadGrant) Covers(agentName, sub, threadID string) bool { return threadID != "" && slices.Contains(s.ThreadIDs, threadID) } +// AllowsEndpoint reports whether the holder may call endpoint of the grant's +// agent. +func (s *AgentThreadGrant) AllowsEndpoint(endpoint string) bool { + if s == nil { + return false + } + return len(s.Endpoints) == 0 || slices.Contains(s.Endpoints, endpoint) +} + func (s *AgentThreadGrant) Clone() *AgentThreadGrant { if s == nil { return nil @@ -749,6 +760,9 @@ func (s *AgentThreadGrant) Clone() *AgentThreadGrant { if len(s.ThreadIDs) > 0 { clone.ThreadIDs = append([]string{}, s.ThreadIDs...) } + if len(s.Endpoints) > 0 { + clone.Endpoints = append([]string{}, s.Endpoints...) + } return &clone } @@ -761,6 +775,7 @@ func (s *AgentThreadGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { e.AddString("AgentName", s.AgentName) e.AddString("Sub", s.Sub) zap.Strings("ThreadIDs", s.ThreadIDs).AddTo(e) + zap.Strings("Endpoints", s.Endpoints).AddTo(e) e.AddBool("List", s.List) e.AddBool("Create", s.Create) e.AddBool("Send", s.Send) diff --git a/auth/grants_test.go b/auth/grants_test.go index 9a2c7a0ac..f4c7062e9 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -567,6 +567,17 @@ func TestAgentThreadGrantCovers(t *testing.T) { } } +func TestAgentThreadGrantAllowsEndpoint(t *testing.T) { + t.Parallel() + + all := &AgentThreadGrant{AgentName: "a"} + chat := &AgentThreadGrant{AgentName: "a", Endpoints: []string{"chat"}} + require.True(t, all.AllowsEndpoint("voice")) + require.True(t, chat.AllowsEndpoint("chat")) + require.False(t, chat.AllowsEndpoint("voice")) + require.False(t, (*AgentThreadGrant)(nil).AllowsEndpoint("chat")) +} + func TestAgentThreadGrantCloneIndependent(t *testing.T) { t.Parallel() From ed2de68f11fa99549cd3760321afde4e119b38d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 20:24:21 -0700 Subject: [PATCH 09/56] agent threads: subject, Timestamp/Duration times, CreateThreadDatabase and MergeThread From review: sub is spelled subject (grant and messages), times are google.protobuf.Timestamp/Duration (threads and the AgentDB lifecycle), EnsureStateDatabase is CreateThreadDatabase, Redirect is MergeThread with merged_into on the thread. --- .changeset/agent-threads.md | 2 +- auth/accesstoken_test.go | 2 +- auth/grants.go | 10 +- auth/grants_test.go | 26 +- livekit/agent/livekit_agent_thread.pb.go | 300 +++++++++++---------- livekit/livekit_agentdb.pb.go | 126 +++++---- protobufs/agent/livekit_agent_thread.proto | 47 ++-- protobufs/livekit_agentdb.proto | 15 +- 8 files changed, 277 insertions(+), 251 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index cccced593..991dc2688 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -5,4 +5,4 @@ Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim (optionally limited to some of the agent's endpoints), and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. -The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is reserved. +The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is reserved, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/auth/accesstoken_test.go b/auth/accesstoken_test.go index c712785db..092acb1d2 100644 --- a/auth/accesstoken_test.go +++ b/auth/accesstoken_test.go @@ -249,7 +249,7 @@ func TestAgentThreadGrantRoundTrip(t *testing.T) { t.Parallel() apiKey, secret := apiKeypair() - grant := &AgentThreadGrant{AgentName: "my-agent", Sub: "user-1", ThreadIDs: []string{"AT_1"}, Read: true, Send: true} + grant := &AgentThreadGrant{AgentName: "my-agent", Subject: "user-1", ThreadIDs: []string{"AT_1"}, Read: true, Send: true} raw, err := NewAccessToken(apiKey, secret). SetAgentThreadGrant(grant). SetValidFor(time.Minute). diff --git a/auth/grants.go b/auth/grants.go index 945bb6058..d5fa2b09c 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -720,7 +720,7 @@ func (s *AgentEndpointGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // endpoints the holder may call; empty allows all of them. type AgentThreadGrant struct { AgentName string `json:"agentName"` - Sub string `json:"sub,omitempty"` + Subject string `json:"subject,omitempty"` ThreadIDs []string `json:"threadIds,omitempty"` Endpoints []string `json:"endpoints,omitempty"` List bool `json:"list,omitempty"` @@ -731,12 +731,12 @@ type AgentThreadGrant struct { } // Covers reports whether a thread of agentName is in scope, by subject or by -// id. An empty Sub or thread id never matches; the caller checks the flags. -func (s *AgentThreadGrant) Covers(agentName, sub, threadID string) bool { +// id. An empty Subject or thread id never matches; the caller checks the flags. +func (s *AgentThreadGrant) Covers(agentName, subject, threadID string) bool { if s == nil || s.AgentName == "" || s.AgentName != agentName { return false } - if s.Sub != "" && s.Sub == sub { + if s.Subject != "" && s.Subject == subject { return true } return threadID != "" && slices.Contains(s.ThreadIDs, threadID) @@ -773,7 +773,7 @@ func (s *AgentThreadGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { } e.AddString("AgentName", s.AgentName) - e.AddString("Sub", s.Sub) + e.AddString("Subject", s.Subject) zap.Strings("ThreadIDs", s.ThreadIDs).AddTo(e) zap.Strings("Endpoints", s.Endpoints).AddTo(e) e.AddBool("List", s.List) diff --git a/auth/grants_test.go b/auth/grants_test.go index f4c7062e9..1067bed78 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -532,37 +532,37 @@ func TestAgentEndpointGrantCloneIndependent(t *testing.T) { func TestAgentThreadGrantCovers(t *testing.T) { t.Parallel() - bySub := &AgentThreadGrant{AgentName: "a", Sub: "u1"} + bySubject := &AgentThreadGrant{AgentName: "a", Subject: "u1"} byID := &AgentThreadGrant{AgentName: "a", ThreadIDs: []string{"AT_1", "AT_2"}} - both := &AgentThreadGrant{AgentName: "a", Sub: "u1", ThreadIDs: []string{"AT_1"}} + both := &AgentThreadGrant{AgentName: "a", Subject: "u1", ThreadIDs: []string{"AT_1"}} cases := []struct { name string grant *AgentThreadGrant agentName string - sub string + subject string threadID string want bool }{ {"nil grant", nil, "a", "u1", "AT_1", false}, - {"no agent", &AgentThreadGrant{Sub: "u1"}, "", "u1", "AT_1", false}, - {"neither sub nor ids", &AgentThreadGrant{AgentName: "a"}, "a", "", "AT_1", false}, - {"sub match", bySub, "a", "u1", "AT_9", true}, - {"sub mismatch", bySub, "a", "u2", "AT_9", false}, - {"sub empty on thread", bySub, "a", "", "AT_9", false}, - {"agent mismatch", bySub, "b", "u1", "AT_9", false}, - {"agent case differs", bySub, "A", "u1", "AT_9", false}, + {"no agent", &AgentThreadGrant{Subject: "u1"}, "", "u1", "AT_1", false}, + {"neither subject nor ids", &AgentThreadGrant{AgentName: "a"}, "a", "", "AT_1", false}, + {"subject match", bySubject, "a", "u1", "AT_9", true}, + {"subject mismatch", bySubject, "a", "u2", "AT_9", false}, + {"subject empty on thread", bySubject, "a", "", "AT_9", false}, + {"agent mismatch", bySubject, "b", "u1", "AT_9", false}, + {"agent case differs", bySubject, "A", "u1", "AT_9", false}, {"id listed", byID, "a", "u2", "AT_2", true}, {"id not listed", byID, "a", "u2", "AT_3", false}, {"id empty", byID, "a", "u2", "", false}, {"id listed, agent mismatch", byID, "b", "u2", "AT_2", false}, - {"both: sub", both, "a", "u1", "AT_9", true}, + {"both: subject", both, "a", "u1", "AT_9", true}, {"both: id", both, "a", "u2", "AT_1", true}, {"both: neither", both, "a", "u2", "AT_9", false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, c.grant.Covers(c.agentName, c.sub, c.threadID)) + require.Equal(t, c.want, c.grant.Covers(c.agentName, c.subject, c.threadID)) }) } } @@ -581,7 +581,7 @@ func TestAgentThreadGrantAllowsEndpoint(t *testing.T) { func TestAgentThreadGrantCloneIndependent(t *testing.T) { t.Parallel() - grants := &ClaimGrants{AgentThread: &AgentThreadGrant{AgentName: "a", Sub: "u1", ThreadIDs: []string{"AT_1"}, Send: true}} + grants := &ClaimGrants{AgentThread: &AgentThreadGrant{AgentName: "a", Subject: "u1", ThreadIDs: []string{"AT_1"}, Send: true}} clone := grants.Clone() require.NotSame(t, grants.AgentThread, clone.AgentThread) require.True(t, reflect.DeepEqual(grants.AgentThread, clone.AgentThread)) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 798764571..e85c4170d 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -30,6 +30,8 @@ import ( _ "github.com/livekit/protocol/livekit/logger" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -42,7 +44,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// Times are unix seconds. type AgentThread struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -136,13 +137,14 @@ type AgentThread_CreateRequest struct { AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` // When set, create is get-or-create on (agent_name, key). Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - // The subject the thread belongs to, as scoped by an AgentThreadGrant. - Sub string `protobuf:"bytes,3,opt,name=sub,proto3" json:"sub,omitempty"` + // Groups threads, e.g. by user: an application-defined value, typically the + // user's identity. An AgentThreadGrant is scoped by it. + Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // 0 takes the server default. - IdleTtlSeconds int64 `protobuf:"varint,5,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3" json:"idle_ttl_seconds,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Unset takes the server default. + IdleTtl *durationpb.Duration `protobuf:"bytes,5,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentThread_CreateRequest) Reset() { @@ -189,9 +191,9 @@ func (x *AgentThread_CreateRequest) GetKey() string { return "" } -func (x *AgentThread_CreateRequest) GetSub() string { +func (x *AgentThread_CreateRequest) GetSubject() string { if x != nil { - return x.Sub + return x.Subject } return "" } @@ -203,11 +205,11 @@ func (x *AgentThread_CreateRequest) GetAttributes() map[string]string { return nil } -func (x *AgentThread_CreateRequest) GetIdleTtlSeconds() int64 { +func (x *AgentThread_CreateRequest) GetIdleTtl() *durationpb.Duration { if x != nil { - return x.IdleTtlSeconds + return x.IdleTtl } - return 0 + return nil } type AgentThread_CreateResponse struct { @@ -349,7 +351,7 @@ func (*AgentThread_GetRequest_ByKey) isAgentThread_GetRequest_Lookup() {} type AgentThread_ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` - Sub string `protobuf:"bytes,2,opt,name=sub,proto3" json:"sub,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` // Cursor from the previous ListResponse; empty for the first page. Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` @@ -394,9 +396,9 @@ func (x *AgentThread_ListRequest) GetAgentName() string { return "" } -func (x *AgentThread_ListRequest) GetSub() string { +func (x *AgentThread_ListRequest) GetSubject() string { if x != nil { - return x.Sub + return x.Subject } return "" } @@ -472,10 +474,10 @@ type AgentThread_UpdateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` // Merged; an empty value deletes the entry. - Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - IdleTtlSeconds *int64 `protobuf:"varint,3,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3,oneof" json:"idle_ttl_seconds,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + IdleTtl *durationpb.Duration `protobuf:"bytes,3,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentThread_UpdateRequest) Reset() { @@ -522,11 +524,11 @@ func (x *AgentThread_UpdateRequest) GetAttributes() map[string]string { return nil } -func (x *AgentThread_UpdateRequest) GetIdleTtlSeconds() int64 { - if x != nil && x.IdleTtlSeconds != nil { - return *x.IdleTtlSeconds +func (x *AgentThread_UpdateRequest) GetIdleTtl() *durationpb.Duration { + if x != nil { + return x.IdleTtl } - return 0 + return nil } type AgentThread_TouchRequest struct { @@ -575,7 +577,7 @@ func (x *AgentThread_TouchRequest) GetThreadId() string { type AgentThread_TouchResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - ExpiresAt int64 `protobuf:"varint,1,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -610,19 +612,19 @@ func (*AgentThread_TouchResponse) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} } -func (x *AgentThread_TouchResponse) GetExpiresAt() int64 { +func (x *AgentThread_TouchResponse) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } - return 0 + return nil } type AgentThread_AddKeyRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - // 0 keeps the key for the life of the thread. - TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` + // Unset keeps the key for the life of the thread. + Ttl *durationpb.Duration `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"` // Move the key here when another thread of the same agent holds it. Replace bool `protobuf:"varint,4,opt,name=replace,proto3" json:"replace,omitempty"` unknownFields protoimpl.UnknownFields @@ -673,11 +675,11 @@ func (x *AgentThread_AddKeyRequest) GetKey() string { return "" } -func (x *AgentThread_AddKeyRequest) GetTtlSeconds() int64 { +func (x *AgentThread_AddKeyRequest) GetTtl() *durationpb.Duration { if x != nil { - return x.TtlSeconds + return x.Ttl } - return 0 + return nil } func (x *AgentThread_AddKeyRequest) GetReplace() bool { @@ -820,27 +822,27 @@ func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { } // Creates the thread's database if it has none. Idempotent. -type AgentThread_EnsureStateDatabaseRequest struct { +type AgentThread_CreateThreadDatabaseRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AgentThread_EnsureStateDatabaseRequest) Reset() { - *x = AgentThread_EnsureStateDatabaseRequest{} +func (x *AgentThread_CreateThreadDatabaseRequest) Reset() { + *x = AgentThread_CreateThreadDatabaseRequest{} mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentThread_EnsureStateDatabaseRequest) String() string { +func (x *AgentThread_CreateThreadDatabaseRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentThread_EnsureStateDatabaseRequest) ProtoMessage() {} +func (*AgentThread_CreateThreadDatabaseRequest) ProtoMessage() {} -func (x *AgentThread_EnsureStateDatabaseRequest) ProtoReflect() protoreflect.Message { +func (x *AgentThread_CreateThreadDatabaseRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -852,41 +854,42 @@ func (x *AgentThread_EnsureStateDatabaseRequest) ProtoReflect() protoreflect.Mes return mi.MessageOf(x) } -// Deprecated: Use AgentThread_EnsureStateDatabaseRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_EnsureStateDatabaseRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentThread_CreateThreadDatabaseRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_CreateThreadDatabaseRequest) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 13} } -func (x *AgentThread_EnsureStateDatabaseRequest) GetThreadId() string { +func (x *AgentThread_CreateThreadDatabaseRequest) GetThreadId() string { if x != nil { return x.ThreadId } return "" } -// Marks a thread as merged into redirect_to. -type AgentThread_RedirectRequest struct { +// Marks thread_id as merged into another thread, after its items were copied +// there. +type AgentThread_MergeThreadRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - RedirectTo string `protobuf:"bytes,2,opt,name=redirect_to,json=redirectTo,proto3" json:"redirect_to,omitempty"` + Into string `protobuf:"bytes,2,opt,name=into,proto3" json:"into,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AgentThread_RedirectRequest) Reset() { - *x = AgentThread_RedirectRequest{} +func (x *AgentThread_MergeThreadRequest) Reset() { + *x = AgentThread_MergeThreadRequest{} mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentThread_RedirectRequest) String() string { +func (x *AgentThread_MergeThreadRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentThread_RedirectRequest) ProtoMessage() {} +func (*AgentThread_MergeThreadRequest) ProtoMessage() {} -func (x *AgentThread_RedirectRequest) ProtoReflect() protoreflect.Message { +func (x *AgentThread_MergeThreadRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -898,21 +901,21 @@ func (x *AgentThread_RedirectRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AgentThread_RedirectRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_RedirectRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentThread_MergeThreadRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_MergeThreadRequest) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 14} } -func (x *AgentThread_RedirectRequest) GetThreadId() string { +func (x *AgentThread_MergeThreadRequest) GetThreadId() string { if x != nil { return x.ThreadId } return "" } -func (x *AgentThread_RedirectRequest) GetRedirectTo() string { +func (x *AgentThread_MergeThreadRequest) GetInto() string { if x != nil { - return x.RedirectTo + return x.Into } return "" } @@ -922,18 +925,20 @@ type AgentThread_Thread struct { ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` // Keys themselves are never returned. - KeyCount uint32 `protobuf:"varint,3,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` - Sub string `protobuf:"bytes,4,opt,name=sub,proto3" json:"sub,omitempty"` - Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - CreatedAt int64 `protobuf:"varint,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - LastActive int64 `protobuf:"varint,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` - ExpiresAt int64 `protobuf:"varint,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` - IdleTtlSeconds int64 `protobuf:"varint,9,opt,name=idle_ttl_seconds,json=idleTtlSeconds,proto3" json:"idle_ttl_seconds,omitempty"` - // "DB_..."; empty until the first write or EnsureStateDatabase. + KeyCount uint32 `protobuf:"varint,3,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` + Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"` + Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + LastActive *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` + // last_active + idle_ttl; every touch or send pushes it out. The thread is + // deleted once it passes. + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + IdleTtl *durationpb.Duration `protobuf:"bytes,9,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + // "DB_..."; empty until the first write or CreateThreadDatabase. DatabaseId string `protobuf:"bytes,10,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` // Set when merged into another thread; calls on it then fail with // FailedPrecondition. - RedirectTo string `protobuf:"bytes,11,opt,name=redirect_to,json=redirectTo,proto3" json:"redirect_to,omitempty"` + MergedInto string `protobuf:"bytes,11,opt,name=merged_into,json=mergedInto,proto3" json:"merged_into,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -989,9 +994,9 @@ func (x *AgentThread_Thread) GetKeyCount() uint32 { return 0 } -func (x *AgentThread_Thread) GetSub() string { +func (x *AgentThread_Thread) GetSubject() string { if x != nil { - return x.Sub + return x.Subject } return "" } @@ -1003,32 +1008,32 @@ func (x *AgentThread_Thread) GetAttributes() map[string]string { return nil } -func (x *AgentThread_Thread) GetCreatedAt() int64 { +func (x *AgentThread_Thread) GetCreatedAt() *timestamppb.Timestamp { if x != nil { return x.CreatedAt } - return 0 + return nil } -func (x *AgentThread_Thread) GetLastActive() int64 { +func (x *AgentThread_Thread) GetLastActive() *timestamppb.Timestamp { if x != nil { return x.LastActive } - return 0 + return nil } -func (x *AgentThread_Thread) GetExpiresAt() int64 { +func (x *AgentThread_Thread) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } - return 0 + return nil } -func (x *AgentThread_Thread) GetIdleTtlSeconds() int64 { +func (x *AgentThread_Thread) GetIdleTtl() *durationpb.Duration { if x != nil { - return x.IdleTtlSeconds + return x.IdleTtl } - return 0 + return nil } func (x *AgentThread_Thread) GetDatabaseId() string { @@ -1038,9 +1043,9 @@ func (x *AgentThread_Thread) GetDatabaseId() string { return "" } -func (x *AgentThread_Thread) GetRedirectTo() string { +func (x *AgentThread_Thread) GetMergedInto() string { if x != nil { - return x.RedirectTo + return x.MergedInto } return "" } @@ -1049,21 +1054,21 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x14logger/options.proto\"\xfb\x10\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\x81\x12\n" + "\vAgentThread\x1a;\n" + "\bAgentKey\x12\x1d\n" + "\n" + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x1a\x9f\x02\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x1a\xb3\x02\n" + "\rCreateRequest\x12\x1d\n" + "\n" + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x12\x15\n" + - "\x03sub\x18\x03 \x01(\tB\x03\xc0P\x01R\x03sub\x12]\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x1d\n" + + "\asubject\x18\x03 \x01(\tB\x03\xc0P\x01R\asubject\x12]\n" + "\n" + "attributes\x18\x04 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + - "attributes\x12(\n" + - "\x10idle_ttl_seconds\x18\x05 \x01(\x03R\x0eidleTtlSeconds\x1a=\n" + + "attributes\x124\n" + + "\bidle_ttl\x18\x05 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ae\n" + @@ -1074,71 +1079,67 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "GetRequest\x12*\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDH\x00R\bthreadId\x12<\n" + "\x06by_key\x18\x02 \x01(\v2#.livekit.agent.AgentThread.AgentKeyH\x00R\x05byKeyB\b\n" + - "\x06lookup\x1aq\n" + + "\x06lookup\x1ay\n" + "\vListRequest\x12\x1d\n" + "\n" + - "agent_name\x18\x01 \x01(\tR\tagentName\x12\x15\n" + - "\x03sub\x18\x02 \x01(\tB\x03\xc0P\x01R\x03sub\x12\x16\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x1d\n" + + "\asubject\x18\x02 \x01(\tB\x03\xc0P\x01R\asubject\x12\x16\n" + "\x06before\x18\x03 \x01(\tR\x06before\x12\x14\n" + "\x05limit\x18\x04 \x01(\x05R\x05limit\x1ac\n" + "\fListResponse\x12;\n" + "\athreads\x18\x01 \x03(\v2!.livekit.agent.AgentThread.ThreadR\athreads\x12\x16\n" + - "\x06before\x18\x02 \x01(\tR\x06before\x1a\x9b\x02\n" + + "\x06before\x18\x02 \x01(\tR\x06before\x1a\x8d\x02\n" + "\rUpdateRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12]\n" + "\n" + "attributes\x18\x02 \x03(\v28.livekit.agent.AgentThread.UpdateRequest.AttributesEntryB\x03\xc0P\x01R\n" + - "attributes\x12-\n" + - "\x10idle_ttl_seconds\x18\x03 \x01(\x03H\x00R\x0eidleTtlSeconds\x88\x01\x01\x1a=\n" + + "attributes\x124\n" + + "\bidle_ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x13\n" + - "\x11_idle_ttl_seconds\x1a8\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a8\n" + "\fTouchRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a.\n" + - "\rTouchResponse\x12\x1d\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aJ\n" + + "\rTouchResponse\x129\n" + "\n" + - "expires_at\x18\x01 \x01(\x03R\texpiresAt\x1a\x86\x01\n" + + "expires_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x1a\x92\x01\n" + "\rAddKeyRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x12\x1f\n" + - "\vttl_seconds\x18\x03 \x01(\x03R\n" + - "ttlSeconds\x12\x18\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12+\n" + + "\x03ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x12\x18\n" + "\areplace\x18\x04 \x01(\bR\areplace\x1aN\n" + "\x10RemoveKeyRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + "\x03key\x18\x02 \x01(\tR\x03key\x1a9\n" + "\rDeleteRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + - "\x0eDeleteResponse\x1aF\n" + - "\x1aEnsureStateDatabaseRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1ak\n" + - "\x0fRedirectRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12.\n" + - "\vredirect_to\x18\x02 \x01(\tB\r\xbaP\n" + - "redirectToR\n" + - "redirectTo\x1a\xe7\x03\n" + + "\x0eDeleteResponse\x1aG\n" + + "\x1bCreateThreadDatabaseRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aR\n" + + "\x12MergeThreadRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x12\n" + + "\x04into\x18\x02 \x01(\tR\x04into\x1a\xcf\x04\n" + "\x06Thread\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x1d\n" + "\n" + "agent_name\x18\x02 \x01(\tR\tagentName\x12\x1b\n" + - "\tkey_count\x18\x03 \x01(\rR\bkeyCount\x12\x15\n" + - "\x03sub\x18\x04 \x01(\tB\x03\xc0P\x01R\x03sub\x12V\n" + + "\tkey_count\x18\x03 \x01(\rR\bkeyCount\x12\x1d\n" + + "\asubject\x18\x04 \x01(\tB\x03\xc0P\x01R\asubject\x12V\n" + "\n" + "attributes\x18\x05 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + - "attributes\x12\x1d\n" + + "attributes\x129\n" + "\n" + - "created_at\x18\x06 \x01(\x03R\tcreatedAt\x12\x1f\n" + - "\vlast_active\x18\a \x01(\x03R\n" + - "lastActive\x12\x1d\n" + + "created_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + + "\vlast_active\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "lastActive\x129\n" + "\n" + - "expires_at\x18\b \x01(\x03R\texpiresAt\x12(\n" + - "\x10idle_ttl_seconds\x18\t \x01(\x03R\x0eidleTtlSeconds\x12\x1f\n" + + "expires_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x124\n" + + "\bidle_ttl\x18\t \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x12\x1f\n" + "\vdatabase_id\x18\n" + " \x01(\tR\n" + "databaseId\x12\x1f\n" + - "\vredirect_to\x18\v \x01(\tR\n" + - "redirectTo\x1a=\n" + + "\vmerged_into\x18\v \x01(\tR\n" + + "mergedInto\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" @@ -1157,39 +1158,49 @@ func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_agent_livekit_agent_thread_proto_goTypes = []any{ - (*AgentThread)(nil), // 0: livekit.agent.AgentThread - (*AgentThread_AgentKey)(nil), // 1: livekit.agent.AgentThread.AgentKey - (*AgentThread_CreateRequest)(nil), // 2: livekit.agent.AgentThread.CreateRequest - (*AgentThread_CreateResponse)(nil), // 3: livekit.agent.AgentThread.CreateResponse - (*AgentThread_GetRequest)(nil), // 4: livekit.agent.AgentThread.GetRequest - (*AgentThread_ListRequest)(nil), // 5: livekit.agent.AgentThread.ListRequest - (*AgentThread_ListResponse)(nil), // 6: livekit.agent.AgentThread.ListResponse - (*AgentThread_UpdateRequest)(nil), // 7: livekit.agent.AgentThread.UpdateRequest - (*AgentThread_TouchRequest)(nil), // 8: livekit.agent.AgentThread.TouchRequest - (*AgentThread_TouchResponse)(nil), // 9: livekit.agent.AgentThread.TouchResponse - (*AgentThread_AddKeyRequest)(nil), // 10: livekit.agent.AgentThread.AddKeyRequest - (*AgentThread_RemoveKeyRequest)(nil), // 11: livekit.agent.AgentThread.RemoveKeyRequest - (*AgentThread_DeleteRequest)(nil), // 12: livekit.agent.AgentThread.DeleteRequest - (*AgentThread_DeleteResponse)(nil), // 13: livekit.agent.AgentThread.DeleteResponse - (*AgentThread_EnsureStateDatabaseRequest)(nil), // 14: livekit.agent.AgentThread.EnsureStateDatabaseRequest - (*AgentThread_RedirectRequest)(nil), // 15: livekit.agent.AgentThread.RedirectRequest - (*AgentThread_Thread)(nil), // 16: livekit.agent.AgentThread.Thread - nil, // 17: livekit.agent.AgentThread.CreateRequest.AttributesEntry - nil, // 18: livekit.agent.AgentThread.UpdateRequest.AttributesEntry - nil, // 19: livekit.agent.AgentThread.Thread.AttributesEntry + (*AgentThread)(nil), // 0: livekit.agent.AgentThread + (*AgentThread_AgentKey)(nil), // 1: livekit.agent.AgentThread.AgentKey + (*AgentThread_CreateRequest)(nil), // 2: livekit.agent.AgentThread.CreateRequest + (*AgentThread_CreateResponse)(nil), // 3: livekit.agent.AgentThread.CreateResponse + (*AgentThread_GetRequest)(nil), // 4: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 5: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 6: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 7: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_TouchRequest)(nil), // 8: livekit.agent.AgentThread.TouchRequest + (*AgentThread_TouchResponse)(nil), // 9: livekit.agent.AgentThread.TouchResponse + (*AgentThread_AddKeyRequest)(nil), // 10: livekit.agent.AgentThread.AddKeyRequest + (*AgentThread_RemoveKeyRequest)(nil), // 11: livekit.agent.AgentThread.RemoveKeyRequest + (*AgentThread_DeleteRequest)(nil), // 12: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 13: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_CreateThreadDatabaseRequest)(nil), // 14: livekit.agent.AgentThread.CreateThreadDatabaseRequest + (*AgentThread_MergeThreadRequest)(nil), // 15: livekit.agent.AgentThread.MergeThreadRequest + (*AgentThread_Thread)(nil), // 16: livekit.agent.AgentThread.Thread + nil, // 17: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 18: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 19: livekit.agent.AgentThread.Thread.AttributesEntry + (*durationpb.Duration)(nil), // 20: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ 17, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry - 16, // 1: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread - 1, // 2: livekit.agent.AgentThread.GetRequest.by_key:type_name -> livekit.agent.AgentThread.AgentKey - 16, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread - 18, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry - 19, // 5: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 20, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration + 16, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread + 1, // 3: livekit.agent.AgentThread.GetRequest.by_key:type_name -> livekit.agent.AgentThread.AgentKey + 16, // 4: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread + 18, // 5: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry + 20, // 6: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration + 21, // 7: livekit.agent.AgentThread.TouchResponse.expires_at:type_name -> google.protobuf.Timestamp + 20, // 8: livekit.agent.AgentThread.AddKeyRequest.ttl:type_name -> google.protobuf.Duration + 19, // 9: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry + 21, // 10: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp + 21, // 11: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp + 21, // 12: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp + 20, // 13: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_agent_livekit_agent_thread_proto_init() } @@ -1201,7 +1212,6 @@ func file_agent_livekit_agent_thread_proto_init() { (*AgentThread_GetRequest_ThreadId)(nil), (*AgentThread_GetRequest_ByKey)(nil), } - file_agent_livekit_agent_thread_proto_msgTypes[7].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index fae84af93..88a3bc423 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: livekit_agentdb.proto package livekit @@ -27,6 +27,8 @@ package livekit import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -177,9 +179,9 @@ func (*AgentDB) Descriptor() ([]byte, []int) { type AgentDB_CreateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Unset: never expires, and expires_at is then 0 everywhere. + // Unset: never expires, and expires_at is then unset everywhere. // Otherwise clamped to the server cap, and never extended. - TtlSeconds int64 `protobuf:"varint,2,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` + Ttl *durationpb.Duration `protobuf:"bytes,2,opt,name=ttl,proto3" json:"ttl,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -214,17 +216,17 @@ func (*AgentDB_CreateRequest) Descriptor() ([]byte, []int) { return file_livekit_agentdb_proto_rawDescGZIP(), []int{0, 0} } -func (x *AgentDB_CreateRequest) GetTtlSeconds() int64 { +func (x *AgentDB_CreateRequest) GetTtl() *durationpb.Duration { if x != nil { - return x.TtlSeconds + return x.Ttl } - return 0 + return nil } type AgentDB_CreateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` // "DB_..." - ExpiresAt int64 `protobuf:"varint,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -266,11 +268,11 @@ func (x *AgentDB_CreateResponse) GetDatabaseId() string { return "" } -func (x *AgentDB_CreateResponse) GetExpiresAt() int64 { +func (x *AgentDB_CreateResponse) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } - return 0 + return nil } type AgentDB_GetRequest struct { @@ -426,8 +428,8 @@ type AgentDB_AgentDatabase struct { state protoimpl.MessageState `protogen:"open.v1"` DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` Region string `protobuf:"bytes,2,opt,name=region,proto3" json:"region,omitempty"` - CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - ExpiresAt int64 `protobuf:"varint,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` Tip int64 `protobuf:"varint,5,opt,name=tip,proto3" json:"tip,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -477,18 +479,18 @@ func (x *AgentDB_AgentDatabase) GetRegion() string { return "" } -func (x *AgentDB_AgentDatabase) GetCreatedAt() int64 { +func (x *AgentDB_AgentDatabase) GetCreatedAt() *timestamppb.Timestamp { if x != nil { return x.CreatedAt } - return 0 + return nil } -func (x *AgentDB_AgentDatabase) GetExpiresAt() int64 { +func (x *AgentDB_AgentDatabase) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } - return 0 + return nil } func (x *AgentDB_AgentDatabase) GetTip() int64 { @@ -625,7 +627,7 @@ func (x *AgentDB_DumpRequest) GetDatabaseId() string { type AgentDB_DumpResponse struct { state protoimpl.MessageState `protogen:"open.v1"` DownloadUrl string `protobuf:"bytes,1,opt,name=download_url,json=downloadUrl,proto3" json:"download_url,omitempty"` // pre-authenticated object-storage URL - ExpiresAt int64 `protobuf:"varint,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` // of the URL, not the database + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` // of the URL, not the database Tip int64 `protobuf:"varint,3,opt,name=tip,proto3" json:"tip,omitempty"` // what the dump is consistent at unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -668,11 +670,11 @@ func (x *AgentDB_DumpResponse) GetDownloadUrl() string { return "" } -func (x *AgentDB_DumpResponse) GetExpiresAt() int64 { +func (x *AgentDB_DumpResponse) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } - return 0 + return nil } func (x *AgentDB_DumpResponse) GetTip() int64 { @@ -2101,16 +2103,15 @@ var File_livekit_agentdb_proto protoreflect.FileDescriptor const file_livekit_agentdb_proto_rawDesc = "" + "\n" + - "\x15livekit_agentdb.proto\x12\alivekit\"\xce\x19\n" + - "\aAgentDB\x1a>\n" + - "\rCreateRequest\x12\x1f\n" + - "\vttl_seconds\x18\x02 \x01(\x03R\n" + - "ttlSecondsJ\x04\b\x01\x10\x02R\x06region\x1aP\n" + + "\x15livekit_agentdb.proto\x12\alivekit\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xca\x1a\n" + + "\aAgentDB\x1aJ\n" + + "\rCreateRequest\x12+\n" + + "\x03ttl\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\x03ttlJ\x04\b\x01\x10\x02R\x06region\x1al\n" + "\x0eCreateResponse\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + - "databaseId\x12\x1d\n" + + "databaseId\x129\n" + "\n" + - "expires_at\x18\x02 \x01(\x03R\texpiresAt\x1a-\n" + + "expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x1a-\n" + "\n" + "GetRequest\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + @@ -2121,15 +2122,15 @@ const file_livekit_agentdb_proto_rawDesc = "" + "page_token\x18\x02 \x01(\tR\tpageToken\x1at\n" + "\fListResponse\x12<\n" + "\tdatabases\x18\x01 \x03(\v2\x1e.livekit.AgentDB.AgentDatabaseR\tdatabases\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x1a\x98\x01\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x1a\xd0\x01\n" + "\rAgentDatabase\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + "databaseId\x12\x16\n" + - "\x06region\x18\x02 \x01(\tR\x06region\x12\x1d\n" + + "\x06region\x18\x02 \x01(\tR\x06region\x129\n" + "\n" + - "created_at\x18\x03 \x01(\x03R\tcreatedAt\x12\x1d\n" + + "created_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + "\n" + - "expires_at\x18\x04 \x01(\x03R\texpiresAt\x12\x10\n" + + "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x10\n" + "\x03tip\x18\x05 \x01(\x03R\x03tip\x1a0\n" + "\rDeleteRequest\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + @@ -2137,11 +2138,11 @@ const file_livekit_agentdb_proto_rawDesc = "" + "\x0eDeleteResponse\x1a.\n" + "\vDumpRequest\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + - "databaseId\x1ab\n" + + "databaseId\x1a~\n" + "\fDumpResponse\x12!\n" + - "\fdownload_url\x18\x01 \x01(\tR\vdownloadUrl\x12\x1d\n" + + "\fdownload_url\x18\x01 \x01(\tR\vdownloadUrl\x129\n" + "\n" + - "expires_at\x18\x02 \x01(\x03R\texpiresAt\x12\x10\n" + + "expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x10\n" + "\x03tip\x18\x03 \x01(\x03R\x03tip\x1a\xcd\x13\n" + "\x04Wire\x1a\xe0\x04\n" + "\rClientMessage\x12\x1d\n" + @@ -2293,35 +2294,42 @@ var file_livekit_agentdb_proto_goTypes = []any{ (*AgentDB_Wire_ExecResult)(nil), // 31: livekit.AgentDB.Wire.ExecResult (*AgentDB_Wire_Done)(nil), // 32: livekit.AgentDB.Wire.Done (*AgentDB_Wire_Error)(nil), // 33: livekit.AgentDB.Wire.Error + (*durationpb.Duration)(nil), // 34: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 35: google.protobuf.Timestamp } var file_livekit_agentdb_proto_depIdxs = []int32{ - 8, // 0: livekit.AgentDB.ListResponse.databases:type_name -> livekit.AgentDB.AgentDatabase - 26, // 1: livekit.AgentDB.Wire.ClientMessage.hello:type_name -> livekit.AgentDB.Wire.Hello - 17, // 2: livekit.AgentDB.Wire.ClientMessage.exec:type_name -> livekit.AgentDB.Wire.Statement - 17, // 3: livekit.AgentDB.Wire.ClientMessage.query:type_name -> livekit.AgentDB.Wire.Statement - 18, // 4: livekit.AgentDB.Wire.ClientMessage.batch:type_name -> livekit.AgentDB.Wire.Batch - 19, // 5: livekit.AgentDB.Wire.ClientMessage.begin:type_name -> livekit.AgentDB.Wire.Begin - 20, // 6: livekit.AgentDB.Wire.ClientMessage.commit:type_name -> livekit.AgentDB.Wire.Commit - 21, // 7: livekit.AgentDB.Wire.ClientMessage.rollback:type_name -> livekit.AgentDB.Wire.Rollback - 22, // 8: livekit.AgentDB.Wire.ClientMessage.cancel:type_name -> livekit.AgentDB.Wire.Cancel - 23, // 9: livekit.AgentDB.Wire.ClientMessage.credit:type_name -> livekit.AgentDB.Wire.Credit - 24, // 10: livekit.AgentDB.Wire.ClientMessage.ping:type_name -> livekit.AgentDB.Wire.Ping - 27, // 11: livekit.AgentDB.Wire.ServerMessage.hello_ok:type_name -> livekit.AgentDB.Wire.HelloOk - 28, // 12: livekit.AgentDB.Wire.ServerMessage.columns:type_name -> livekit.AgentDB.Wire.Columns - 30, // 13: livekit.AgentDB.Wire.ServerMessage.column_batch:type_name -> livekit.AgentDB.Wire.ColumnBatch - 31, // 14: livekit.AgentDB.Wire.ServerMessage.exec_result:type_name -> livekit.AgentDB.Wire.ExecResult - 32, // 15: livekit.AgentDB.Wire.ServerMessage.done:type_name -> livekit.AgentDB.Wire.Done - 33, // 16: livekit.AgentDB.Wire.ServerMessage.error:type_name -> livekit.AgentDB.Wire.Error - 25, // 17: livekit.AgentDB.Wire.ServerMessage.pong:type_name -> livekit.AgentDB.Wire.Pong - 16, // 18: livekit.AgentDB.Wire.Statement.params:type_name -> livekit.AgentDB.Wire.Value - 0, // 19: livekit.AgentDB.Wire.Statement.lang:type_name -> livekit.AgentDB.Wire.QueryLang - 17, // 20: livekit.AgentDB.Wire.Batch.statements:type_name -> livekit.AgentDB.Wire.Statement - 29, // 21: livekit.AgentDB.Wire.ColumnBatch.columns:type_name -> livekit.AgentDB.Wire.Column - 22, // [22:22] is the sub-list for method output_type - 22, // [22:22] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 34, // 0: livekit.AgentDB.CreateRequest.ttl:type_name -> google.protobuf.Duration + 35, // 1: livekit.AgentDB.CreateResponse.expires_at:type_name -> google.protobuf.Timestamp + 8, // 2: livekit.AgentDB.ListResponse.databases:type_name -> livekit.AgentDB.AgentDatabase + 35, // 3: livekit.AgentDB.AgentDatabase.created_at:type_name -> google.protobuf.Timestamp + 35, // 4: livekit.AgentDB.AgentDatabase.expires_at:type_name -> google.protobuf.Timestamp + 35, // 5: livekit.AgentDB.DumpResponse.expires_at:type_name -> google.protobuf.Timestamp + 26, // 6: livekit.AgentDB.Wire.ClientMessage.hello:type_name -> livekit.AgentDB.Wire.Hello + 17, // 7: livekit.AgentDB.Wire.ClientMessage.exec:type_name -> livekit.AgentDB.Wire.Statement + 17, // 8: livekit.AgentDB.Wire.ClientMessage.query:type_name -> livekit.AgentDB.Wire.Statement + 18, // 9: livekit.AgentDB.Wire.ClientMessage.batch:type_name -> livekit.AgentDB.Wire.Batch + 19, // 10: livekit.AgentDB.Wire.ClientMessage.begin:type_name -> livekit.AgentDB.Wire.Begin + 20, // 11: livekit.AgentDB.Wire.ClientMessage.commit:type_name -> livekit.AgentDB.Wire.Commit + 21, // 12: livekit.AgentDB.Wire.ClientMessage.rollback:type_name -> livekit.AgentDB.Wire.Rollback + 22, // 13: livekit.AgentDB.Wire.ClientMessage.cancel:type_name -> livekit.AgentDB.Wire.Cancel + 23, // 14: livekit.AgentDB.Wire.ClientMessage.credit:type_name -> livekit.AgentDB.Wire.Credit + 24, // 15: livekit.AgentDB.Wire.ClientMessage.ping:type_name -> livekit.AgentDB.Wire.Ping + 27, // 16: livekit.AgentDB.Wire.ServerMessage.hello_ok:type_name -> livekit.AgentDB.Wire.HelloOk + 28, // 17: livekit.AgentDB.Wire.ServerMessage.columns:type_name -> livekit.AgentDB.Wire.Columns + 30, // 18: livekit.AgentDB.Wire.ServerMessage.column_batch:type_name -> livekit.AgentDB.Wire.ColumnBatch + 31, // 19: livekit.AgentDB.Wire.ServerMessage.exec_result:type_name -> livekit.AgentDB.Wire.ExecResult + 32, // 20: livekit.AgentDB.Wire.ServerMessage.done:type_name -> livekit.AgentDB.Wire.Done + 33, // 21: livekit.AgentDB.Wire.ServerMessage.error:type_name -> livekit.AgentDB.Wire.Error + 25, // 22: livekit.AgentDB.Wire.ServerMessage.pong:type_name -> livekit.AgentDB.Wire.Pong + 16, // 23: livekit.AgentDB.Wire.Statement.params:type_name -> livekit.AgentDB.Wire.Value + 0, // 24: livekit.AgentDB.Wire.Statement.lang:type_name -> livekit.AgentDB.Wire.QueryLang + 17, // 25: livekit.AgentDB.Wire.Batch.statements:type_name -> livekit.AgentDB.Wire.Statement + 29, // 26: livekit.AgentDB.Wire.ColumnBatch.columns:type_name -> livekit.AgentDB.Wire.Column + 27, // [27:27] is the sub-list for method output_type + 27, // [27:27] is the sub-list for method input_type + 27, // [27:27] is the sub-list for extension type_name + 27, // [27:27] is the sub-list for extension extendee + 0, // [0:27] is the sub-list for field type_name } func init() { file_livekit_agentdb_proto_init() } diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 68f1f71c7..f6c819fbf 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -24,9 +24,10 @@ package livekit.agent; option go_package = "github.com/livekit/protocol/livekit/agent"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; import "logger/options.proto"; -// Times are unix seconds. message AgentThread { message AgentKey { string agent_name = 1; @@ -37,11 +38,12 @@ message AgentThread { string agent_name = 1; // When set, create is get-or-create on (agent_name, key). string key = 2; - // The subject the thread belongs to, as scoped by an AgentThreadGrant. - string sub = 3 [(logger.sensitivity) = SENSITIVITY_PII]; + // Groups threads, e.g. by user: an application-defined value, typically the + // user's identity. An AgentThreadGrant is scoped by it. + string subject = 3 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; - // 0 takes the server default. - int64 idle_ttl_seconds = 5; + // Unset takes the server default. + google.protobuf.Duration idle_ttl = 5; } message CreateResponse { @@ -60,7 +62,7 @@ message AgentThread { // The threads of one subject under one agent, newest first. message ListRequest { string agent_name = 1; - string sub = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + string subject = 2 [(logger.sensitivity) = SENSITIVITY_PII]; // Cursor from the previous ListResponse; empty for the first page. string before = 3; int32 limit = 4; @@ -76,7 +78,7 @@ message AgentThread { string thread_id = 1 [(logger.name) = "threadID"]; // Merged; an empty value deletes the entry. map attributes = 2 [(logger.sensitivity) = SENSITIVITY_PII]; - optional int64 idle_ttl_seconds = 3; + google.protobuf.Duration idle_ttl = 3; } message TouchRequest { @@ -84,14 +86,14 @@ message AgentThread { } message TouchResponse { - int64 expires_at = 1; + google.protobuf.Timestamp expires_at = 1; } message AddKeyRequest { string thread_id = 1 [(logger.name) = "threadID"]; string key = 2; - // 0 keeps the key for the life of the thread. - int64 ttl_seconds = 3; + // Unset keeps the key for the life of the thread. + google.protobuf.Duration ttl = 3; // Move the key here when another thread of the same agent holds it. bool replace = 4; } @@ -108,14 +110,15 @@ message AgentThread { message DeleteResponse {} // Creates the thread's database if it has none. Idempotent. - message EnsureStateDatabaseRequest { + message CreateThreadDatabaseRequest { string thread_id = 1 [(logger.name) = "threadID"]; } - // Marks a thread as merged into redirect_to. - message RedirectRequest { + // Marks thread_id as merged into another thread, after its items were copied + // there. + message MergeThreadRequest { string thread_id = 1 [(logger.name) = "threadID"]; - string redirect_to = 2 [(logger.name) = "redirectTo"]; + string into = 2; } message Thread { @@ -123,16 +126,18 @@ message AgentThread { string agent_name = 2; // Keys themselves are never returned. uint32 key_count = 3; - string sub = 4 [(logger.sensitivity) = SENSITIVITY_PII]; + string subject = 4 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 5 [(logger.sensitivity) = SENSITIVITY_PII]; - int64 created_at = 6; - int64 last_active = 7; - int64 expires_at = 8; - int64 idle_ttl_seconds = 9; - // "DB_..."; empty until the first write or EnsureStateDatabase. + google.protobuf.Timestamp created_at = 6; + google.protobuf.Timestamp last_active = 7; + // last_active + idle_ttl; every touch or send pushes it out. The thread is + // deleted once it passes. + google.protobuf.Timestamp expires_at = 8; + google.protobuf.Duration idle_ttl = 9; + // "DB_..."; empty until the first write or CreateThreadDatabase. string database_id = 10; // Set when merged into another thread; calls on it then fail with // FailedPrecondition. - string redirect_to = 11; + string merged_into = 11; } } diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index 8824e9805..2f2ee8333 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -24,6 +24,9 @@ option go_package = "github.com/livekit/protocol/livekit"; option csharp_namespace = "LiveKit.Proto"; option ruby_package = "LiveKit::Proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + // Nested so that names like Value and Ping need not be unique across the // livekit package. `tip` throughout is the latest durable commit sequence. message AgentDB { @@ -31,14 +34,14 @@ message AgentDB { // A database lives in its project's data region. reserved 1; reserved "region"; - // Unset: never expires, and expires_at is then 0 everywhere. + // Unset: never expires, and expires_at is then unset everywhere. // Otherwise clamped to the server cap, and never extended. - int64 ttl_seconds = 2; + google.protobuf.Duration ttl = 2; } message CreateResponse { string database_id = 1; // "DB_..." - int64 expires_at = 2; + google.protobuf.Timestamp expires_at = 2; } message GetRequest { @@ -59,8 +62,8 @@ message AgentDB { message AgentDatabase { string database_id = 1; string region = 2; - int64 created_at = 3; - int64 expires_at = 4; + google.protobuf.Timestamp created_at = 3; + google.protobuf.Timestamp expires_at = 4; int64 tip = 5; } @@ -76,7 +79,7 @@ message AgentDB { message DumpResponse { string download_url = 1; // pre-authenticated object-storage URL - int64 expires_at = 2; // of the URL, not the database + google.protobuf.Timestamp expires_at = 2; // of the URL, not the database int64 tip = 3; // what the dump is consistent at } From f3a28aa0876d179f66897663229f7dbd732c882c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 03:25:12 +0000 Subject: [PATCH 10/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- livekit/livekit_agentdb.pb.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index e85c4170d..41a0b46ec 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 88a3bc423..32b41791d 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: livekit_agentdb.proto package livekit From 6448cf4b77949d2dd3809606364761b2e78ee7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 21:07:00 -0700 Subject: [PATCH 11/56] agent threads: the grant is scope, ids and flags; subject is scope The thread grant covers thread data only, by scope or id across the project's agents: no agent name and no endpoints, so it never opens a non-public endpoint (agentEndpoint does that). Send is write. The thread's subject is its scope. --- .changeset/agent-threads.md | 2 +- auth/accesstoken_test.go | 4 +- auth/grants.go | 40 +++++---------- auth/grants_test.go | 59 +++++++--------------- livekit/agent/livekit_agent_thread.pb.go | 46 ++++++++--------- protobufs/agent/livekit_agent_thread.proto | 12 ++--- 6 files changed, 63 insertions(+), 100 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index 991dc2688..05d04077c 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim (optionally limited to some of the agent's endpoints), and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim (threads by scope or id, with list/create/write/read/delete), and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is reserved, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/auth/accesstoken_test.go b/auth/accesstoken_test.go index 092acb1d2..d4574c9de 100644 --- a/auth/accesstoken_test.go +++ b/auth/accesstoken_test.go @@ -249,7 +249,7 @@ func TestAgentThreadGrantRoundTrip(t *testing.T) { t.Parallel() apiKey, secret := apiKeypair() - grant := &AgentThreadGrant{AgentName: "my-agent", Subject: "user-1", ThreadIDs: []string{"AT_1"}, Read: true, Send: true} + grant := &AgentThreadGrant{Scope: "user-1", ThreadIDs: []string{"AT_1"}, Read: true, Write: true} raw, err := NewAccessToken(apiKey, secret). SetAgentThreadGrant(grant). SetValidFor(time.Minute). @@ -259,7 +259,7 @@ func TestAgentThreadGrantRoundTrip(t *testing.T) { // the claim keys are camelCase claims := decodeClaims(t, raw) require.Contains(t, claims, `"agentThread"`) - require.Contains(t, claims, `"agentName"`) + require.Contains(t, claims, `"scope"`) require.Contains(t, claims, `"threadIds"`) v, err := ParseAPIToken(raw) diff --git a/auth/grants.go b/auth/grants.go index d5fa2b09c..5ded02bc8 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -715,42 +715,31 @@ func (s *AgentEndpointGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // ------------------------------------------------------------------ -// AgentThreadGrant scopes a token to one agent's threads, selected by subject -// or by id, with per-action flags. Endpoints limits which of the agent's -// endpoints the holder may call; empty allows all of them. +// AgentThreadGrant gives a token access to threads: those whose scope equals +// Scope, or those listed in ThreadIDs, across the project's agents. It grants +// thread data only; which endpoints a token may call is agentEndpoint's. type AgentThreadGrant struct { - AgentName string `json:"agentName"` - Subject string `json:"subject,omitempty"` + Scope string `json:"scope,omitempty"` ThreadIDs []string `json:"threadIds,omitempty"` - Endpoints []string `json:"endpoints,omitempty"` List bool `json:"list,omitempty"` Create bool `json:"create,omitempty"` - Send bool `json:"send,omitempty"` + Write bool `json:"write,omitempty"` Read bool `json:"read,omitempty"` Delete bool `json:"delete,omitempty"` } -// Covers reports whether a thread of agentName is in scope, by subject or by -// id. An empty Subject or thread id never matches; the caller checks the flags. -func (s *AgentThreadGrant) Covers(agentName, subject, threadID string) bool { - if s == nil || s.AgentName == "" || s.AgentName != agentName { +// Covers reports whether a thread is in the grant, by scope or by id. An empty +// Scope or thread id never matches; the caller checks the flags. +func (s *AgentThreadGrant) Covers(scope, threadID string) bool { + if s == nil { return false } - if s.Subject != "" && s.Subject == subject { + if s.Scope != "" && s.Scope == scope { return true } return threadID != "" && slices.Contains(s.ThreadIDs, threadID) } -// AllowsEndpoint reports whether the holder may call endpoint of the grant's -// agent. -func (s *AgentThreadGrant) AllowsEndpoint(endpoint string) bool { - if s == nil { - return false - } - return len(s.Endpoints) == 0 || slices.Contains(s.Endpoints, endpoint) -} - func (s *AgentThreadGrant) Clone() *AgentThreadGrant { if s == nil { return nil @@ -760,9 +749,6 @@ func (s *AgentThreadGrant) Clone() *AgentThreadGrant { if len(s.ThreadIDs) > 0 { clone.ThreadIDs = append([]string{}, s.ThreadIDs...) } - if len(s.Endpoints) > 0 { - clone.Endpoints = append([]string{}, s.Endpoints...) - } return &clone } @@ -772,13 +758,11 @@ func (s *AgentThreadGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { return nil } - e.AddString("AgentName", s.AgentName) - e.AddString("Subject", s.Subject) + e.AddString("Scope", s.Scope) zap.Strings("ThreadIDs", s.ThreadIDs).AddTo(e) - zap.Strings("Endpoints", s.Endpoints).AddTo(e) e.AddBool("List", s.List) e.AddBool("Create", s.Create) - e.AddBool("Send", s.Send) + e.AddBool("Write", s.Write) e.AddBool("Read", s.Read) e.AddBool("Delete", s.Delete) return nil diff --git a/auth/grants_test.go b/auth/grants_test.go index 1067bed78..7ff29e810 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -532,63 +532,42 @@ func TestAgentEndpointGrantCloneIndependent(t *testing.T) { func TestAgentThreadGrantCovers(t *testing.T) { t.Parallel() - bySubject := &AgentThreadGrant{AgentName: "a", Subject: "u1"} - byID := &AgentThreadGrant{AgentName: "a", ThreadIDs: []string{"AT_1", "AT_2"}} - both := &AgentThreadGrant{AgentName: "a", Subject: "u1", ThreadIDs: []string{"AT_1"}} - + byScope := &AgentThreadGrant{Scope: "u1"} + byID := &AgentThreadGrant{ThreadIDs: []string{"AT_1", "AT_2"}} cases := []struct { - name string - grant *AgentThreadGrant - agentName string - subject string - threadID string - want bool + name string + grant *AgentThreadGrant + scope string + threadID string + want bool }{ - {"nil grant", nil, "a", "u1", "AT_1", false}, - {"no agent", &AgentThreadGrant{Subject: "u1"}, "", "u1", "AT_1", false}, - {"neither subject nor ids", &AgentThreadGrant{AgentName: "a"}, "a", "", "AT_1", false}, - {"subject match", bySubject, "a", "u1", "AT_9", true}, - {"subject mismatch", bySubject, "a", "u2", "AT_9", false}, - {"subject empty on thread", bySubject, "a", "", "AT_9", false}, - {"agent mismatch", bySubject, "b", "u1", "AT_9", false}, - {"agent case differs", bySubject, "A", "u1", "AT_9", false}, - {"id listed", byID, "a", "u2", "AT_2", true}, - {"id not listed", byID, "a", "u2", "AT_3", false}, - {"id empty", byID, "a", "u2", "", false}, - {"id listed, agent mismatch", byID, "b", "u2", "AT_2", false}, - {"both: subject", both, "a", "u1", "AT_9", true}, - {"both: id", both, "a", "u2", "AT_1", true}, - {"both: neither", both, "a", "u2", "AT_9", false}, + {"nil grant", nil, "u1", "AT_1", false}, + {"neither scope nor ids", &AgentThreadGrant{}, "", "AT_1", false}, + {"scope match", byScope, "u1", "AT_9", true}, + {"scope mismatch", byScope, "u2", "AT_9", false}, + {"scope empty on thread", byScope, "", "AT_9", false}, + {"id match", byID, "", "AT_2", true}, + {"id mismatch", byID, "", "AT_3", false}, + {"empty id", byID, "", "", false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, c.grant.Covers(c.agentName, c.subject, c.threadID)) + require.Equal(t, c.want, c.grant.Covers(c.scope, c.threadID)) }) } } -func TestAgentThreadGrantAllowsEndpoint(t *testing.T) { - t.Parallel() - - all := &AgentThreadGrant{AgentName: "a"} - chat := &AgentThreadGrant{AgentName: "a", Endpoints: []string{"chat"}} - require.True(t, all.AllowsEndpoint("voice")) - require.True(t, chat.AllowsEndpoint("chat")) - require.False(t, chat.AllowsEndpoint("voice")) - require.False(t, (*AgentThreadGrant)(nil).AllowsEndpoint("chat")) -} - func TestAgentThreadGrantCloneIndependent(t *testing.T) { t.Parallel() - grants := &ClaimGrants{AgentThread: &AgentThreadGrant{AgentName: "a", Subject: "u1", ThreadIDs: []string{"AT_1"}, Send: true}} + grants := &ClaimGrants{AgentThread: &AgentThreadGrant{Scope: "u1", ThreadIDs: []string{"AT_1"}, Write: true}} clone := grants.Clone() require.NotSame(t, grants.AgentThread, clone.AgentThread) require.True(t, reflect.DeepEqual(grants.AgentThread, clone.AgentThread)) - clone.AgentThread.Send = false + clone.AgentThread.Write = false clone.AgentThread.ThreadIDs[0] = "AT_2" clone.AgentThread.ThreadIDs = append(clone.AgentThread.ThreadIDs, "AT_3") - require.True(t, grants.AgentThread.Send) + require.True(t, grants.AgentThread.Write) require.Equal(t, []string{"AT_1"}, grants.AgentThread.ThreadIDs) } diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 41a0b46ec..7dd037c34 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -14,14 +14,14 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id; keys (such as a phone -// number) find it again, at most one thread per key and agent. Holds a subject, +// number) find it again, at most one thread per key and agent. Holds a scope, // attributes, an idle TTL and the AgentDB database with its state. Served by // cloud-protocol's AgentThreads. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -138,8 +138,8 @@ type AgentThread_CreateRequest struct { // When set, create is get-or-create on (agent_name, key). Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // Groups threads, e.g. by user: an application-defined value, typically the - // user's identity. An AgentThreadGrant is scoped by it. - Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` + // user's identity. An AgentThreadGrant is scoped by it, across agents. + Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Unset takes the server default. IdleTtl *durationpb.Duration `protobuf:"bytes,5,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` @@ -191,9 +191,9 @@ func (x *AgentThread_CreateRequest) GetKey() string { return "" } -func (x *AgentThread_CreateRequest) GetSubject() string { +func (x *AgentThread_CreateRequest) GetScope() string { if x != nil { - return x.Subject + return x.Scope } return "" } @@ -347,11 +347,11 @@ func (*AgentThread_GetRequest_ThreadId) isAgentThread_GetRequest_Lookup() {} func (*AgentThread_GetRequest_ByKey) isAgentThread_GetRequest_Lookup() {} -// The threads of one subject under one agent, newest first. +// The threads of one scope under one agent, newest first. type AgentThread_ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` // Cursor from the previous ListResponse; empty for the first page. Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` @@ -396,9 +396,9 @@ func (x *AgentThread_ListRequest) GetAgentName() string { return "" } -func (x *AgentThread_ListRequest) GetSubject() string { +func (x *AgentThread_ListRequest) GetScope() string { if x != nil { - return x.Subject + return x.Scope } return "" } @@ -926,7 +926,7 @@ type AgentThread_Thread struct { AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` // Keys themselves are never returned. KeyCount uint32 `protobuf:"varint,3,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` - Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"` + Scope string `protobuf:"bytes,4,opt,name=scope,proto3" json:"scope,omitempty"` Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastActive *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` @@ -994,9 +994,9 @@ func (x *AgentThread_Thread) GetKeyCount() uint32 { return 0 } -func (x *AgentThread_Thread) GetSubject() string { +func (x *AgentThread_Thread) GetScope() string { if x != nil { - return x.Subject + return x.Scope } return "" } @@ -1054,17 +1054,17 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\x81\x12\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\xf5\x11\n" + "\vAgentThread\x1a;\n" + "\bAgentKey\x12\x1d\n" + "\n" + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x1a\xb3\x02\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x1a\xaf\x02\n" + "\rCreateRequest\x12\x1d\n" + "\n" + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x12\x1d\n" + - "\asubject\x18\x03 \x01(\tB\x03\xc0P\x01R\asubject\x12]\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + + "\x05scope\x18\x03 \x01(\tB\x03\xc0P\x01R\x05scope\x12]\n" + "\n" + "attributes\x18\x04 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + "attributes\x124\n" + @@ -1079,11 +1079,11 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "GetRequest\x12*\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDH\x00R\bthreadId\x12<\n" + "\x06by_key\x18\x02 \x01(\v2#.livekit.agent.AgentThread.AgentKeyH\x00R\x05byKeyB\b\n" + - "\x06lookup\x1ay\n" + + "\x06lookup\x1au\n" + "\vListRequest\x12\x1d\n" + "\n" + - "agent_name\x18\x01 \x01(\tR\tagentName\x12\x1d\n" + - "\asubject\x18\x02 \x01(\tB\x03\xc0P\x01R\asubject\x12\x16\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x19\n" + + "\x05scope\x18\x02 \x01(\tB\x03\xc0P\x01R\x05scope\x12\x16\n" + "\x06before\x18\x03 \x01(\tR\x06before\x12\x14\n" + "\x05limit\x18\x04 \x01(\x05R\x05limit\x1ac\n" + "\fListResponse\x12;\n" + @@ -1118,13 +1118,13 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aR\n" + "\x12MergeThreadRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x12\n" + - "\x04into\x18\x02 \x01(\tR\x04into\x1a\xcf\x04\n" + + "\x04into\x18\x02 \x01(\tR\x04into\x1a\xcb\x04\n" + "\x06Thread\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x1d\n" + "\n" + "agent_name\x18\x02 \x01(\tR\tagentName\x12\x1b\n" + - "\tkey_count\x18\x03 \x01(\rR\bkeyCount\x12\x1d\n" + - "\asubject\x18\x04 \x01(\tB\x03\xc0P\x01R\asubject\x12V\n" + + "\tkey_count\x18\x03 \x01(\rR\bkeyCount\x12\x19\n" + + "\x05scope\x18\x04 \x01(\tB\x03\xc0P\x01R\x05scope\x12V\n" + "\n" + "attributes\x18\x05 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + "attributes\x129\n" + diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index f6c819fbf..40c53db4d 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -14,7 +14,7 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id; keys (such as a phone -// number) find it again, at most one thread per key and agent. Holds a subject, +// number) find it again, at most one thread per key and agent. Holds a scope, // attributes, an idle TTL and the AgentDB database with its state. Served by // cloud-protocol's AgentThreads. @@ -39,8 +39,8 @@ message AgentThread { // When set, create is get-or-create on (agent_name, key). string key = 2; // Groups threads, e.g. by user: an application-defined value, typically the - // user's identity. An AgentThreadGrant is scoped by it. - string subject = 3 [(logger.sensitivity) = SENSITIVITY_PII]; + // user's identity. An AgentThreadGrant is scoped by it, across agents. + string scope = 3 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; // Unset takes the server default. google.protobuf.Duration idle_ttl = 5; @@ -59,10 +59,10 @@ message AgentThread { } } - // The threads of one subject under one agent, newest first. + // The threads of one scope under one agent, newest first. message ListRequest { string agent_name = 1; - string subject = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + string scope = 2 [(logger.sensitivity) = SENSITIVITY_PII]; // Cursor from the previous ListResponse; empty for the first page. string before = 3; int32 limit = 4; @@ -126,7 +126,7 @@ message AgentThread { string agent_name = 2; // Keys themselves are never returned. uint32 key_count = 3; - string subject = 4 [(logger.sensitivity) = SENSITIVITY_PII]; + string scope = 4 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 5 [(logger.sensitivity) = SENSITIVITY_PII]; google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp last_active = 7; From 17ef238240195842f856c3803872afc6a7ca31fd Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 04:07:45 +0000 Subject: [PATCH 12/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 7dd037c34..847e128b0 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 291214876edd31d40979c58b22535fcb33260f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 21:08:02 -0700 Subject: [PATCH 13/56] auth: AgentThreadGrant.Allows, like AgentEndpointGrant.Allows --- auth/grants.go | 4 ++-- auth/grants_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/auth/grants.go b/auth/grants.go index 5ded02bc8..3da0c1a20 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -728,9 +728,9 @@ type AgentThreadGrant struct { Delete bool `json:"delete,omitempty"` } -// Covers reports whether a thread is in the grant, by scope or by id. An empty +// Allows reports whether a thread is in the grant, by scope or by id. An empty // Scope or thread id never matches; the caller checks the flags. -func (s *AgentThreadGrant) Covers(scope, threadID string) bool { +func (s *AgentThreadGrant) Allows(scope, threadID string) bool { if s == nil { return false } diff --git a/auth/grants_test.go b/auth/grants_test.go index 7ff29e810..c74cf779c 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -529,7 +529,7 @@ func TestAgentEndpointGrantCloneIndependent(t *testing.T) { require.Equal(t, "a", grants.AgentEndpoint.AgentName) } -func TestAgentThreadGrantCovers(t *testing.T) { +func TestAgentThreadGrantAllows(t *testing.T) { t.Parallel() byScope := &AgentThreadGrant{Scope: "u1"} @@ -552,7 +552,7 @@ func TestAgentThreadGrantCovers(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, c.grant.Covers(c.scope, c.threadID)) + require.Equal(t, c.want, c.grant.Allows(c.scope, c.threadID)) }) } } From 5e6b136da9ea4b4fdb5d065e14e4ab9f89f751f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 21:13:44 -0700 Subject: [PATCH 14/56] auth: AgentThreadGrant.CanCreate, CanList, CanRead, CanWrite, CanDelete One check per action. Per-thread checks take the thread, since a grant matches by scope or by id; create needs only the grant, which creates in its own scope. --- auth/grants.go | 38 ++++++++++++++++++++++++++++------ auth/grants_test.go | 50 +++++++++++++++++++++++---------------------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/auth/grants.go b/auth/grants.go index 3da0c1a20..12fe65847 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -24,6 +24,7 @@ import ( "golang.org/x/exp/slices" "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/livekit/agent" "github.com/livekit/protocol/logger" "github.com/livekit/protocol/utils" "github.com/livekit/protocol/utils/protojson" @@ -728,16 +729,41 @@ type AgentThreadGrant struct { Delete bool `json:"delete,omitempty"` } -// Allows reports whether a thread is in the grant, by scope or by id. An empty -// Scope or thread id never matches; the caller checks the flags. -func (s *AgentThreadGrant) Allows(scope, threadID string) bool { - if s == nil { +// CanCreate reports whether the holder may create threads. They are created in +// the grant's scope, so a grant without one cannot create. +func (s *AgentThreadGrant) CanCreate() bool { + return s != nil && s.Create && s.Scope != "" +} + +// CanList reports whether the holder may list the threads of scope. +func (s *AgentThreadGrant) CanList(scope string) bool { + return s != nil && s.List && s.Scope != "" && s.Scope == scope +} + +// CanRead reports whether the holder may read thread. +func (s *AgentThreadGrant) CanRead(thread *agent.AgentThread_Thread) bool { + return s != nil && s.Read && s.includes(thread) +} + +// CanWrite reports whether the holder may send to thread. +func (s *AgentThreadGrant) CanWrite(thread *agent.AgentThread_Thread) bool { + return s != nil && s.Write && s.includes(thread) +} + +// CanDelete reports whether the holder may delete thread. +func (s *AgentThreadGrant) CanDelete(thread *agent.AgentThread_Thread) bool { + return s != nil && s.Delete && s.includes(thread) +} + +// includes reports whether thread is in the grant, by scope or by id. +func (s *AgentThreadGrant) includes(thread *agent.AgentThread_Thread) bool { + if thread == nil { return false } - if s.Scope != "" && s.Scope == scope { + if s.Scope != "" && s.Scope == thread.Scope { return true } - return threadID != "" && slices.Contains(s.ThreadIDs, threadID) + return thread.ThreadId != "" && slices.Contains(s.ThreadIDs, thread.ThreadId) } func (s *AgentThreadGrant) Clone() *AgentThreadGrant { diff --git a/auth/grants_test.go b/auth/grants_test.go index c74cf779c..1d730d659 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/require" "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/livekit/agent" ) func TestGrants(t *testing.T) { @@ -529,32 +530,33 @@ func TestAgentEndpointGrantCloneIndependent(t *testing.T) { require.Equal(t, "a", grants.AgentEndpoint.AgentName) } -func TestAgentThreadGrantAllows(t *testing.T) { +func TestAgentThreadGrantChecks(t *testing.T) { t.Parallel() - byScope := &AgentThreadGrant{Scope: "u1"} - byID := &AgentThreadGrant{ThreadIDs: []string{"AT_1", "AT_2"}} - cases := []struct { - name string - grant *AgentThreadGrant - scope string - threadID string - want bool - }{ - {"nil grant", nil, "u1", "AT_1", false}, - {"neither scope nor ids", &AgentThreadGrant{}, "", "AT_1", false}, - {"scope match", byScope, "u1", "AT_9", true}, - {"scope mismatch", byScope, "u2", "AT_9", false}, - {"scope empty on thread", byScope, "", "AT_9", false}, - {"id match", byID, "", "AT_2", true}, - {"id mismatch", byID, "", "AT_3", false}, - {"empty id", byID, "", "", false}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, c.grant.Allows(c.scope, c.threadID)) - }) - } + mine := &agent.AgentThread_Thread{ThreadId: "AT_1", Scope: "u1"} + shared := &agent.AgentThread_Thread{ThreadId: "AT_2", Scope: "u2"} + other := &agent.AgentThread_Thread{ThreadId: "AT_3", Scope: "u2"} + unscoped := &agent.AgentThread_Thread{ThreadId: "AT_4"} + + g := &AgentThreadGrant{Scope: "u1", ThreadIDs: []string{"AT_2"}, List: true, Create: true, Read: true} + require.True(t, g.CanRead(mine)) + require.True(t, g.CanRead(shared)) + require.False(t, g.CanRead(other)) + require.False(t, g.CanRead(unscoped)) + require.False(t, g.CanWrite(mine)) + require.False(t, g.CanDelete(mine)) + require.True(t, g.CanList("u1")) + require.False(t, g.CanList("u2")) + require.False(t, g.CanList("")) + require.True(t, g.CanCreate()) + + idsOnly := &AgentThreadGrant{ThreadIDs: []string{"AT_4"}, Create: true, Write: true} + require.True(t, idsOnly.CanWrite(unscoped)) + require.False(t, idsOnly.CanCreate()) + + var none *AgentThreadGrant + require.False(t, none.CanRead(mine)) + require.False(t, none.CanCreate()) } func TestAgentThreadGrantCloneIndependent(t *testing.T) { From f46c27b25ccdc82802abe7ac9f3aa1455f72df85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 21:23:49 -0700 Subject: [PATCH 15/56] agent threads: no agent_name; keys and listing are per project A conversation is not owned by one agent: any agent of the project can pick it up. Keys resolve per project, listing is by scope, and GetRequest looks up by thread id or key. --- livekit/agent/livekit_agent_thread.pb.go | 326 ++++++++------------- protobufs/agent/livekit_agent_thread.proto | 23 +- 2 files changed, 128 insertions(+), 221 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 847e128b0..44f60b38b 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -14,14 +14,14 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id; keys (such as a phone -// number) find it again, at most one thread per key and agent. Holds a scope, +// number) find it again, at most one thread per key in a project. Holds a scope, // attributes, an idle TTL and the AgentDB database with its state. Served by // cloud-protocol's AgentThreads. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -80,65 +80,12 @@ func (*AgentThread) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0} } -type AgentThread_AgentKey struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_AgentKey) Reset() { - *x = AgentThread_AgentKey{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_AgentKey) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_AgentKey) ProtoMessage() {} - -func (x *AgentThread_AgentKey) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_AgentKey.ProtoReflect.Descriptor instead. -func (*AgentThread_AgentKey) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 0} -} - -func (x *AgentThread_AgentKey) GetAgentName() string { - if x != nil { - return x.AgentName - } - return "" -} - -func (x *AgentThread_AgentKey) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - type AgentThread_CreateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` - // When set, create is get-or-create on (agent_name, key). + state protoimpl.MessageState `protogen:"open.v1"` + // When set, create is get-or-create on key. Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // Groups threads, e.g. by user: an application-defined value, typically the - // user's identity. An AgentThreadGrant is scoped by it, across agents. + // user's identity. An AgentThreadGrant is scoped by it. Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Unset takes the server default. @@ -149,7 +96,7 @@ type AgentThread_CreateRequest struct { func (x *AgentThread_CreateRequest) Reset() { *x = AgentThread_CreateRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -161,7 +108,7 @@ func (x *AgentThread_CreateRequest) String() string { func (*AgentThread_CreateRequest) ProtoMessage() {} func (x *AgentThread_CreateRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -174,14 +121,7 @@ func (x *AgentThread_CreateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_CreateRequest.ProtoReflect.Descriptor instead. func (*AgentThread_CreateRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 1} -} - -func (x *AgentThread_CreateRequest) GetAgentName() string { - if x != nil { - return x.AgentName - } - return "" + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 0} } func (x *AgentThread_CreateRequest) GetKey() string { @@ -223,7 +163,7 @@ type AgentThread_CreateResponse struct { func (x *AgentThread_CreateResponse) Reset() { *x = AgentThread_CreateResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -235,7 +175,7 @@ func (x *AgentThread_CreateResponse) String() string { func (*AgentThread_CreateResponse) ProtoMessage() {} func (x *AgentThread_CreateResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -248,7 +188,7 @@ func (x *AgentThread_CreateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_CreateResponse.ProtoReflect.Descriptor instead. func (*AgentThread_CreateResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 2} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 1} } func (x *AgentThread_CreateResponse) GetThread() *AgentThread_Thread { @@ -270,7 +210,7 @@ type AgentThread_GetRequest struct { // Types that are valid to be assigned to Lookup: // // *AgentThread_GetRequest_ThreadId - // *AgentThread_GetRequest_ByKey + // *AgentThread_GetRequest_Key Lookup isAgentThread_GetRequest_Lookup `protobuf_oneof:"lookup"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -278,7 +218,7 @@ type AgentThread_GetRequest struct { func (x *AgentThread_GetRequest) Reset() { *x = AgentThread_GetRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -290,7 +230,7 @@ func (x *AgentThread_GetRequest) String() string { func (*AgentThread_GetRequest) ProtoMessage() {} func (x *AgentThread_GetRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -303,7 +243,7 @@ func (x *AgentThread_GetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_GetRequest.ProtoReflect.Descriptor instead. func (*AgentThread_GetRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 3} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 2} } func (x *AgentThread_GetRequest) GetLookup() isAgentThread_GetRequest_Lookup { @@ -322,13 +262,13 @@ func (x *AgentThread_GetRequest) GetThreadId() string { return "" } -func (x *AgentThread_GetRequest) GetByKey() *AgentThread_AgentKey { +func (x *AgentThread_GetRequest) GetKey() string { if x != nil { - if x, ok := x.Lookup.(*AgentThread_GetRequest_ByKey); ok { - return x.ByKey + if x, ok := x.Lookup.(*AgentThread_GetRequest_Key); ok { + return x.Key } } - return nil + return "" } type isAgentThread_GetRequest_Lookup interface { @@ -339,19 +279,18 @@ type AgentThread_GetRequest_ThreadId struct { ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3,oneof"` } -type AgentThread_GetRequest_ByKey struct { - ByKey *AgentThread_AgentKey `protobuf:"bytes,2,opt,name=by_key,json=byKey,proto3,oneof"` +type AgentThread_GetRequest_Key struct { + Key string `protobuf:"bytes,2,opt,name=key,proto3,oneof"` } func (*AgentThread_GetRequest_ThreadId) isAgentThread_GetRequest_Lookup() {} -func (*AgentThread_GetRequest_ByKey) isAgentThread_GetRequest_Lookup() {} +func (*AgentThread_GetRequest_Key) isAgentThread_GetRequest_Lookup() {} -// The threads of one scope under one agent, newest first. +// The threads of one scope, newest first. type AgentThread_ListRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` - Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` // Cursor from the previous ListResponse; empty for the first page. Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` @@ -361,7 +300,7 @@ type AgentThread_ListRequest struct { func (x *AgentThread_ListRequest) Reset() { *x = AgentThread_ListRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -373,7 +312,7 @@ func (x *AgentThread_ListRequest) String() string { func (*AgentThread_ListRequest) ProtoMessage() {} func (x *AgentThread_ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -386,14 +325,7 @@ func (x *AgentThread_ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_ListRequest.ProtoReflect.Descriptor instead. func (*AgentThread_ListRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 4} -} - -func (x *AgentThread_ListRequest) GetAgentName() string { - if x != nil { - return x.AgentName - } - return "" + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 3} } func (x *AgentThread_ListRequest) GetScope() string { @@ -428,7 +360,7 @@ type AgentThread_ListResponse struct { func (x *AgentThread_ListResponse) Reset() { *x = AgentThread_ListResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -440,7 +372,7 @@ func (x *AgentThread_ListResponse) String() string { func (*AgentThread_ListResponse) ProtoMessage() {} func (x *AgentThread_ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -453,7 +385,7 @@ func (x *AgentThread_ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_ListResponse.ProtoReflect.Descriptor instead. func (*AgentThread_ListResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 5} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 4} } func (x *AgentThread_ListResponse) GetThreads() []*AgentThread_Thread { @@ -482,7 +414,7 @@ type AgentThread_UpdateRequest struct { func (x *AgentThread_UpdateRequest) Reset() { *x = AgentThread_UpdateRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -494,7 +426,7 @@ func (x *AgentThread_UpdateRequest) String() string { func (*AgentThread_UpdateRequest) ProtoMessage() {} func (x *AgentThread_UpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -507,7 +439,7 @@ func (x *AgentThread_UpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_UpdateRequest.ProtoReflect.Descriptor instead. func (*AgentThread_UpdateRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 5} } func (x *AgentThread_UpdateRequest) GetThreadId() string { @@ -540,7 +472,7 @@ type AgentThread_TouchRequest struct { func (x *AgentThread_TouchRequest) Reset() { *x = AgentThread_TouchRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -552,7 +484,7 @@ func (x *AgentThread_TouchRequest) String() string { func (*AgentThread_TouchRequest) ProtoMessage() {} func (x *AgentThread_TouchRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -565,7 +497,7 @@ func (x *AgentThread_TouchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_TouchRequest.ProtoReflect.Descriptor instead. func (*AgentThread_TouchRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} } func (x *AgentThread_TouchRequest) GetThreadId() string { @@ -584,7 +516,7 @@ type AgentThread_TouchResponse struct { func (x *AgentThread_TouchResponse) Reset() { *x = AgentThread_TouchResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -596,7 +528,7 @@ func (x *AgentThread_TouchResponse) String() string { func (*AgentThread_TouchResponse) ProtoMessage() {} func (x *AgentThread_TouchResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -609,7 +541,7 @@ func (x *AgentThread_TouchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_TouchResponse.ProtoReflect.Descriptor instead. func (*AgentThread_TouchResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} } func (x *AgentThread_TouchResponse) GetExpiresAt() *timestamppb.Timestamp { @@ -625,7 +557,7 @@ type AgentThread_AddKeyRequest struct { Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // Unset keeps the key for the life of the thread. Ttl *durationpb.Duration `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"` - // Move the key here when another thread of the same agent holds it. + // Move the key here when another thread holds it. Replace bool `protobuf:"varint,4,opt,name=replace,proto3" json:"replace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -633,7 +565,7 @@ type AgentThread_AddKeyRequest struct { func (x *AgentThread_AddKeyRequest) Reset() { *x = AgentThread_AddKeyRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -645,7 +577,7 @@ func (x *AgentThread_AddKeyRequest) String() string { func (*AgentThread_AddKeyRequest) ProtoMessage() {} func (x *AgentThread_AddKeyRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -658,7 +590,7 @@ func (x *AgentThread_AddKeyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_AddKeyRequest.ProtoReflect.Descriptor instead. func (*AgentThread_AddKeyRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} } func (x *AgentThread_AddKeyRequest) GetThreadId() string { @@ -699,7 +631,7 @@ type AgentThread_RemoveKeyRequest struct { func (x *AgentThread_RemoveKeyRequest) Reset() { *x = AgentThread_RemoveKeyRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -711,7 +643,7 @@ func (x *AgentThread_RemoveKeyRequest) String() string { func (*AgentThread_RemoveKeyRequest) ProtoMessage() {} func (x *AgentThread_RemoveKeyRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -724,7 +656,7 @@ func (x *AgentThread_RemoveKeyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_RemoveKeyRequest.ProtoReflect.Descriptor instead. func (*AgentThread_RemoveKeyRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 10} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} } func (x *AgentThread_RemoveKeyRequest) GetThreadId() string { @@ -750,7 +682,7 @@ type AgentThread_DeleteRequest struct { func (x *AgentThread_DeleteRequest) Reset() { *x = AgentThread_DeleteRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -762,7 +694,7 @@ func (x *AgentThread_DeleteRequest) String() string { func (*AgentThread_DeleteRequest) ProtoMessage() {} func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -775,7 +707,7 @@ func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_DeleteRequest.ProtoReflect.Descriptor instead. func (*AgentThread_DeleteRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 11} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 10} } func (x *AgentThread_DeleteRequest) GetThreadId() string { @@ -793,7 +725,7 @@ type AgentThread_DeleteResponse struct { func (x *AgentThread_DeleteResponse) Reset() { *x = AgentThread_DeleteResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -805,7 +737,7 @@ func (x *AgentThread_DeleteResponse) String() string { func (*AgentThread_DeleteResponse) ProtoMessage() {} func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -818,7 +750,7 @@ func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_DeleteResponse.ProtoReflect.Descriptor instead. func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 12} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 11} } // Creates the thread's database if it has none. Idempotent. @@ -831,7 +763,7 @@ type AgentThread_CreateThreadDatabaseRequest struct { func (x *AgentThread_CreateThreadDatabaseRequest) Reset() { *x = AgentThread_CreateThreadDatabaseRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -843,7 +775,7 @@ func (x *AgentThread_CreateThreadDatabaseRequest) String() string { func (*AgentThread_CreateThreadDatabaseRequest) ProtoMessage() {} func (x *AgentThread_CreateThreadDatabaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -856,7 +788,7 @@ func (x *AgentThread_CreateThreadDatabaseRequest) ProtoReflect() protoreflect.Me // Deprecated: Use AgentThread_CreateThreadDatabaseRequest.ProtoReflect.Descriptor instead. func (*AgentThread_CreateThreadDatabaseRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 13} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 12} } func (x *AgentThread_CreateThreadDatabaseRequest) GetThreadId() string { @@ -878,7 +810,7 @@ type AgentThread_MergeThreadRequest struct { func (x *AgentThread_MergeThreadRequest) Reset() { *x = AgentThread_MergeThreadRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -890,7 +822,7 @@ func (x *AgentThread_MergeThreadRequest) String() string { func (*AgentThread_MergeThreadRequest) ProtoMessage() {} func (x *AgentThread_MergeThreadRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -903,7 +835,7 @@ func (x *AgentThread_MergeThreadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_MergeThreadRequest.ProtoReflect.Descriptor instead. func (*AgentThread_MergeThreadRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 14} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 13} } func (x *AgentThread_MergeThreadRequest) GetThreadId() string { @@ -921,9 +853,8 @@ func (x *AgentThread_MergeThreadRequest) GetInto() string { } type AgentThread_Thread struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` // Keys themselves are never returned. KeyCount uint32 `protobuf:"varint,3,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` Scope string `protobuf:"bytes,4,opt,name=scope,proto3" json:"scope,omitempty"` @@ -945,7 +876,7 @@ type AgentThread_Thread struct { func (x *AgentThread_Thread) Reset() { *x = AgentThread_Thread{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -957,7 +888,7 @@ func (x *AgentThread_Thread) String() string { func (*AgentThread_Thread) ProtoMessage() {} func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -970,7 +901,7 @@ func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_Thread.ProtoReflect.Descriptor instead. func (*AgentThread_Thread) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 15} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 14} } func (x *AgentThread_Thread) GetThreadId() string { @@ -980,13 +911,6 @@ func (x *AgentThread_Thread) GetThreadId() string { return "" } -func (x *AgentThread_Thread) GetAgentName() string { - if x != nil { - return x.AgentName - } - return "" -} - func (x *AgentThread_Thread) GetKeyCount() uint32 { if x != nil { return x.KeyCount @@ -1054,15 +978,9 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\xf5\x11\n" + - "\vAgentThread\x1a;\n" + - "\bAgentKey\x12\x1d\n" + - "\n" + - "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x1a\xaf\x02\n" + - "\rCreateRequest\x12\x1d\n" + - "\n" + - "agent_name\x18\x01 \x01(\tR\tagentName\x12\x10\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\xc2\x10\n" + + "\vAgentThread\x1a\x96\x02\n" + + "\rCreateRequest\x12\x10\n" + "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + "\x05scope\x18\x03 \x01(\tB\x03\xc0P\x01R\x05scope\x12]\n" + "\n" + @@ -1071,21 +989,19 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\bidle_ttl\x18\x05 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ae\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02\x1ae\n" + "\x0eCreateResponse\x129\n" + "\x06thread\x18\x01 \x01(\v2!.livekit.agent.AgentThread.ThreadR\x06thread\x12\x18\n" + - "\acreated\x18\x02 \x01(\bR\acreated\x1a\x80\x01\n" + + "\acreated\x18\x02 \x01(\bR\acreated\x1aV\n" + "\n" + "GetRequest\x12*\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDH\x00R\bthreadId\x12<\n" + - "\x06by_key\x18\x02 \x01(\v2#.livekit.agent.AgentThread.AgentKeyH\x00R\x05byKeyB\b\n" + - "\x06lookup\x1au\n" + - "\vListRequest\x12\x1d\n" + - "\n" + - "agent_name\x18\x01 \x01(\tR\tagentName\x12\x19\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDH\x00R\bthreadId\x12\x12\n" + + "\x03key\x18\x02 \x01(\tH\x00R\x03keyB\b\n" + + "\x06lookup\x1a\\\n" + + "\vListRequest\x12\x19\n" + "\x05scope\x18\x02 \x01(\tB\x03\xc0P\x01R\x05scope\x12\x16\n" + "\x06before\x18\x03 \x01(\tR\x06before\x12\x14\n" + - "\x05limit\x18\x04 \x01(\x05R\x05limit\x1ac\n" + + "\x05limit\x18\x04 \x01(\x05R\x05limitJ\x04\b\x01\x10\x02\x1ac\n" + "\fListResponse\x12;\n" + "\athreads\x18\x01 \x03(\v2!.livekit.agent.AgentThread.ThreadR\athreads\x12\x16\n" + "\x06before\x18\x02 \x01(\tR\x06before\x1a\x8d\x02\n" + @@ -1118,11 +1034,9 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aR\n" + "\x12MergeThreadRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x12\n" + - "\x04into\x18\x02 \x01(\tR\x04into\x1a\xcb\x04\n" + + "\x04into\x18\x02 \x01(\tR\x04into\x1a\xb2\x04\n" + "\x06Thread\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x1d\n" + - "\n" + - "agent_name\x18\x02 \x01(\tR\tagentName\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x1b\n" + "\tkey_count\x18\x03 \x01(\rR\bkeyCount\x12\x19\n" + "\x05scope\x18\x04 \x01(\tB\x03\xc0P\x01R\x05scope\x12V\n" + "\n" + @@ -1142,7 +1056,7 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "mergedInto\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x02\x10\x03B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" var ( file_agent_livekit_agent_thread_proto_rawDescOnce sync.Once @@ -1156,51 +1070,49 @@ func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { return file_agent_livekit_agent_thread_proto_rawDescData } -var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_agent_livekit_agent_thread_proto_goTypes = []any{ (*AgentThread)(nil), // 0: livekit.agent.AgentThread - (*AgentThread_AgentKey)(nil), // 1: livekit.agent.AgentThread.AgentKey - (*AgentThread_CreateRequest)(nil), // 2: livekit.agent.AgentThread.CreateRequest - (*AgentThread_CreateResponse)(nil), // 3: livekit.agent.AgentThread.CreateResponse - (*AgentThread_GetRequest)(nil), // 4: livekit.agent.AgentThread.GetRequest - (*AgentThread_ListRequest)(nil), // 5: livekit.agent.AgentThread.ListRequest - (*AgentThread_ListResponse)(nil), // 6: livekit.agent.AgentThread.ListResponse - (*AgentThread_UpdateRequest)(nil), // 7: livekit.agent.AgentThread.UpdateRequest - (*AgentThread_TouchRequest)(nil), // 8: livekit.agent.AgentThread.TouchRequest - (*AgentThread_TouchResponse)(nil), // 9: livekit.agent.AgentThread.TouchResponse - (*AgentThread_AddKeyRequest)(nil), // 10: livekit.agent.AgentThread.AddKeyRequest - (*AgentThread_RemoveKeyRequest)(nil), // 11: livekit.agent.AgentThread.RemoveKeyRequest - (*AgentThread_DeleteRequest)(nil), // 12: livekit.agent.AgentThread.DeleteRequest - (*AgentThread_DeleteResponse)(nil), // 13: livekit.agent.AgentThread.DeleteResponse - (*AgentThread_CreateThreadDatabaseRequest)(nil), // 14: livekit.agent.AgentThread.CreateThreadDatabaseRequest - (*AgentThread_MergeThreadRequest)(nil), // 15: livekit.agent.AgentThread.MergeThreadRequest - (*AgentThread_Thread)(nil), // 16: livekit.agent.AgentThread.Thread - nil, // 17: livekit.agent.AgentThread.CreateRequest.AttributesEntry - nil, // 18: livekit.agent.AgentThread.UpdateRequest.AttributesEntry - nil, // 19: livekit.agent.AgentThread.Thread.AttributesEntry - (*durationpb.Duration)(nil), // 20: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp + (*AgentThread_CreateRequest)(nil), // 1: livekit.agent.AgentThread.CreateRequest + (*AgentThread_CreateResponse)(nil), // 2: livekit.agent.AgentThread.CreateResponse + (*AgentThread_GetRequest)(nil), // 3: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 4: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 5: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 6: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_TouchRequest)(nil), // 7: livekit.agent.AgentThread.TouchRequest + (*AgentThread_TouchResponse)(nil), // 8: livekit.agent.AgentThread.TouchResponse + (*AgentThread_AddKeyRequest)(nil), // 9: livekit.agent.AgentThread.AddKeyRequest + (*AgentThread_RemoveKeyRequest)(nil), // 10: livekit.agent.AgentThread.RemoveKeyRequest + (*AgentThread_DeleteRequest)(nil), // 11: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 12: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_CreateThreadDatabaseRequest)(nil), // 13: livekit.agent.AgentThread.CreateThreadDatabaseRequest + (*AgentThread_MergeThreadRequest)(nil), // 14: livekit.agent.AgentThread.MergeThreadRequest + (*AgentThread_Thread)(nil), // 15: livekit.agent.AgentThread.Thread + nil, // 16: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 17: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 18: livekit.agent.AgentThread.Thread.AttributesEntry + (*durationpb.Duration)(nil), // 19: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ - 17, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry - 20, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration - 16, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread - 1, // 3: livekit.agent.AgentThread.GetRequest.by_key:type_name -> livekit.agent.AgentThread.AgentKey - 16, // 4: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread - 18, // 5: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry - 20, // 6: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration - 21, // 7: livekit.agent.AgentThread.TouchResponse.expires_at:type_name -> google.protobuf.Timestamp - 20, // 8: livekit.agent.AgentThread.AddKeyRequest.ttl:type_name -> google.protobuf.Duration - 19, // 9: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry - 21, // 10: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp - 21, // 11: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp - 21, // 12: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp - 20, // 13: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration - 14, // [14:14] is the sub-list for method output_type - 14, // [14:14] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 16, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry + 19, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration + 15, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread + 15, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread + 17, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry + 19, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration + 20, // 6: livekit.agent.AgentThread.TouchResponse.expires_at:type_name -> google.protobuf.Timestamp + 19, // 7: livekit.agent.AgentThread.AddKeyRequest.ttl:type_name -> google.protobuf.Duration + 18, // 8: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry + 20, // 9: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp + 20, // 10: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp + 20, // 11: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp + 19, // 12: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_agent_livekit_agent_thread_proto_init() } @@ -1208,9 +1120,9 @@ func file_agent_livekit_agent_thread_proto_init() { if File_agent_livekit_agent_thread_proto != nil { return } - file_agent_livekit_agent_thread_proto_msgTypes[4].OneofWrappers = []any{ + file_agent_livekit_agent_thread_proto_msgTypes[3].OneofWrappers = []any{ (*AgentThread_GetRequest_ThreadId)(nil), - (*AgentThread_GetRequest_ByKey)(nil), + (*AgentThread_GetRequest_Key)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1218,7 +1130,7 @@ func file_agent_livekit_agent_thread_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_livekit_agent_thread_proto_rawDesc), len(file_agent_livekit_agent_thread_proto_rawDesc)), NumEnums: 0, - NumMessages: 20, + NumMessages: 19, NumExtensions: 0, NumServices: 0, }, diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 40c53db4d..ed5de7fd8 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -14,7 +14,7 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id; keys (such as a phone -// number) find it again, at most one thread per key and agent. Holds a scope, +// number) find it again, at most one thread per key in a project. Holds a scope, // attributes, an idle TTL and the AgentDB database with its state. Served by // cloud-protocol's AgentThreads. @@ -29,17 +29,12 @@ import "google/protobuf/timestamp.proto"; import "logger/options.proto"; message AgentThread { - message AgentKey { - string agent_name = 1; - string key = 2; - } - message CreateRequest { - string agent_name = 1; - // When set, create is get-or-create on (agent_name, key). + reserved 1; + // When set, create is get-or-create on key. string key = 2; // Groups threads, e.g. by user: an application-defined value, typically the - // user's identity. An AgentThreadGrant is scoped by it, across agents. + // user's identity. An AgentThreadGrant is scoped by it. string scope = 3 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; // Unset takes the server default. @@ -55,13 +50,13 @@ message AgentThread { message GetRequest { oneof lookup { string thread_id = 1 [(logger.name) = "threadID"]; - AgentKey by_key = 2; + string key = 2; } } - // The threads of one scope under one agent, newest first. + // The threads of one scope, newest first. message ListRequest { - string agent_name = 1; + reserved 1; string scope = 2 [(logger.sensitivity) = SENSITIVITY_PII]; // Cursor from the previous ListResponse; empty for the first page. string before = 3; @@ -94,7 +89,7 @@ message AgentThread { string key = 2; // Unset keeps the key for the life of the thread. google.protobuf.Duration ttl = 3; - // Move the key here when another thread of the same agent holds it. + // Move the key here when another thread holds it. bool replace = 4; } @@ -123,7 +118,7 @@ message AgentThread { message Thread { string thread_id = 1 [(logger.name) = "threadID"]; - string agent_name = 2; + reserved 2; // Keys themselves are never returned. uint32 key_count = 3; string scope = 4 [(logger.sensitivity) = SENSITIVITY_PII]; From 78649938a760ad731e078cd09f3217644561ac01 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 04:24:34 +0000 Subject: [PATCH 16/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 44f60b38b..572e810f7 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 2345bef9f11744ea29c8f4acfa623adb6522c6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 7 Oct 2026 22:31:10 -0700 Subject: [PATCH 17/56] agent threads: one internal key, database created with the thread, no touch Keys are internal (LiveKit phone numbers): one per thread, set at create. Removes AddKey, RemoveKey, Touch and CreateThreadDatabase; an update counts as activity and the database comes with the thread. --- livekit/agent/livekit_agent_thread.pb.go | 404 ++++----------------- protobufs/agent/livekit_agent_thread.proto | 41 +-- 2 files changed, 72 insertions(+), 373 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 572e810f7..384ee3488 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -82,7 +82,8 @@ func (*AgentThread) Descriptor() ([]byte, []int) { type AgentThread_CreateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // When set, create is get-or-create on key. + // Internal: when set, create is get-or-create on key (e.g. a LiveKit phone + // number). Not exposed by the public API. Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // Groups threads, e.g. by user: an application-defined value, typically the // user's identity. An AgentThreadGrant is scoped by it. @@ -407,7 +408,7 @@ type AgentThread_UpdateRequest struct { ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` // Merged; an empty value deletes the entry. Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - IdleTtl *durationpb.Duration `protobuf:"bytes,3,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + IdleTtl *durationpb.Duration `protobuf:"bytes,3,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` // Every update counts as activity and pushes expires_at out by idle_ttl. unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -463,216 +464,6 @@ func (x *AgentThread_UpdateRequest) GetIdleTtl() *durationpb.Duration { return nil } -type AgentThread_TouchRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_TouchRequest) Reset() { - *x = AgentThread_TouchRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_TouchRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_TouchRequest) ProtoMessage() {} - -func (x *AgentThread_TouchRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_TouchRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_TouchRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} -} - -func (x *AgentThread_TouchRequest) GetThreadId() string { - if x != nil { - return x.ThreadId - } - return "" -} - -type AgentThread_TouchResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_TouchResponse) Reset() { - *x = AgentThread_TouchResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_TouchResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_TouchResponse) ProtoMessage() {} - -func (x *AgentThread_TouchResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_TouchResponse.ProtoReflect.Descriptor instead. -func (*AgentThread_TouchResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} -} - -func (x *AgentThread_TouchResponse) GetExpiresAt() *timestamppb.Timestamp { - if x != nil { - return x.ExpiresAt - } - return nil -} - -type AgentThread_AddKeyRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - // Unset keeps the key for the life of the thread. - Ttl *durationpb.Duration `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"` - // Move the key here when another thread holds it. - Replace bool `protobuf:"varint,4,opt,name=replace,proto3" json:"replace,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_AddKeyRequest) Reset() { - *x = AgentThread_AddKeyRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_AddKeyRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_AddKeyRequest) ProtoMessage() {} - -func (x *AgentThread_AddKeyRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_AddKeyRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_AddKeyRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} -} - -func (x *AgentThread_AddKeyRequest) GetThreadId() string { - if x != nil { - return x.ThreadId - } - return "" -} - -func (x *AgentThread_AddKeyRequest) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -func (x *AgentThread_AddKeyRequest) GetTtl() *durationpb.Duration { - if x != nil { - return x.Ttl - } - return nil -} - -func (x *AgentThread_AddKeyRequest) GetReplace() bool { - if x != nil { - return x.Replace - } - return false -} - -type AgentThread_RemoveKeyRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_RemoveKeyRequest) Reset() { - *x = AgentThread_RemoveKeyRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_RemoveKeyRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_RemoveKeyRequest) ProtoMessage() {} - -func (x *AgentThread_RemoveKeyRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_RemoveKeyRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_RemoveKeyRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} -} - -func (x *AgentThread_RemoveKeyRequest) GetThreadId() string { - if x != nil { - return x.ThreadId - } - return "" -} - -func (x *AgentThread_RemoveKeyRequest) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - type AgentThread_DeleteRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` @@ -682,7 +473,7 @@ type AgentThread_DeleteRequest struct { func (x *AgentThread_DeleteRequest) Reset() { *x = AgentThread_DeleteRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -694,7 +485,7 @@ func (x *AgentThread_DeleteRequest) String() string { func (*AgentThread_DeleteRequest) ProtoMessage() {} func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -707,7 +498,7 @@ func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_DeleteRequest.ProtoReflect.Descriptor instead. func (*AgentThread_DeleteRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 10} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} } func (x *AgentThread_DeleteRequest) GetThreadId() string { @@ -725,7 +516,7 @@ type AgentThread_DeleteResponse struct { func (x *AgentThread_DeleteResponse) Reset() { *x = AgentThread_DeleteResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -737,7 +528,7 @@ func (x *AgentThread_DeleteResponse) String() string { func (*AgentThread_DeleteResponse) ProtoMessage() {} func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -750,52 +541,7 @@ func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_DeleteResponse.ProtoReflect.Descriptor instead. func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 11} -} - -// Creates the thread's database if it has none. Idempotent. -type AgentThread_CreateThreadDatabaseRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_CreateThreadDatabaseRequest) Reset() { - *x = AgentThread_CreateThreadDatabaseRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_CreateThreadDatabaseRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_CreateThreadDatabaseRequest) ProtoMessage() {} - -func (x *AgentThread_CreateThreadDatabaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_CreateThreadDatabaseRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_CreateThreadDatabaseRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 12} -} - -func (x *AgentThread_CreateThreadDatabaseRequest) GetThreadId() string { - if x != nil { - return x.ThreadId - } - return "" + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} } // Marks thread_id as merged into another thread, after its items were copied @@ -810,7 +556,7 @@ type AgentThread_MergeThreadRequest struct { func (x *AgentThread_MergeThreadRequest) Reset() { *x = AgentThread_MergeThreadRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -822,7 +568,7 @@ func (x *AgentThread_MergeThreadRequest) String() string { func (*AgentThread_MergeThreadRequest) ProtoMessage() {} func (x *AgentThread_MergeThreadRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[14] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -835,7 +581,7 @@ func (x *AgentThread_MergeThreadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_MergeThreadRequest.ProtoReflect.Descriptor instead. func (*AgentThread_MergeThreadRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 13} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} } func (x *AgentThread_MergeThreadRequest) GetThreadId() string { @@ -855,17 +601,17 @@ func (x *AgentThread_MergeThreadRequest) GetInto() string { type AgentThread_Thread struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // Keys themselves are never returned. - KeyCount uint32 `protobuf:"varint,3,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` + // Internal: the key the thread was created with, if any. + Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` Scope string `protobuf:"bytes,4,opt,name=scope,proto3" json:"scope,omitempty"` Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastActive *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` - // last_active + idle_ttl; every touch or send pushes it out. The thread is - // deleted once it passes. + // last_active + idle_ttl; every update pushes it out. The thread and its + // database are deleted once it passes. ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` IdleTtl *durationpb.Duration `protobuf:"bytes,9,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` - // "DB_..."; empty until the first write or CreateThreadDatabase. + // The thread's database ("DB_..."), created with the thread. DatabaseId string `protobuf:"bytes,10,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` // Set when merged into another thread; calls on it then fail with // FailedPrecondition. @@ -876,7 +622,7 @@ type AgentThread_Thread struct { func (x *AgentThread_Thread) Reset() { *x = AgentThread_Thread{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -888,7 +634,7 @@ func (x *AgentThread_Thread) String() string { func (*AgentThread_Thread) ProtoMessage() {} func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[15] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -901,7 +647,7 @@ func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_Thread.ProtoReflect.Descriptor instead. func (*AgentThread_Thread) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 14} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} } func (x *AgentThread_Thread) GetThreadId() string { @@ -911,11 +657,11 @@ func (x *AgentThread_Thread) GetThreadId() string { return "" } -func (x *AgentThread_Thread) GetKeyCount() uint32 { +func (x *AgentThread_Thread) GetKey() string { if x != nil { - return x.KeyCount + return x.Key } - return 0 + return "" } func (x *AgentThread_Thread) GetScope() string { @@ -978,7 +724,7 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\xc2\x10\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\x83\r\n" + "\vAgentThread\x1a\x96\x02\n" + "\rCreateRequest\x12\x10\n" + "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + @@ -1013,31 +759,16 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\bidle_ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a8\n" + - "\fTouchRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aJ\n" + - "\rTouchResponse\x129\n" + - "\n" + - "expires_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x1a\x92\x01\n" + - "\rAddKeyRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x12+\n" + - "\x03ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x12\x18\n" + - "\areplace\x18\x04 \x01(\bR\areplace\x1aN\n" + - "\x10RemoveKeyRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x1a9\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a9\n" + "\rDeleteRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + - "\x0eDeleteResponse\x1aG\n" + - "\x1bCreateThreadDatabaseRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aR\n" + + "\x0eDeleteResponse\x1aR\n" + "\x12MergeThreadRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x12\n" + - "\x04into\x18\x02 \x01(\tR\x04into\x1a\xb2\x04\n" + + "\x04into\x18\x02 \x01(\tR\x04into\x1a\xa7\x04\n" + "\x06Thread\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x1b\n" + - "\tkey_count\x18\x03 \x01(\rR\bkeyCount\x12\x19\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + + "\x03key\x18\x03 \x01(\tR\x03key\x12\x19\n" + "\x05scope\x18\x04 \x01(\tB\x03\xc0P\x01R\x05scope\x12V\n" + "\n" + "attributes\x18\x05 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + @@ -1070,49 +801,42 @@ func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { return file_agent_livekit_agent_thread_proto_rawDescData } -var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_agent_livekit_agent_thread_proto_goTypes = []any{ - (*AgentThread)(nil), // 0: livekit.agent.AgentThread - (*AgentThread_CreateRequest)(nil), // 1: livekit.agent.AgentThread.CreateRequest - (*AgentThread_CreateResponse)(nil), // 2: livekit.agent.AgentThread.CreateResponse - (*AgentThread_GetRequest)(nil), // 3: livekit.agent.AgentThread.GetRequest - (*AgentThread_ListRequest)(nil), // 4: livekit.agent.AgentThread.ListRequest - (*AgentThread_ListResponse)(nil), // 5: livekit.agent.AgentThread.ListResponse - (*AgentThread_UpdateRequest)(nil), // 6: livekit.agent.AgentThread.UpdateRequest - (*AgentThread_TouchRequest)(nil), // 7: livekit.agent.AgentThread.TouchRequest - (*AgentThread_TouchResponse)(nil), // 8: livekit.agent.AgentThread.TouchResponse - (*AgentThread_AddKeyRequest)(nil), // 9: livekit.agent.AgentThread.AddKeyRequest - (*AgentThread_RemoveKeyRequest)(nil), // 10: livekit.agent.AgentThread.RemoveKeyRequest - (*AgentThread_DeleteRequest)(nil), // 11: livekit.agent.AgentThread.DeleteRequest - (*AgentThread_DeleteResponse)(nil), // 12: livekit.agent.AgentThread.DeleteResponse - (*AgentThread_CreateThreadDatabaseRequest)(nil), // 13: livekit.agent.AgentThread.CreateThreadDatabaseRequest - (*AgentThread_MergeThreadRequest)(nil), // 14: livekit.agent.AgentThread.MergeThreadRequest - (*AgentThread_Thread)(nil), // 15: livekit.agent.AgentThread.Thread - nil, // 16: livekit.agent.AgentThread.CreateRequest.AttributesEntry - nil, // 17: livekit.agent.AgentThread.UpdateRequest.AttributesEntry - nil, // 18: livekit.agent.AgentThread.Thread.AttributesEntry - (*durationpb.Duration)(nil), // 19: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp + (*AgentThread)(nil), // 0: livekit.agent.AgentThread + (*AgentThread_CreateRequest)(nil), // 1: livekit.agent.AgentThread.CreateRequest + (*AgentThread_CreateResponse)(nil), // 2: livekit.agent.AgentThread.CreateResponse + (*AgentThread_GetRequest)(nil), // 3: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 4: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 5: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 6: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_DeleteRequest)(nil), // 7: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 8: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_MergeThreadRequest)(nil), // 9: livekit.agent.AgentThread.MergeThreadRequest + (*AgentThread_Thread)(nil), // 10: livekit.agent.AgentThread.Thread + nil, // 11: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 12: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 13: livekit.agent.AgentThread.Thread.AttributesEntry + (*durationpb.Duration)(nil), // 14: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ - 16, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry - 19, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration - 15, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread - 15, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread - 17, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry - 19, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration - 20, // 6: livekit.agent.AgentThread.TouchResponse.expires_at:type_name -> google.protobuf.Timestamp - 19, // 7: livekit.agent.AgentThread.AddKeyRequest.ttl:type_name -> google.protobuf.Duration - 18, // 8: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry - 20, // 9: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp - 20, // 10: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp - 20, // 11: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp - 19, // 12: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration - 13, // [13:13] is the sub-list for method output_type - 13, // [13:13] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 11, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry + 14, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration + 10, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread + 10, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread + 12, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry + 14, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration + 13, // 6: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry + 15, // 7: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp + 15, // 8: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp + 15, // 9: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp + 14, // 10: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_agent_livekit_agent_thread_proto_init() } @@ -1130,7 +854,7 @@ func file_agent_livekit_agent_thread_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_livekit_agent_thread_proto_rawDesc), len(file_agent_livekit_agent_thread_proto_rawDesc)), NumEnums: 0, - NumMessages: 19, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index ed5de7fd8..4aaf96afc 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -31,7 +31,8 @@ import "logger/options.proto"; message AgentThread { message CreateRequest { reserved 1; - // When set, create is get-or-create on key. + // Internal: when set, create is get-or-create on key (e.g. a LiveKit phone + // number). Not exposed by the public API. string key = 2; // Groups threads, e.g. by user: an application-defined value, typically the // user's identity. An AgentThreadGrant is scoped by it. @@ -74,28 +75,7 @@ message AgentThread { // Merged; an empty value deletes the entry. map attributes = 2 [(logger.sensitivity) = SENSITIVITY_PII]; google.protobuf.Duration idle_ttl = 3; - } - - message TouchRequest { - string thread_id = 1 [(logger.name) = "threadID"]; - } - - message TouchResponse { - google.protobuf.Timestamp expires_at = 1; - } - - message AddKeyRequest { - string thread_id = 1 [(logger.name) = "threadID"]; - string key = 2; - // Unset keeps the key for the life of the thread. - google.protobuf.Duration ttl = 3; - // Move the key here when another thread holds it. - bool replace = 4; - } - - message RemoveKeyRequest { - string thread_id = 1 [(logger.name) = "threadID"]; - string key = 2; + // Every update counts as activity and pushes expires_at out by idle_ttl. } message DeleteRequest { @@ -104,11 +84,6 @@ message AgentThread { message DeleteResponse {} - // Creates the thread's database if it has none. Idempotent. - message CreateThreadDatabaseRequest { - string thread_id = 1 [(logger.name) = "threadID"]; - } - // Marks thread_id as merged into another thread, after its items were copied // there. message MergeThreadRequest { @@ -119,17 +94,17 @@ message AgentThread { message Thread { string thread_id = 1 [(logger.name) = "threadID"]; reserved 2; - // Keys themselves are never returned. - uint32 key_count = 3; + // Internal: the key the thread was created with, if any. + string key = 3; string scope = 4 [(logger.sensitivity) = SENSITIVITY_PII]; map attributes = 5 [(logger.sensitivity) = SENSITIVITY_PII]; google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp last_active = 7; - // last_active + idle_ttl; every touch or send pushes it out. The thread is - // deleted once it passes. + // last_active + idle_ttl; every update pushes it out. The thread and its + // database are deleted once it passes. google.protobuf.Timestamp expires_at = 8; google.protobuf.Duration idle_ttl = 9; - // "DB_..."; empty until the first write or CreateThreadDatabase. + // The thread's database ("DB_..."), created with the thread. string database_id = 10; // Set when merged into another thread; calls on it then fail with // FailedPrecondition. From c4e191cddabda34b90b5fb0da648d39d364cba35 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 05:31:59 +0000 Subject: [PATCH 18/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 384ee3488..1117a4e70 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From cc880113f8da99eaef6ef5909291f14ebe5ac63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 08:39:11 -0700 Subject: [PATCH 19/56] agent thread: header matches the single internal key --- protobufs/agent/livekit_agent_thread.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 4aaf96afc..8e23be095 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -13,10 +13,10 @@ // limitations under the License. // AgentThread: the durable conversation between an agent and a person, across -// channels and over time. Identified by an "AT_" id; keys (such as a phone -// number) find it again, at most one thread per key in a project. Holds a scope, -// attributes, an idle TTL and the AgentDB database with its state. Served by -// cloud-protocol's AgentThreads. +// channels and over time. Identified by an "AT_" id. Holds a scope, attributes, +// an idle TTL and the AgentDB database created with it. An internal key (a +// LiveKit phone number) can find it again. Served by cloud-protocol's +// AgentThreads. syntax = "proto3"; From 07d6ab25fb2cadcc68293f6f83f0e4ae99b06af1 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:40:01 +0000 Subject: [PATCH 20/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 1117a4e70..267eec3e1 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -13,10 +13,10 @@ // limitations under the License. // AgentThread: the durable conversation between an agent and a person, across -// channels and over time. Identified by an "AT_" id; keys (such as a phone -// number) find it again, at most one thread per key in a project. Holds a scope, -// attributes, an idle TTL and the AgentDB database with its state. Served by -// cloud-protocol's AgentThreads. +// channels and over time. Identified by an "AT_" id. Holds a scope, attributes, +// an idle TTL and the AgentDB database created with it. An internal key (a +// LiveKit phone number) can find it again. Served by cloud-protocol's +// AgentThreads. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: From fe85b6042b92e3ab3456beecdabad8801dd11601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 13:24:55 -0700 Subject: [PATCH 21/56] agent thread: no merge for now --- livekit/agent/livekit_agent_thread.pb.go | 146 ++++++--------------- protobufs/agent/livekit_agent_thread.proto | 13 +- 2 files changed, 40 insertions(+), 119 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 267eec3e1..0c4f94c9f 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -406,7 +406,7 @@ func (x *AgentThread_ListResponse) GetBefore() string { type AgentThread_UpdateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // Merged; an empty value deletes the entry. + // Added to the existing attributes; an empty value deletes the entry. Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` IdleTtl *durationpb.Duration `protobuf:"bytes,3,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` // Every update counts as activity and pushes expires_at out by idle_ttl. unknownFields protoimpl.UnknownFields @@ -544,60 +544,6 @@ func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} } -// Marks thread_id as merged into another thread, after its items were copied -// there. -type AgentThread_MergeThreadRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - Into string `protobuf:"bytes,2,opt,name=into,proto3" json:"into,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_MergeThreadRequest) Reset() { - *x = AgentThread_MergeThreadRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_MergeThreadRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_MergeThreadRequest) ProtoMessage() {} - -func (x *AgentThread_MergeThreadRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_MergeThreadRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_MergeThreadRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} -} - -func (x *AgentThread_MergeThreadRequest) GetThreadId() string { - if x != nil { - return x.ThreadId - } - return "" -} - -func (x *AgentThread_MergeThreadRequest) GetInto() string { - if x != nil { - return x.Into - } - return "" -} - type AgentThread_Thread struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` @@ -612,17 +558,14 @@ type AgentThread_Thread struct { ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` IdleTtl *durationpb.Duration `protobuf:"bytes,9,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` // The thread's database ("DB_..."), created with the thread. - DatabaseId string `protobuf:"bytes,10,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` - // Set when merged into another thread; calls on it then fail with - // FailedPrecondition. - MergedInto string `protobuf:"bytes,11,opt,name=merged_into,json=mergedInto,proto3" json:"merged_into,omitempty"` + DatabaseId string `protobuf:"bytes,10,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *AgentThread_Thread) Reset() { *x = AgentThread_Thread{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -634,7 +577,7 @@ func (x *AgentThread_Thread) String() string { func (*AgentThread_Thread) ProtoMessage() {} func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -647,7 +590,7 @@ func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_Thread.ProtoReflect.Descriptor instead. func (*AgentThread_Thread) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} } func (x *AgentThread_Thread) GetThreadId() string { @@ -713,18 +656,11 @@ func (x *AgentThread_Thread) GetDatabaseId() string { return "" } -func (x *AgentThread_Thread) GetMergedInto() string { - if x != nil { - return x.MergedInto - } - return "" -} - var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\x83\r\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\x94\f\n" + "\vAgentThread\x1a\x96\x02\n" + "\rCreateRequest\x12\x10\n" + "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + @@ -762,10 +698,7 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a9\n" + "\rDeleteRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + - "\x0eDeleteResponse\x1aR\n" + - "\x12MergeThreadRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x12\n" + - "\x04into\x18\x02 \x01(\tR\x04into\x1a\xa7\x04\n" + + "\x0eDeleteResponse\x1a\x8c\x04\n" + "\x06Thread\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + "\x03key\x18\x03 \x01(\tR\x03key\x12\x19\n" + @@ -782,12 +715,10 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\bidle_ttl\x18\t \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x12\x1f\n" + "\vdatabase_id\x18\n" + " \x01(\tR\n" + - "databaseId\x12\x1f\n" + - "\vmerged_into\x18\v \x01(\tR\n" + - "mergedInto\x1a=\n" + + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x02\x10\x03B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x02\x10\x03J\x04\b\v\x10\fB+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" var ( file_agent_livekit_agent_thread_proto_rawDescOnce sync.Once @@ -801,37 +732,36 @@ func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { return file_agent_livekit_agent_thread_proto_rawDescData } -var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_agent_livekit_agent_thread_proto_goTypes = []any{ - (*AgentThread)(nil), // 0: livekit.agent.AgentThread - (*AgentThread_CreateRequest)(nil), // 1: livekit.agent.AgentThread.CreateRequest - (*AgentThread_CreateResponse)(nil), // 2: livekit.agent.AgentThread.CreateResponse - (*AgentThread_GetRequest)(nil), // 3: livekit.agent.AgentThread.GetRequest - (*AgentThread_ListRequest)(nil), // 4: livekit.agent.AgentThread.ListRequest - (*AgentThread_ListResponse)(nil), // 5: livekit.agent.AgentThread.ListResponse - (*AgentThread_UpdateRequest)(nil), // 6: livekit.agent.AgentThread.UpdateRequest - (*AgentThread_DeleteRequest)(nil), // 7: livekit.agent.AgentThread.DeleteRequest - (*AgentThread_DeleteResponse)(nil), // 8: livekit.agent.AgentThread.DeleteResponse - (*AgentThread_MergeThreadRequest)(nil), // 9: livekit.agent.AgentThread.MergeThreadRequest - (*AgentThread_Thread)(nil), // 10: livekit.agent.AgentThread.Thread - nil, // 11: livekit.agent.AgentThread.CreateRequest.AttributesEntry - nil, // 12: livekit.agent.AgentThread.UpdateRequest.AttributesEntry - nil, // 13: livekit.agent.AgentThread.Thread.AttributesEntry - (*durationpb.Duration)(nil), // 14: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp + (*AgentThread)(nil), // 0: livekit.agent.AgentThread + (*AgentThread_CreateRequest)(nil), // 1: livekit.agent.AgentThread.CreateRequest + (*AgentThread_CreateResponse)(nil), // 2: livekit.agent.AgentThread.CreateResponse + (*AgentThread_GetRequest)(nil), // 3: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 4: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 5: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 6: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_DeleteRequest)(nil), // 7: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 8: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_Thread)(nil), // 9: livekit.agent.AgentThread.Thread + nil, // 10: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 11: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 12: livekit.agent.AgentThread.Thread.AttributesEntry + (*durationpb.Duration)(nil), // 13: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ - 11, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry - 14, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration - 10, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread - 10, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread - 12, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry - 14, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration - 13, // 6: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry - 15, // 7: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp - 15, // 8: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp - 15, // 9: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp - 14, // 10: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration + 10, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry + 13, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration + 9, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread + 9, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread + 11, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry + 13, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration + 12, // 6: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry + 14, // 7: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp + 14, // 8: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp + 14, // 9: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp + 13, // 10: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration 11, // [11:11] is the sub-list for method output_type 11, // [11:11] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name @@ -854,7 +784,7 @@ func file_agent_livekit_agent_thread_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_livekit_agent_thread_proto_rawDesc), len(file_agent_livekit_agent_thread_proto_rawDesc)), NumEnums: 0, - NumMessages: 14, + NumMessages: 13, NumExtensions: 0, NumServices: 0, }, diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 8e23be095..25f16f28f 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -72,7 +72,7 @@ message AgentThread { message UpdateRequest { string thread_id = 1 [(logger.name) = "threadID"]; - // Merged; an empty value deletes the entry. + // Added to the existing attributes; an empty value deletes the entry. map attributes = 2 [(logger.sensitivity) = SENSITIVITY_PII]; google.protobuf.Duration idle_ttl = 3; // Every update counts as activity and pushes expires_at out by idle_ttl. @@ -84,13 +84,6 @@ message AgentThread { message DeleteResponse {} - // Marks thread_id as merged into another thread, after its items were copied - // there. - message MergeThreadRequest { - string thread_id = 1 [(logger.name) = "threadID"]; - string into = 2; - } - message Thread { string thread_id = 1 [(logger.name) = "threadID"]; reserved 2; @@ -106,8 +99,6 @@ message AgentThread { google.protobuf.Duration idle_ttl = 9; // The thread's database ("DB_..."), created with the thread. string database_id = 10; - // Set when merged into another thread; calls on it then fail with - // FailedPrecondition. - string merged_into = 11; + reserved 11; } } From fb69b0ba750e447bb26a64415286181b366523b1 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:25:35 +0000 Subject: [PATCH 22/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 0c4f94c9f..123cf71bf 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 3c6aea99a8f445205388d35a4c5a8bf55cb10b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 13:26:47 -0700 Subject: [PATCH 23/56] agent thread, agentdb: no reserved fields, nothing shipped yet --- .changeset/agent-threads.md | 2 +- livekit/agent/livekit_agent_thread.pb.go | 73 +++++++++++----------- livekit/livekit_agentdb.pb.go | 10 +-- protobufs/agent/livekit_agent_thread.proto | 34 +++++----- protobufs/livekit_agentdb.proto | 5 +- 5 files changed, 58 insertions(+), 66 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index 05d04077c..ad7b209a8 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -5,4 +5,4 @@ Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim (threads by scope or id, with list/create/write/read/delete), and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. -The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is reserved, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. +The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 123cf71bf..6d799107a 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -84,13 +84,13 @@ type AgentThread_CreateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Internal: when set, create is get-or-create on key (e.g. a LiveKit phone // number). Not exposed by the public API. - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Groups threads, e.g. by user: an application-defined value, typically the // user's identity. An AgentThreadGrant is scoped by it. - Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` - Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` + Attributes map[string]string `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Unset takes the server default. - IdleTtl *durationpb.Duration `protobuf:"bytes,5,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + IdleTtl *durationpb.Duration `protobuf:"bytes,4,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -291,10 +291,10 @@ func (*AgentThread_GetRequest_Key) isAgentThread_GetRequest_Lookup() {} // The threads of one scope, newest first. type AgentThread_ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` + Scope string `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` // Cursor from the previous ListResponse; empty for the first page. - Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` - Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + Before string `protobuf:"bytes,2,opt,name=before,proto3" json:"before,omitempty"` + Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -548,17 +548,17 @@ type AgentThread_Thread struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` // Internal: the key the thread was created with, if any. - Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` - Scope string `protobuf:"bytes,4,opt,name=scope,proto3" json:"scope,omitempty"` - Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - LastActive *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` + Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + LastActive *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` // last_active + idle_ttl; every update pushes it out. The thread and its // database are deleted once it passes. - ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` - IdleTtl *durationpb.Duration `protobuf:"bytes,9,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + IdleTtl *durationpb.Duration `protobuf:"bytes,8,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` // The thread's database ("DB_..."), created with the thread. - DatabaseId string `protobuf:"bytes,10,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` + DatabaseId string `protobuf:"bytes,9,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -660,18 +660,18 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\x94\f\n" + - "\vAgentThread\x1a\x96\x02\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\xfc\v\n" + + "\vAgentThread\x1a\x90\x02\n" + "\rCreateRequest\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + - "\x05scope\x18\x03 \x01(\tB\x03\xc0P\x01R\x05scope\x12]\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x19\n" + + "\x05scope\x18\x02 \x01(\tB\x03\xc0P\x01R\x05scope\x12]\n" + "\n" + - "attributes\x18\x04 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + + "attributes\x18\x03 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + "attributes\x124\n" + - "\bidle_ttl\x18\x05 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + + "\bidle_ttl\x18\x04 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02\x1ae\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ae\n" + "\x0eCreateResponse\x129\n" + "\x06thread\x18\x01 \x01(\v2!.livekit.agent.AgentThread.ThreadR\x06thread\x12\x18\n" + "\acreated\x18\x02 \x01(\bR\acreated\x1aV\n" + @@ -679,11 +679,11 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "GetRequest\x12*\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDH\x00R\bthreadId\x12\x12\n" + "\x03key\x18\x02 \x01(\tH\x00R\x03keyB\b\n" + - "\x06lookup\x1a\\\n" + + "\x06lookup\x1aV\n" + "\vListRequest\x12\x19\n" + - "\x05scope\x18\x02 \x01(\tB\x03\xc0P\x01R\x05scope\x12\x16\n" + - "\x06before\x18\x03 \x01(\tR\x06before\x12\x14\n" + - "\x05limit\x18\x04 \x01(\x05R\x05limitJ\x04\b\x01\x10\x02\x1ac\n" + + "\x05scope\x18\x01 \x01(\tB\x03\xc0P\x01R\x05scope\x12\x16\n" + + "\x06before\x18\x02 \x01(\tR\x06before\x12\x14\n" + + "\x05limit\x18\x03 \x01(\x05R\x05limit\x1ac\n" + "\fListResponse\x12;\n" + "\athreads\x18\x01 \x03(\v2!.livekit.agent.AgentThread.ThreadR\athreads\x12\x16\n" + "\x06before\x18\x02 \x01(\tR\x06before\x1a\x8d\x02\n" + @@ -698,27 +698,26 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a9\n" + "\rDeleteRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + - "\x0eDeleteResponse\x1a\x8c\x04\n" + + "\x0eDeleteResponse\x1a\x80\x04\n" + "\x06Thread\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + - "\x03key\x18\x03 \x01(\tR\x03key\x12\x19\n" + - "\x05scope\x18\x04 \x01(\tB\x03\xc0P\x01R\x05scope\x12V\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + + "\x05scope\x18\x03 \x01(\tB\x03\xc0P\x01R\x05scope\x12V\n" + "\n" + - "attributes\x18\x05 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + + "attributes\x18\x04 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + "attributes\x129\n" + "\n" + - "created_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + - "\vlast_active\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + + "\vlast_active\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\n" + "lastActive\x129\n" + "\n" + - "expires_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x124\n" + - "\bidle_ttl\x18\t \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x12\x1f\n" + - "\vdatabase_id\x18\n" + - " \x01(\tR\n" + + "expires_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x124\n" + + "\bidle_ttl\x18\b \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x12\x1f\n" + + "\vdatabase_id\x18\t \x01(\tR\n" + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x02\x10\x03J\x04\b\v\x10\fB+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" var ( file_agent_livekit_agent_thread_proto_rawDescOnce sync.Once diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 32b41791d..af454f016 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: livekit_agentdb.proto package livekit @@ -181,7 +181,7 @@ type AgentDB_CreateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Unset: never expires, and expires_at is then unset everywhere. // Otherwise clamped to the server cap, and never extended. - Ttl *durationpb.Duration `protobuf:"bytes,2,opt,name=ttl,proto3" json:"ttl,omitempty"` + Ttl *durationpb.Duration `protobuf:"bytes,1,opt,name=ttl,proto3" json:"ttl,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2103,10 +2103,10 @@ var File_livekit_agentdb_proto protoreflect.FileDescriptor const file_livekit_agentdb_proto_rawDesc = "" + "\n" + - "\x15livekit_agentdb.proto\x12\alivekit\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xca\x1a\n" + - "\aAgentDB\x1aJ\n" + + "\x15livekit_agentdb.proto\x12\alivekit\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xbc\x1a\n" + + "\aAgentDB\x1a<\n" + "\rCreateRequest\x12+\n" + - "\x03ttl\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\x03ttlJ\x04\b\x01\x10\x02R\x06region\x1al\n" + + "\x03ttl\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x1al\n" + "\x0eCreateResponse\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + "databaseId\x129\n" + diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 25f16f28f..a0edf0d9a 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -30,16 +30,15 @@ import "logger/options.proto"; message AgentThread { message CreateRequest { - reserved 1; // Internal: when set, create is get-or-create on key (e.g. a LiveKit phone // number). Not exposed by the public API. - string key = 2; + string key = 1; // Groups threads, e.g. by user: an application-defined value, typically the // user's identity. An AgentThreadGrant is scoped by it. - string scope = 3 [(logger.sensitivity) = SENSITIVITY_PII]; - map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; + string scope = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + map attributes = 3 [(logger.sensitivity) = SENSITIVITY_PII]; // Unset takes the server default. - google.protobuf.Duration idle_ttl = 5; + google.protobuf.Duration idle_ttl = 4; } message CreateResponse { @@ -57,11 +56,10 @@ message AgentThread { // The threads of one scope, newest first. message ListRequest { - reserved 1; - string scope = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + string scope = 1 [(logger.sensitivity) = SENSITIVITY_PII]; // Cursor from the previous ListResponse; empty for the first page. - string before = 3; - int32 limit = 4; + string before = 2; + int32 limit = 3; } message ListResponse { @@ -86,19 +84,17 @@ message AgentThread { message Thread { string thread_id = 1 [(logger.name) = "threadID"]; - reserved 2; // Internal: the key the thread was created with, if any. - string key = 3; - string scope = 4 [(logger.sensitivity) = SENSITIVITY_PII]; - map attributes = 5 [(logger.sensitivity) = SENSITIVITY_PII]; - google.protobuf.Timestamp created_at = 6; - google.protobuf.Timestamp last_active = 7; + string key = 2; + string scope = 3 [(logger.sensitivity) = SENSITIVITY_PII]; + map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp last_active = 6; // last_active + idle_ttl; every update pushes it out. The thread and its // database are deleted once it passes. - google.protobuf.Timestamp expires_at = 8; - google.protobuf.Duration idle_ttl = 9; + google.protobuf.Timestamp expires_at = 7; + google.protobuf.Duration idle_ttl = 8; // The thread's database ("DB_..."), created with the thread. - string database_id = 10; - reserved 11; + string database_id = 9; } } diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index 2f2ee8333..36ce95d45 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -31,12 +31,9 @@ import "google/protobuf/timestamp.proto"; // livekit package. `tip` throughout is the latest durable commit sequence. message AgentDB { message CreateRequest { - // A database lives in its project's data region. - reserved 1; - reserved "region"; // Unset: never expires, and expires_at is then unset everywhere. // Otherwise clamped to the server cap, and never extended. - google.protobuf.Duration ttl = 2; + google.protobuf.Duration ttl = 1; } message CreateResponse { From abbc6efbd1ee1deadefaad5ece2f878bfbb867e3 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:27:38 +0000 Subject: [PATCH 24/56] generated protobuf --- livekit/livekit_agentdb.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index af454f016..f286ed14e 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: livekit_agentdb.proto package livekit From 145381d488d6664a3ae261285dabc7f0cbd8ae65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 13:28:09 -0700 Subject: [PATCH 25/56] auth: thread grant reads, writes and deletes by id --- auth/grants.go | 37 +++++++++++++++---------------------- auth/grants_test.go | 24 +++++++++--------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/auth/grants.go b/auth/grants.go index 12fe65847..041ef8269 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -24,7 +24,6 @@ import ( "golang.org/x/exp/slices" "github.com/livekit/protocol/livekit" - "github.com/livekit/protocol/livekit/agent" "github.com/livekit/protocol/logger" "github.com/livekit/protocol/utils" "github.com/livekit/protocol/utils/protojson" @@ -716,9 +715,10 @@ func (s *AgentEndpointGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // ------------------------------------------------------------------ -// AgentThreadGrant gives a token access to threads: those whose scope equals -// Scope, or those listed in ThreadIDs, across the project's agents. It grants -// thread data only; which endpoints a token may call is agentEndpoint's. +// AgentThreadGrant gives a token access to threads, across the project's +// agents: list and create within Scope, and read, write and delete the threads +// in ThreadIDs. It grants thread data only; which endpoints a token may call is +// agentEndpoint's. type AgentThreadGrant struct { Scope string `json:"scope,omitempty"` ThreadIDs []string `json:"threadIds,omitempty"` @@ -740,30 +740,23 @@ func (s *AgentThreadGrant) CanList(scope string) bool { return s != nil && s.List && s.Scope != "" && s.Scope == scope } -// CanRead reports whether the holder may read thread. -func (s *AgentThreadGrant) CanRead(thread *agent.AgentThread_Thread) bool { - return s != nil && s.Read && s.includes(thread) +// CanRead reports whether the holder may read threadID. +func (s *AgentThreadGrant) CanRead(threadID string) bool { + return s != nil && s.Read && s.includes(threadID) } -// CanWrite reports whether the holder may send to thread. -func (s *AgentThreadGrant) CanWrite(thread *agent.AgentThread_Thread) bool { - return s != nil && s.Write && s.includes(thread) +// CanWrite reports whether the holder may send to threadID. +func (s *AgentThreadGrant) CanWrite(threadID string) bool { + return s != nil && s.Write && s.includes(threadID) } -// CanDelete reports whether the holder may delete thread. -func (s *AgentThreadGrant) CanDelete(thread *agent.AgentThread_Thread) bool { - return s != nil && s.Delete && s.includes(thread) +// CanDelete reports whether the holder may delete threadID. +func (s *AgentThreadGrant) CanDelete(threadID string) bool { + return s != nil && s.Delete && s.includes(threadID) } -// includes reports whether thread is in the grant, by scope or by id. -func (s *AgentThreadGrant) includes(thread *agent.AgentThread_Thread) bool { - if thread == nil { - return false - } - if s.Scope != "" && s.Scope == thread.Scope { - return true - } - return thread.ThreadId != "" && slices.Contains(s.ThreadIDs, thread.ThreadId) +func (s *AgentThreadGrant) includes(threadID string) bool { + return threadID != "" && slices.Contains(s.ThreadIDs, threadID) } func (s *AgentThreadGrant) Clone() *AgentThreadGrant { diff --git a/auth/grants_test.go b/auth/grants_test.go index 1d730d659..532c56a94 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -22,7 +22,6 @@ import ( "github.com/stretchr/testify/require" "github.com/livekit/protocol/livekit" - "github.com/livekit/protocol/livekit/agent" ) func TestGrants(t *testing.T) { @@ -533,29 +532,24 @@ func TestAgentEndpointGrantCloneIndependent(t *testing.T) { func TestAgentThreadGrantChecks(t *testing.T) { t.Parallel() - mine := &agent.AgentThread_Thread{ThreadId: "AT_1", Scope: "u1"} - shared := &agent.AgentThread_Thread{ThreadId: "AT_2", Scope: "u2"} - other := &agent.AgentThread_Thread{ThreadId: "AT_3", Scope: "u2"} - unscoped := &agent.AgentThread_Thread{ThreadId: "AT_4"} - - g := &AgentThreadGrant{Scope: "u1", ThreadIDs: []string{"AT_2"}, List: true, Create: true, Read: true} - require.True(t, g.CanRead(mine)) - require.True(t, g.CanRead(shared)) - require.False(t, g.CanRead(other)) - require.False(t, g.CanRead(unscoped)) - require.False(t, g.CanWrite(mine)) - require.False(t, g.CanDelete(mine)) + // Scope covers list and create only; read, write and delete need the id. + g := &AgentThreadGrant{Scope: "u1", ThreadIDs: []string{"AT_1"}, List: true, Create: true, Read: true} + require.True(t, g.CanRead("AT_1")) + require.False(t, g.CanRead("AT_2")) + require.False(t, g.CanRead("")) + require.False(t, g.CanWrite("AT_1")) + require.False(t, g.CanDelete("AT_1")) require.True(t, g.CanList("u1")) require.False(t, g.CanList("u2")) require.False(t, g.CanList("")) require.True(t, g.CanCreate()) idsOnly := &AgentThreadGrant{ThreadIDs: []string{"AT_4"}, Create: true, Write: true} - require.True(t, idsOnly.CanWrite(unscoped)) + require.True(t, idsOnly.CanWrite("AT_4")) require.False(t, idsOnly.CanCreate()) var none *AgentThreadGrant - require.False(t, none.CanRead(mine)) + require.False(t, none.CanRead("AT_1")) require.False(t, none.CanCreate()) } From 0c5f896a11dca176e4bab143689e600bcc4e6ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:03:33 -0700 Subject: [PATCH 26/56] agent thread: history, AgentTask and the shared conversation schema --- .changeset/agent-threads.md | 2 +- livekit/agent/livekit_agent_thread.pb.go | 724 +++++++++++++++++++-- livekit/agent/thread_schema.go | 27 + livekit/agent/thread_schema_v1.sql | 78 +++ protobufs/agent/livekit_agent_thread.proto | 80 ++- 5 files changed, 850 insertions(+), 61 deletions(-) create mode 100644 livekit/agent/thread_schema.go create mode 100644 livekit/agent/thread_schema_v1.sql diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index ad7b209a8..cb39d7c09 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels: `AgentThread` messages (`agent/livekit_agent_thread.proto`), `auth.AgentThreadGrant` under the `agentThread` claim (threads by scope or id, with list/create/write/read/delete), and the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (the tasks agents run in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 6d799107a..cd7d3ab0a 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -16,12 +16,13 @@ // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a // LiveKit phone number) can find it again. Served by cloud-protocol's -// AgentThreads. +// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared +// by the agents framework, which writes it, and the platform, which reads it. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -31,6 +32,7 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" + structpb "google.golang.org/protobuf/types/known/structpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" @@ -44,6 +46,64 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type AgentTask_Status int32 + +const ( + AgentTask_SUBMITTED AgentTask_Status = 0 + AgentTask_WORKING AgentTask_Status = 1 + AgentTask_INPUT_REQUIRED AgentTask_Status = 2 + AgentTask_COMPLETED AgentTask_Status = 3 + AgentTask_FAILED AgentTask_Status = 4 + AgentTask_CANCELED AgentTask_Status = 5 +) + +// Enum value maps for AgentTask_Status. +var ( + AgentTask_Status_name = map[int32]string{ + 0: "SUBMITTED", + 1: "WORKING", + 2: "INPUT_REQUIRED", + 3: "COMPLETED", + 4: "FAILED", + 5: "CANCELED", + } + AgentTask_Status_value = map[string]int32{ + "SUBMITTED": 0, + "WORKING": 1, + "INPUT_REQUIRED": 2, + "COMPLETED": 3, + "FAILED": 4, + "CANCELED": 5, + } +) + +func (x AgentTask_Status) Enum() *AgentTask_Status { + p := new(AgentTask_Status) + *p = x + return p +} + +func (x AgentTask_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AgentTask_Status) Descriptor() protoreflect.EnumDescriptor { + return file_agent_livekit_agent_thread_proto_enumTypes[0].Descriptor() +} + +func (AgentTask_Status) Type() protoreflect.EnumType { + return &file_agent_livekit_agent_thread_proto_enumTypes[0] +} + +func (x AgentTask_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AgentTask_Status.Descriptor instead. +func (AgentTask_Status) EnumDescriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{1, 0} +} + type AgentThread struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -80,6 +140,44 @@ func (*AgentThread) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0} } +// AgentTask: a task an agent runs within a thread, the AgentTask class an agent +// tool submitted. +type AgentTask struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentTask) Reset() { + *x = AgentTask{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentTask) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentTask) ProtoMessage() {} + +func (x *AgentTask) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentTask.ProtoReflect.Descriptor instead. +func (*AgentTask) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{1} +} + type AgentThread_CreateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Internal: when set, create is get-or-create on key (e.g. a LiveKit phone @@ -97,7 +195,7 @@ type AgentThread_CreateRequest struct { func (x *AgentThread_CreateRequest) Reset() { *x = AgentThread_CreateRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -109,7 +207,7 @@ func (x *AgentThread_CreateRequest) String() string { func (*AgentThread_CreateRequest) ProtoMessage() {} func (x *AgentThread_CreateRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[1] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -164,7 +262,7 @@ type AgentThread_CreateResponse struct { func (x *AgentThread_CreateResponse) Reset() { *x = AgentThread_CreateResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -176,7 +274,7 @@ func (x *AgentThread_CreateResponse) String() string { func (*AgentThread_CreateResponse) ProtoMessage() {} func (x *AgentThread_CreateResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[2] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -219,7 +317,7 @@ type AgentThread_GetRequest struct { func (x *AgentThread_GetRequest) Reset() { *x = AgentThread_GetRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -231,7 +329,7 @@ func (x *AgentThread_GetRequest) String() string { func (*AgentThread_GetRequest) ProtoMessage() {} func (x *AgentThread_GetRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -301,7 +399,7 @@ type AgentThread_ListRequest struct { func (x *AgentThread_ListRequest) Reset() { *x = AgentThread_ListRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -313,7 +411,7 @@ func (x *AgentThread_ListRequest) String() string { func (*AgentThread_ListRequest) ProtoMessage() {} func (x *AgentThread_ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -361,7 +459,7 @@ type AgentThread_ListResponse struct { func (x *AgentThread_ListResponse) Reset() { *x = AgentThread_ListResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -373,7 +471,7 @@ func (x *AgentThread_ListResponse) String() string { func (*AgentThread_ListResponse) ProtoMessage() {} func (x *AgentThread_ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -415,7 +513,7 @@ type AgentThread_UpdateRequest struct { func (x *AgentThread_UpdateRequest) Reset() { *x = AgentThread_UpdateRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -427,7 +525,7 @@ func (x *AgentThread_UpdateRequest) String() string { func (*AgentThread_UpdateRequest) ProtoMessage() {} func (x *AgentThread_UpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -473,7 +571,7 @@ type AgentThread_DeleteRequest struct { func (x *AgentThread_DeleteRequest) Reset() { *x = AgentThread_DeleteRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -485,7 +583,7 @@ func (x *AgentThread_DeleteRequest) String() string { func (*AgentThread_DeleteRequest) ProtoMessage() {} func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -516,7 +614,7 @@ type AgentThread_DeleteResponse struct { func (x *AgentThread_DeleteResponse) Reset() { *x = AgentThread_DeleteResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -528,7 +626,7 @@ func (x *AgentThread_DeleteResponse) String() string { func (*AgentThread_DeleteResponse) ProtoMessage() {} func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -544,6 +642,202 @@ func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} } +// The conversation, oldest first. +type AgentThread_HistoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + // One agent's history; empty for every agent's. + Agent string `protobuf:"bytes,2,opt,name=agent,proto3" json:"agent,omitempty"` + // Cursor from the previous HistoryResponse; empty for the first page. + After string `protobuf:"bytes,3,opt,name=after,proto3" json:"after,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_HistoryRequest) Reset() { + *x = AgentThread_HistoryRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_HistoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_HistoryRequest) ProtoMessage() {} + +func (x *AgentThread_HistoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_HistoryRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_HistoryRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} +} + +func (x *AgentThread_HistoryRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentThread_HistoryRequest) GetAgent() string { + if x != nil { + return x.Agent + } + return "" +} + +func (x *AgentThread_HistoryRequest) GetAfter() string { + if x != nil { + return x.After + } + return "" +} + +func (x *AgentThread_HistoryRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +type AgentThread_HistoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*AgentThread_Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + // Empty at the end. + After string `protobuf:"bytes,2,opt,name=after,proto3" json:"after,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_HistoryResponse) Reset() { + *x = AgentThread_HistoryResponse{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_HistoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_HistoryResponse) ProtoMessage() {} + +func (x *AgentThread_HistoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_HistoryResponse.ProtoReflect.Descriptor instead. +func (*AgentThread_HistoryResponse) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} +} + +func (x *AgentThread_HistoryResponse) GetItems() []*AgentThread_Item { + if x != nil { + return x.Items + } + return nil +} + +func (x *AgentThread_HistoryResponse) GetAfter() string { + if x != nil { + return x.After + } + return "" +} + +// One chat item of an agent's history. Stored as the JSON of +// ChatContext.ChatItem (items.payload in thread_schema_v1.sql). +type AgentThread_Item struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent string `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + // Position in the agent's history. + Seq int64 `protobuf:"varint,2,opt,name=seq,proto3" json:"seq,omitempty"` + // The A2A message the item came from. + MessageId string `protobuf:"bytes,3,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` + Item *ChatContext_ChatItem `protobuf:"bytes,4,opt,name=item,proto3" json:"item,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentThread_Item) Reset() { + *x = AgentThread_Item{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentThread_Item) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentThread_Item) ProtoMessage() {} + +func (x *AgentThread_Item) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentThread_Item.ProtoReflect.Descriptor instead. +func (*AgentThread_Item) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 10} +} + +func (x *AgentThread_Item) GetAgent() string { + if x != nil { + return x.Agent + } + return "" +} + +func (x *AgentThread_Item) GetSeq() int64 { + if x != nil { + return x.Seq + } + return 0 +} + +func (x *AgentThread_Item) GetMessageId() string { + if x != nil { + return x.MessageId + } + return "" +} + +func (x *AgentThread_Item) GetItem() *ChatContext_ChatItem { + if x != nil { + return x.Item + } + return nil +} + type AgentThread_Thread struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` @@ -565,7 +859,7 @@ type AgentThread_Thread struct { func (x *AgentThread_Thread) Reset() { *x = AgentThread_Thread{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -577,7 +871,7 @@ func (x *AgentThread_Thread) String() string { func (*AgentThread_Thread) ProtoMessage() {} func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -590,7 +884,7 @@ func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_Thread.ProtoReflect.Descriptor instead. func (*AgentThread_Thread) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 11} } func (x *AgentThread_Thread) GetThreadId() string { @@ -656,11 +950,257 @@ func (x *AgentThread_Thread) GetDatabaseId() string { return "" } +// A thread's tasks, oldest first. +type AgentTask_ListRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + // One agent's tasks; empty for every agent's. + Agent string `protobuf:"bytes,2,opt,name=agent,proto3" json:"agent,omitempty"` + // Only tasks in this status; unset for every status. + Status *AgentTask_Status `protobuf:"varint,3,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status,oneof" json:"status,omitempty"` + // Cursor from the previous ListResponse; empty for the first page. + After string `protobuf:"bytes,4,opt,name=after,proto3" json:"after,omitempty"` + Limit int32 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentTask_ListRequest) Reset() { + *x = AgentTask_ListRequest{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentTask_ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentTask_ListRequest) ProtoMessage() {} + +func (x *AgentTask_ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentTask_ListRequest.ProtoReflect.Descriptor instead. +func (*AgentTask_ListRequest) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *AgentTask_ListRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentTask_ListRequest) GetAgent() string { + if x != nil { + return x.Agent + } + return "" +} + +func (x *AgentTask_ListRequest) GetStatus() AgentTask_Status { + if x != nil && x.Status != nil { + return *x.Status + } + return AgentTask_SUBMITTED +} + +func (x *AgentTask_ListRequest) GetAfter() string { + if x != nil { + return x.After + } + return "" +} + +func (x *AgentTask_ListRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +type AgentTask_ListResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tasks []*AgentTask_Task `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"` + // Empty at the end. + After string `protobuf:"bytes,2,opt,name=after,proto3" json:"after,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentTask_ListResponse) Reset() { + *x = AgentTask_ListResponse{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentTask_ListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentTask_ListResponse) ProtoMessage() {} + +func (x *AgentTask_ListResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentTask_ListResponse.ProtoReflect.Descriptor instead. +func (*AgentTask_ListResponse) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{1, 1} +} + +func (x *AgentTask_ListResponse) GetTasks() []*AgentTask_Task { + if x != nil { + return x.Tasks + } + return nil +} + +func (x *AgentTask_ListResponse) GetAfter() string { + if x != nil { + return x.After + } + return "" +} + +type AgentTask_Task struct { + state protoimpl.MessageState `protogen:"open.v1"` + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + ThreadId string `protobuf:"bytes,2,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + // The agent running it. + Agent string `protobuf:"bytes,3,opt,name=agent,proto3" json:"agent,omitempty"` + // The AgentTask class. + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + // Its constructor arguments. + Arguments *structpb.Struct `protobuf:"bytes,5,opt,name=arguments,proto3" json:"arguments,omitempty"` + Status AgentTask_Status `protobuf:"varint,6,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status" json:"status,omitempty"` + // The task's own conversation. + History []*ChatContext_ChatItem `protobuf:"bytes,7,rep,name=history,proto3" json:"history,omitempty"` + // The task that submitted this one, if any. + Parent string `protobuf:"bytes,8,opt,name=parent,proto3" json:"parent,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentTask_Task) Reset() { + *x = AgentTask_Task{} + mi := &file_agent_livekit_agent_thread_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentTask_Task) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentTask_Task) ProtoMessage() {} + +func (x *AgentTask_Task) ProtoReflect() protoreflect.Message { + mi := &file_agent_livekit_agent_thread_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentTask_Task.ProtoReflect.Descriptor instead. +func (*AgentTask_Task) Descriptor() ([]byte, []int) { + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{1, 2} +} + +func (x *AgentTask_Task) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +func (x *AgentTask_Task) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *AgentTask_Task) GetAgent() string { + if x != nil { + return x.Agent + } + return "" +} + +func (x *AgentTask_Task) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AgentTask_Task) GetArguments() *structpb.Struct { + if x != nil { + return x.Arguments + } + return nil +} + +func (x *AgentTask_Task) GetStatus() AgentTask_Status { + if x != nil { + return x.Status + } + return AgentTask_SUBMITTED +} + +func (x *AgentTask_Task) GetHistory() []*ChatContext_ChatItem { + if x != nil { + return x.History + } + return nil +} + +func (x *AgentTask_Task) GetParent() string { + if x != nil { + return x.Parent + } + return "" +} + +func (x *AgentTask_Task) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x14logger/options.proto\"\xfc\v\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xe8\x0e\n" + "\vAgentThread\x1a\x90\x02\n" + "\rCreateRequest\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x19\n" + @@ -698,7 +1238,21 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a9\n" + "\rDeleteRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + - "\x0eDeleteResponse\x1a\x80\x04\n" + + "\x0eDeleteResponse\x1a|\n" + + "\x0eHistoryRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x14\n" + + "\x05agent\x18\x02 \x01(\tR\x05agent\x12\x14\n" + + "\x05after\x18\x03 \x01(\tR\x05after\x12\x14\n" + + "\x05limit\x18\x04 \x01(\x05R\x05limit\x1a^\n" + + "\x0fHistoryResponse\x125\n" + + "\x05items\x18\x01 \x03(\v2\x1f.livekit.agent.AgentThread.ItemR\x05items\x12\x14\n" + + "\x05after\x18\x02 \x01(\tR\x05after\x1a\x8b\x01\n" + + "\x04Item\x12\x14\n" + + "\x05agent\x18\x01 \x01(\tR\x05agent\x12\x10\n" + + "\x03seq\x18\x02 \x01(\x03R\x03seq\x12\x1d\n" + + "\n" + + "message_id\x18\x03 \x01(\tR\tmessageId\x12<\n" + + "\x04item\x18\x04 \x01(\v2#.livekit.agent.ChatContext.ChatItemB\x03\xc0P\x01R\x04item\x1a\x80\x04\n" + "\x06Thread\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + @@ -717,7 +1271,37 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x90\x06\n" + + "\tAgentTask\x1a\xc2\x01\n" + + "\vListRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x14\n" + + "\x05agent\x18\x02 \x01(\tR\x05agent\x12<\n" + + "\x06status\x18\x03 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusH\x00R\x06status\x88\x01\x01\x12\x14\n" + + "\x05after\x18\x04 \x01(\tR\x05after\x12\x14\n" + + "\x05limit\x18\x05 \x01(\x05R\x05limitB\t\n" + + "\a_status\x1aY\n" + + "\fListResponse\x123\n" + + "\x05tasks\x18\x01 \x03(\v2\x1d.livekit.agent.AgentTask.TaskR\x05tasks\x12\x14\n" + + "\x05after\x18\x02 \x01(\tR\x05after\x1a\xff\x02\n" + + "\x04Task\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12(\n" + + "\tthread_id\x18\x02 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x14\n" + + "\x05agent\x18\x03 \x01(\tR\x05agent\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04name\x12:\n" + + "\targuments\x18\x05 \x01(\v2\x17.google.protobuf.StructB\x03\xc0P\x01R\targuments\x127\n" + + "\x06status\x18\x06 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusR\x06status\x12B\n" + + "\ahistory\x18\a \x03(\v2#.livekit.agent.ChatContext.ChatItemB\x03\xc0P\x01R\ahistory\x12\x16\n" + + "\x06parent\x18\b \x01(\tR\x06parent\x129\n" + + "\n" + + "updated_at\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"a\n" + + "\x06Status\x12\r\n" + + "\tSUBMITTED\x10\x00\x12\v\n" + + "\aWORKING\x10\x01\x12\x12\n" + + "\x0eINPUT_REQUIRED\x10\x02\x12\r\n" + + "\tCOMPLETED\x10\x03\x12\n" + + "\n" + + "\x06FAILED\x10\x04\x12\f\n" + + "\bCANCELED\x10\x05B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" var ( file_agent_livekit_agent_thread_proto_rawDescOnce sync.Once @@ -731,41 +1315,60 @@ func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { return file_agent_livekit_agent_thread_proto_rawDescData } -var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_agent_livekit_agent_thread_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_agent_livekit_agent_thread_proto_goTypes = []any{ - (*AgentThread)(nil), // 0: livekit.agent.AgentThread - (*AgentThread_CreateRequest)(nil), // 1: livekit.agent.AgentThread.CreateRequest - (*AgentThread_CreateResponse)(nil), // 2: livekit.agent.AgentThread.CreateResponse - (*AgentThread_GetRequest)(nil), // 3: livekit.agent.AgentThread.GetRequest - (*AgentThread_ListRequest)(nil), // 4: livekit.agent.AgentThread.ListRequest - (*AgentThread_ListResponse)(nil), // 5: livekit.agent.AgentThread.ListResponse - (*AgentThread_UpdateRequest)(nil), // 6: livekit.agent.AgentThread.UpdateRequest - (*AgentThread_DeleteRequest)(nil), // 7: livekit.agent.AgentThread.DeleteRequest - (*AgentThread_DeleteResponse)(nil), // 8: livekit.agent.AgentThread.DeleteResponse - (*AgentThread_Thread)(nil), // 9: livekit.agent.AgentThread.Thread - nil, // 10: livekit.agent.AgentThread.CreateRequest.AttributesEntry - nil, // 11: livekit.agent.AgentThread.UpdateRequest.AttributesEntry - nil, // 12: livekit.agent.AgentThread.Thread.AttributesEntry - (*durationpb.Duration)(nil), // 13: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp + (AgentTask_Status)(0), // 0: livekit.agent.AgentTask.Status + (*AgentThread)(nil), // 1: livekit.agent.AgentThread + (*AgentTask)(nil), // 2: livekit.agent.AgentTask + (*AgentThread_CreateRequest)(nil), // 3: livekit.agent.AgentThread.CreateRequest + (*AgentThread_CreateResponse)(nil), // 4: livekit.agent.AgentThread.CreateResponse + (*AgentThread_GetRequest)(nil), // 5: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 6: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 7: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 8: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_DeleteRequest)(nil), // 9: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 10: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_HistoryRequest)(nil), // 11: livekit.agent.AgentThread.HistoryRequest + (*AgentThread_HistoryResponse)(nil), // 12: livekit.agent.AgentThread.HistoryResponse + (*AgentThread_Item)(nil), // 13: livekit.agent.AgentThread.Item + (*AgentThread_Thread)(nil), // 14: livekit.agent.AgentThread.Thread + nil, // 15: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 16: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 17: livekit.agent.AgentThread.Thread.AttributesEntry + (*AgentTask_ListRequest)(nil), // 18: livekit.agent.AgentTask.ListRequest + (*AgentTask_ListResponse)(nil), // 19: livekit.agent.AgentTask.ListResponse + (*AgentTask_Task)(nil), // 20: livekit.agent.AgentTask.Task + (*durationpb.Duration)(nil), // 21: google.protobuf.Duration + (*ChatContext_ChatItem)(nil), // 22: livekit.agent.ChatContext.ChatItem + (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 24: google.protobuf.Struct } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ - 10, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry - 13, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration - 9, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread - 9, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread - 11, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry - 13, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration - 12, // 6: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry - 14, // 7: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp - 14, // 8: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp - 14, // 9: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp - 13, // 10: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 15, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry + 21, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration + 14, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread + 14, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread + 16, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry + 21, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration + 13, // 6: livekit.agent.AgentThread.HistoryResponse.items:type_name -> livekit.agent.AgentThread.Item + 22, // 7: livekit.agent.AgentThread.Item.item:type_name -> livekit.agent.ChatContext.ChatItem + 17, // 8: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry + 23, // 9: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp + 23, // 10: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp + 23, // 11: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp + 21, // 12: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration + 0, // 13: livekit.agent.AgentTask.ListRequest.status:type_name -> livekit.agent.AgentTask.Status + 20, // 14: livekit.agent.AgentTask.ListResponse.tasks:type_name -> livekit.agent.AgentTask.Task + 24, // 15: livekit.agent.AgentTask.Task.arguments:type_name -> google.protobuf.Struct + 0, // 16: livekit.agent.AgentTask.Task.status:type_name -> livekit.agent.AgentTask.Status + 22, // 17: livekit.agent.AgentTask.Task.history:type_name -> livekit.agent.ChatContext.ChatItem + 23, // 18: livekit.agent.AgentTask.Task.updated_at:type_name -> google.protobuf.Timestamp + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_agent_livekit_agent_thread_proto_init() } @@ -773,22 +1376,25 @@ func file_agent_livekit_agent_thread_proto_init() { if File_agent_livekit_agent_thread_proto != nil { return } - file_agent_livekit_agent_thread_proto_msgTypes[3].OneofWrappers = []any{ + file_agent_livekit_agent_session_proto_init() + file_agent_livekit_agent_thread_proto_msgTypes[4].OneofWrappers = []any{ (*AgentThread_GetRequest_ThreadId)(nil), (*AgentThread_GetRequest_Key)(nil), } + file_agent_livekit_agent_thread_proto_msgTypes[17].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_livekit_agent_thread_proto_rawDesc), len(file_agent_livekit_agent_thread_proto_rawDesc)), - NumEnums: 0, - NumMessages: 13, + NumEnums: 1, + NumMessages: 20, NumExtensions: 0, NumServices: 0, }, GoTypes: file_agent_livekit_agent_thread_proto_goTypes, DependencyIndexes: file_agent_livekit_agent_thread_proto_depIdxs, + EnumInfos: file_agent_livekit_agent_thread_proto_enumTypes, MessageInfos: file_agent_livekit_agent_thread_proto_msgTypes, }.Build() File_agent_livekit_agent_thread_proto = out.File diff --git a/livekit/agent/thread_schema.go b/livekit/agent/thread_schema.go new file mode 100644 index 000000000..fe2c9a8a9 --- /dev/null +++ b/livekit/agent/thread_schema.go @@ -0,0 +1,27 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package agent + +import _ "embed" + +// ThreadSchemaVersion is the newest conversation schema a thread's database +// can hold. +const ThreadSchemaVersion = 1 + +// ThreadSchemaV1 creates version 1 of the conversation schema, one statement +// per ";" (comments hold none). +// +//go:embed thread_schema_v1.sql +var ThreadSchemaV1 string diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql new file mode 100644 index 000000000..507a0c7f6 --- /dev/null +++ b/livekit/agent/thread_schema_v1.sql @@ -0,0 +1,78 @@ +-- Tables inside a thread's database, created by the agents framework. The +-- platform reads items and tasks. items.payload and tasks.history hold +-- livekit.agent.ChatContext.ChatItem as protobuf JSON. + +CREATE TABLE schema_version ( + version INTEGER NOT NULL, + applied_at INTEGER NOT NULL +); + +CREATE TABLE items ( + lane TEXT NOT NULL, + id TEXT PRIMARY KEY, + seq INTEGER NOT NULL, + type TEXT NOT NULL, + message_id TEXT, + payload JSONB NOT NULL +); +CREATE INDEX items_lane_seq ON items (lane, seq); +CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; + +CREATE TABLE tasks ( + task_id TEXT PRIMARY KEY, + lane TEXT NOT NULL, + seq INTEGER NOT NULL, + status TEXT NOT NULL, + message_id TEXT, + task JSONB NOT NULL, + agent TEXT NOT NULL, + due_at INTEGER, + deadline INTEGER, + ask JSONB, + status_message JSONB, + history JSONB NOT NULL, + artifacts JSONB, + parent TEXT, + push_configs JSONB, + attempts INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL +); +CREATE INDEX tasks_lane_status_due ON tasks (lane, status, due_at); +CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; + +-- One row per agent. +CREATE TABLE lanes ( + lane TEXT PRIMARY KEY, + version INTEGER NOT NULL, + state JSONB, + agent_stack JSONB, + lease_holder TEXT, + lease_token TEXT, + lease_until INTEGER NOT NULL DEFAULT 0, + waiter TEXT, + drained_seq INTEGER NOT NULL DEFAULT 0 +); + +-- The tables below are private to the framework. + +CREATE TABLE events ( + seq INTEGER PRIMARY KEY, + ts_ms INTEGER NOT NULL, + lane TEXT NOT NULL, + run_id TEXT NOT NULL, + author TEXT NOT NULL, + type TEXT NOT NULL, + payload JSONB NOT NULL +); +CREATE INDEX events_lane_seq ON events (lane, seq); + +CREATE TABLE inbox ( + seq INTEGER PRIMARY KEY, + lane TEXT NOT NULL, + task_id TEXT NOT NULL, + message_id TEXT NOT NULL, + message JSONB NOT NULL, + received_at INTEGER NOT NULL +); +CREATE INDEX inbox_lane_seq ON inbox (lane, seq); +CREATE UNIQUE INDEX inbox_lane_message ON inbox (lane, message_id); diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index a0edf0d9a..81e08c1c8 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -16,7 +16,8 @@ // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a // LiveKit phone number) can find it again. Served by cloud-protocol's -// AgentThreads. +// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared +// by the agents framework, which writes it, and the platform, which reads it. syntax = "proto3"; @@ -25,7 +26,9 @@ package livekit.agent; option go_package = "github.com/livekit/protocol/livekit/agent"; import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; +import "agent/livekit_agent_session.proto"; import "logger/options.proto"; message AgentThread { @@ -82,6 +85,33 @@ message AgentThread { message DeleteResponse {} + // The conversation, oldest first. + message HistoryRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + // One agent's history; empty for every agent's. + string agent = 2; + // Cursor from the previous HistoryResponse; empty for the first page. + string after = 3; + int32 limit = 4; + } + + message HistoryResponse { + repeated Item items = 1; + // Empty at the end. + string after = 2; + } + + // One chat item of an agent's history. Stored as the JSON of + // ChatContext.ChatItem (items.payload in thread_schema_v1.sql). + message Item { + string agent = 1; + // Position in the agent's history. + int64 seq = 2; + // The A2A message the item came from. + string message_id = 3; + ChatContext.ChatItem item = 4 [(logger.sensitivity) = SENSITIVITY_PII]; + } + message Thread { string thread_id = 1 [(logger.name) = "threadID"]; // Internal: the key the thread was created with, if any. @@ -98,3 +128,51 @@ message AgentThread { string database_id = 9; } } + +// AgentTask: a task an agent runs within a thread, the AgentTask class an agent +// tool submitted. +message AgentTask { + enum Status { + SUBMITTED = 0; + WORKING = 1; + INPUT_REQUIRED = 2; + COMPLETED = 3; + FAILED = 4; + CANCELED = 5; + } + + // A thread's tasks, oldest first. + message ListRequest { + string thread_id = 1 [(logger.name) = "threadID"]; + // One agent's tasks; empty for every agent's. + string agent = 2; + // Only tasks in this status; unset for every status. + optional Status status = 3; + // Cursor from the previous ListResponse; empty for the first page. + string after = 4; + int32 limit = 5; + } + + message ListResponse { + repeated Task tasks = 1; + // Empty at the end. + string after = 2; + } + + message Task { + string task_id = 1; + string thread_id = 2 [(logger.name) = "threadID"]; + // The agent running it. + string agent = 3; + // The AgentTask class. + string name = 4; + // Its constructor arguments. + google.protobuf.Struct arguments = 5 [(logger.sensitivity) = SENSITIVITY_PII]; + Status status = 6; + // The task's own conversation. + repeated ChatContext.ChatItem history = 7 [(logger.sensitivity) = SENSITIVITY_PII]; + // The task that submitted this one, if any. + string parent = 8; + google.protobuf.Timestamp updated_at = 9; + } +} From 75b36ffe506ca5d1244f54ab3f269d1e6a51c208 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:04:23 +0000 Subject: [PATCH 27/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index cd7d3ab0a..5d559e791 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -22,7 +22,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 1de6dc27caa168fde538076a918d3d68adca98b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:25:28 -0700 Subject: [PATCH 28/56] agent task: background work, four statuses --- .changeset/agent-threads.md | 2 +- livekit/agent/livekit_agent_thread.pb.go | 148 +++++++-------------- protobufs/agent/livekit_agent_thread.proto | 37 ++---- 3 files changed, 62 insertions(+), 125 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index cb39d7c09..5dcb34c91 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (the tasks agents run in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 5d559e791..a43139150 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -22,7 +22,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -32,7 +32,6 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" - structpb "google.golang.org/protobuf/types/known/structpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" @@ -49,31 +48,25 @@ const ( type AgentTask_Status int32 const ( - AgentTask_SUBMITTED AgentTask_Status = 0 - AgentTask_WORKING AgentTask_Status = 1 - AgentTask_INPUT_REQUIRED AgentTask_Status = 2 - AgentTask_COMPLETED AgentTask_Status = 3 - AgentTask_FAILED AgentTask_Status = 4 - AgentTask_CANCELED AgentTask_Status = 5 + AgentTask_RUNNING AgentTask_Status = 0 + AgentTask_COMPLETED AgentTask_Status = 1 + AgentTask_FAILED AgentTask_Status = 2 + AgentTask_CANCELED AgentTask_Status = 3 ) // Enum value maps for AgentTask_Status. var ( AgentTask_Status_name = map[int32]string{ - 0: "SUBMITTED", - 1: "WORKING", - 2: "INPUT_REQUIRED", - 3: "COMPLETED", - 4: "FAILED", - 5: "CANCELED", + 0: "RUNNING", + 1: "COMPLETED", + 2: "FAILED", + 3: "CANCELED", } AgentTask_Status_value = map[string]int32{ - "SUBMITTED": 0, - "WORKING": 1, - "INPUT_REQUIRED": 2, - "COMPLETED": 3, - "FAILED": 4, - "CANCELED": 5, + "RUNNING": 0, + "COMPLETED": 1, + "FAILED": 2, + "CANCELED": 3, } ) @@ -140,8 +133,8 @@ func (*AgentThread) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0} } -// AgentTask: a task an agent runs within a thread, the AgentTask class an agent -// tool submitted. +// AgentTask: background work an agent runs within a thread, such as a booking +// that takes minutes. type AgentTask struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -954,13 +947,11 @@ func (x *AgentThread_Thread) GetDatabaseId() string { type AgentTask_ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // One agent's tasks; empty for every agent's. - Agent string `protobuf:"bytes,2,opt,name=agent,proto3" json:"agent,omitempty"` - // Only tasks in this status; unset for every status. - Status *AgentTask_Status `protobuf:"varint,3,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status,oneof" json:"status,omitempty"` + // Unset for every status. + Status *AgentTask_Status `protobuf:"varint,2,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status,oneof" json:"status,omitempty"` // Cursor from the previous ListResponse; empty for the first page. - After string `protobuf:"bytes,4,opt,name=after,proto3" json:"after,omitempty"` - Limit int32 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"` + After string `protobuf:"bytes,3,opt,name=after,proto3" json:"after,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1002,18 +993,11 @@ func (x *AgentTask_ListRequest) GetThreadId() string { return "" } -func (x *AgentTask_ListRequest) GetAgent() string { - if x != nil { - return x.Agent - } - return "" -} - func (x *AgentTask_ListRequest) GetStatus() AgentTask_Status { if x != nil && x.Status != nil { return *x.Status } - return AgentTask_SUBMITTED + return AgentTask_RUNNING } func (x *AgentTask_ListRequest) GetAfter() string { @@ -1089,16 +1073,10 @@ type AgentTask_Task struct { ThreadId string `protobuf:"bytes,2,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` // The agent running it. Agent string `protobuf:"bytes,3,opt,name=agent,proto3" json:"agent,omitempty"` - // The AgentTask class. - Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` - // Its constructor arguments. - Arguments *structpb.Struct `protobuf:"bytes,5,opt,name=arguments,proto3" json:"arguments,omitempty"` - Status AgentTask_Status `protobuf:"varint,6,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status" json:"status,omitempty"` - // The task's own conversation. - History []*ChatContext_ChatItem `protobuf:"bytes,7,rep,name=history,proto3" json:"history,omitempty"` - // The task that submitted this one, if any. - Parent string `protobuf:"bytes,8,opt,name=parent,proto3" json:"parent,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + // The task's name, as the agent defines it. + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + Status AgentTask_Status `protobuf:"varint,5,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status" json:"status,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1161,32 +1139,11 @@ func (x *AgentTask_Task) GetName() string { return "" } -func (x *AgentTask_Task) GetArguments() *structpb.Struct { - if x != nil { - return x.Arguments - } - return nil -} - func (x *AgentTask_Task) GetStatus() AgentTask_Status { if x != nil { return x.Status } - return AgentTask_SUBMITTED -} - -func (x *AgentTask_Task) GetHistory() []*ChatContext_ChatItem { - if x != nil { - return x.History - } - return nil -} - -func (x *AgentTask_Task) GetParent() string { - if x != nil { - return x.Parent - } - return "" + return AgentTask_RUNNING } func (x *AgentTask_Task) GetUpdatedAt() *timestamppb.Timestamp { @@ -1200,7 +1157,7 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xe8\x0e\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xe8\x0e\n" + "\vAgentThread\x1a\x90\x02\n" + "\rCreateRequest\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x19\n" + @@ -1271,37 +1228,31 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x90\x06\n" + - "\tAgentTask\x1a\xc2\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbf\x04\n" + + "\tAgentTask\x1a\xac\x01\n" + "\vListRequest\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x14\n" + - "\x05agent\x18\x02 \x01(\tR\x05agent\x12<\n" + - "\x06status\x18\x03 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusH\x00R\x06status\x88\x01\x01\x12\x14\n" + - "\x05after\x18\x04 \x01(\tR\x05after\x12\x14\n" + - "\x05limit\x18\x05 \x01(\x05R\x05limitB\t\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12<\n" + + "\x06status\x18\x02 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusH\x00R\x06status\x88\x01\x01\x12\x14\n" + + "\x05after\x18\x03 \x01(\tR\x05after\x12\x14\n" + + "\x05limit\x18\x04 \x01(\x05R\x05limitB\t\n" + "\a_status\x1aY\n" + "\fListResponse\x123\n" + "\x05tasks\x18\x01 \x03(\v2\x1d.livekit.agent.AgentTask.TaskR\x05tasks\x12\x14\n" + - "\x05after\x18\x02 \x01(\tR\x05after\x1a\xff\x02\n" + + "\x05after\x18\x02 \x01(\tR\x05after\x1a\xe7\x01\n" + "\x04Task\x12\x17\n" + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12(\n" + "\tthread_id\x18\x02 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x14\n" + "\x05agent\x18\x03 \x01(\tR\x05agent\x12\x12\n" + - "\x04name\x18\x04 \x01(\tR\x04name\x12:\n" + - "\targuments\x18\x05 \x01(\v2\x17.google.protobuf.StructB\x03\xc0P\x01R\targuments\x127\n" + - "\x06status\x18\x06 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusR\x06status\x12B\n" + - "\ahistory\x18\a \x03(\v2#.livekit.agent.ChatContext.ChatItemB\x03\xc0P\x01R\ahistory\x12\x16\n" + - "\x06parent\x18\b \x01(\tR\x06parent\x129\n" + + "\x04name\x18\x04 \x01(\tR\x04name\x127\n" + + "\x06status\x18\x05 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusR\x06status\x129\n" + "\n" + - "updated_at\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"a\n" + - "\x06Status\x12\r\n" + - "\tSUBMITTED\x10\x00\x12\v\n" + - "\aWORKING\x10\x01\x12\x12\n" + - "\x0eINPUT_REQUIRED\x10\x02\x12\r\n" + - "\tCOMPLETED\x10\x03\x12\n" + + "updated_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\">\n" + + "\x06Status\x12\v\n" + + "\aRUNNING\x10\x00\x12\r\n" + + "\tCOMPLETED\x10\x01\x12\n" + "\n" + - "\x06FAILED\x10\x04\x12\f\n" + - "\bCANCELED\x10\x05B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" + "\x06FAILED\x10\x02\x12\f\n" + + "\bCANCELED\x10\x03B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" var ( file_agent_livekit_agent_thread_proto_rawDescOnce sync.Once @@ -1342,7 +1293,6 @@ var file_agent_livekit_agent_thread_proto_goTypes = []any{ (*durationpb.Duration)(nil), // 21: google.protobuf.Duration (*ChatContext_ChatItem)(nil), // 22: livekit.agent.ChatContext.ChatItem (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 24: google.protobuf.Struct } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ 15, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry @@ -1360,15 +1310,13 @@ var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ 21, // 12: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration 0, // 13: livekit.agent.AgentTask.ListRequest.status:type_name -> livekit.agent.AgentTask.Status 20, // 14: livekit.agent.AgentTask.ListResponse.tasks:type_name -> livekit.agent.AgentTask.Task - 24, // 15: livekit.agent.AgentTask.Task.arguments:type_name -> google.protobuf.Struct - 0, // 16: livekit.agent.AgentTask.Task.status:type_name -> livekit.agent.AgentTask.Status - 22, // 17: livekit.agent.AgentTask.Task.history:type_name -> livekit.agent.ChatContext.ChatItem - 23, // 18: livekit.agent.AgentTask.Task.updated_at:type_name -> google.protobuf.Timestamp - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 0, // 15: livekit.agent.AgentTask.Task.status:type_name -> livekit.agent.AgentTask.Status + 23, // 16: livekit.agent.AgentTask.Task.updated_at:type_name -> google.protobuf.Timestamp + 17, // [17:17] is the sub-list for method output_type + 17, // [17:17] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_agent_livekit_agent_thread_proto_init() } diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 81e08c1c8..358e94024 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -26,7 +26,6 @@ package livekit.agent; option go_package = "github.com/livekit/protocol/livekit/agent"; import "google/protobuf/duration.proto"; -import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "agent/livekit_agent_session.proto"; import "logger/options.proto"; @@ -129,28 +128,24 @@ message AgentThread { } } -// AgentTask: a task an agent runs within a thread, the AgentTask class an agent -// tool submitted. +// AgentTask: background work an agent runs within a thread, such as a booking +// that takes minutes. message AgentTask { enum Status { - SUBMITTED = 0; - WORKING = 1; - INPUT_REQUIRED = 2; - COMPLETED = 3; - FAILED = 4; - CANCELED = 5; + RUNNING = 0; + COMPLETED = 1; + FAILED = 2; + CANCELED = 3; } // A thread's tasks, oldest first. message ListRequest { string thread_id = 1 [(logger.name) = "threadID"]; - // One agent's tasks; empty for every agent's. - string agent = 2; - // Only tasks in this status; unset for every status. - optional Status status = 3; + // Unset for every status. + optional Status status = 2; // Cursor from the previous ListResponse; empty for the first page. - string after = 4; - int32 limit = 5; + string after = 3; + int32 limit = 4; } message ListResponse { @@ -164,15 +159,9 @@ message AgentTask { string thread_id = 2 [(logger.name) = "threadID"]; // The agent running it. string agent = 3; - // The AgentTask class. + // The task's name, as the agent defines it. string name = 4; - // Its constructor arguments. - google.protobuf.Struct arguments = 5 [(logger.sensitivity) = SENSITIVITY_PII]; - Status status = 6; - // The task's own conversation. - repeated ChatContext.ChatItem history = 7 [(logger.sensitivity) = SENSITIVITY_PII]; - // The task that submitted this one, if any. - string parent = 8; - google.protobuf.Timestamp updated_at = 9; + Status status = 5; + google.protobuf.Timestamp updated_at = 6; } } From 5719fb0bd87b8902b9aef79f023ae5ec14adb01c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:26:22 +0000 Subject: [PATCH 29/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index a43139150..f0e03e269 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -22,7 +22,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 22dafe5c0c3c5e66f794b8efb15673e4d87ca7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:33:19 -0700 Subject: [PATCH 30/56] agent thread: no conversation schema in protocol --- .changeset/agent-threads.md | 2 +- livekit/agent/livekit_agent_thread.pb.go | 10 +-- livekit/agent/thread_schema.go | 27 -------- livekit/agent/thread_schema_v1.sql | 78 ---------------------- protobufs/agent/livekit_agent_thread.proto | 8 +-- 5 files changed, 10 insertions(+), 115 deletions(-) delete mode 100644 livekit/agent/thread_schema.go delete mode 100644 livekit/agent/thread_schema_v1.sql diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index 5dcb34c91..d1884c021 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index f0e03e269..9445364c1 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -16,13 +16,13 @@ // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a // LiveKit phone number) can find it again. Served by cloud-protocol's -// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared -// by the agents framework, which writes it, and the platform, which reads it. +// AgentThreads. The agents framework writes the conversation into the +// database; the platform reads its history and tasks back. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -759,8 +759,8 @@ func (x *AgentThread_HistoryResponse) GetAfter() string { return "" } -// One chat item of an agent's history. Stored as the JSON of -// ChatContext.ChatItem (items.payload in thread_schema_v1.sql). +// One chat item of an agent's history, stored by the framework as +// ChatContext.ChatItem protobuf JSON. type AgentThread_Item struct { state protoimpl.MessageState `protogen:"open.v1"` Agent string `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` diff --git a/livekit/agent/thread_schema.go b/livekit/agent/thread_schema.go deleted file mode 100644 index fe2c9a8a9..000000000 --- a/livekit/agent/thread_schema.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2026 LiveKit, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package agent - -import _ "embed" - -// ThreadSchemaVersion is the newest conversation schema a thread's database -// can hold. -const ThreadSchemaVersion = 1 - -// ThreadSchemaV1 creates version 1 of the conversation schema, one statement -// per ";" (comments hold none). -// -//go:embed thread_schema_v1.sql -var ThreadSchemaV1 string diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql deleted file mode 100644 index 507a0c7f6..000000000 --- a/livekit/agent/thread_schema_v1.sql +++ /dev/null @@ -1,78 +0,0 @@ --- Tables inside a thread's database, created by the agents framework. The --- platform reads items and tasks. items.payload and tasks.history hold --- livekit.agent.ChatContext.ChatItem as protobuf JSON. - -CREATE TABLE schema_version ( - version INTEGER NOT NULL, - applied_at INTEGER NOT NULL -); - -CREATE TABLE items ( - lane TEXT NOT NULL, - id TEXT PRIMARY KEY, - seq INTEGER NOT NULL, - type TEXT NOT NULL, - message_id TEXT, - payload JSONB NOT NULL -); -CREATE INDEX items_lane_seq ON items (lane, seq); -CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; - -CREATE TABLE tasks ( - task_id TEXT PRIMARY KEY, - lane TEXT NOT NULL, - seq INTEGER NOT NULL, - status TEXT NOT NULL, - message_id TEXT, - task JSONB NOT NULL, - agent TEXT NOT NULL, - due_at INTEGER, - deadline INTEGER, - ask JSONB, - status_message JSONB, - history JSONB NOT NULL, - artifacts JSONB, - parent TEXT, - push_configs JSONB, - attempts INTEGER NOT NULL DEFAULT 0, - updated_at INTEGER NOT NULL -); -CREATE INDEX tasks_lane_status_due ON tasks (lane, status, due_at); -CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; - --- One row per agent. -CREATE TABLE lanes ( - lane TEXT PRIMARY KEY, - version INTEGER NOT NULL, - state JSONB, - agent_stack JSONB, - lease_holder TEXT, - lease_token TEXT, - lease_until INTEGER NOT NULL DEFAULT 0, - waiter TEXT, - drained_seq INTEGER NOT NULL DEFAULT 0 -); - --- The tables below are private to the framework. - -CREATE TABLE events ( - seq INTEGER PRIMARY KEY, - ts_ms INTEGER NOT NULL, - lane TEXT NOT NULL, - run_id TEXT NOT NULL, - author TEXT NOT NULL, - type TEXT NOT NULL, - payload JSONB NOT NULL -); -CREATE INDEX events_lane_seq ON events (lane, seq); - -CREATE TABLE inbox ( - seq INTEGER PRIMARY KEY, - lane TEXT NOT NULL, - task_id TEXT NOT NULL, - message_id TEXT NOT NULL, - message JSONB NOT NULL, - received_at INTEGER NOT NULL -); -CREATE INDEX inbox_lane_seq ON inbox (lane, seq); -CREATE UNIQUE INDEX inbox_lane_message ON inbox (lane, message_id); diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 358e94024..6efdba5d4 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -16,8 +16,8 @@ // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a // LiveKit phone number) can find it again. Served by cloud-protocol's -// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared -// by the agents framework, which writes it, and the platform, which reads it. +// AgentThreads. The agents framework writes the conversation into the +// database; the platform reads its history and tasks back. syntax = "proto3"; @@ -100,8 +100,8 @@ message AgentThread { string after = 2; } - // One chat item of an agent's history. Stored as the JSON of - // ChatContext.ChatItem (items.payload in thread_schema_v1.sql). + // One chat item of an agent's history, stored by the framework as + // ChatContext.ChatItem protobuf JSON. message Item { string agent = 1; // Position in the agent's history. From d7679dd1c8ca66179fb224c90412305d5c817f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:34:22 -0700 Subject: [PATCH 31/56] Revert "agent thread: no conversation schema in protocol" This reverts commit 22dafe5c0c3c5e66f794b8efb15673e4d87ca7ff. --- .changeset/agent-threads.md | 2 +- livekit/agent/livekit_agent_thread.pb.go | 10 +-- livekit/agent/thread_schema.go | 27 ++++++++ livekit/agent/thread_schema_v1.sql | 78 ++++++++++++++++++++++ protobufs/agent/livekit_agent_thread.proto | 8 +-- 5 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 livekit/agent/thread_schema.go create mode 100644 livekit/agent/thread_schema_v1.sql diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index d1884c021..5dcb34c91 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 9445364c1..f0e03e269 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -16,13 +16,13 @@ // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a // LiveKit phone number) can find it again. Served by cloud-protocol's -// AgentThreads. The agents framework writes the conversation into the -// database; the platform reads its history and tasks back. +// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared +// by the agents framework, which writes it, and the platform, which reads it. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent @@ -759,8 +759,8 @@ func (x *AgentThread_HistoryResponse) GetAfter() string { return "" } -// One chat item of an agent's history, stored by the framework as -// ChatContext.ChatItem protobuf JSON. +// One chat item of an agent's history. Stored as the JSON of +// ChatContext.ChatItem (items.payload in thread_schema_v1.sql). type AgentThread_Item struct { state protoimpl.MessageState `protogen:"open.v1"` Agent string `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` diff --git a/livekit/agent/thread_schema.go b/livekit/agent/thread_schema.go new file mode 100644 index 000000000..fe2c9a8a9 --- /dev/null +++ b/livekit/agent/thread_schema.go @@ -0,0 +1,27 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package agent + +import _ "embed" + +// ThreadSchemaVersion is the newest conversation schema a thread's database +// can hold. +const ThreadSchemaVersion = 1 + +// ThreadSchemaV1 creates version 1 of the conversation schema, one statement +// per ";" (comments hold none). +// +//go:embed thread_schema_v1.sql +var ThreadSchemaV1 string diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql new file mode 100644 index 000000000..507a0c7f6 --- /dev/null +++ b/livekit/agent/thread_schema_v1.sql @@ -0,0 +1,78 @@ +-- Tables inside a thread's database, created by the agents framework. The +-- platform reads items and tasks. items.payload and tasks.history hold +-- livekit.agent.ChatContext.ChatItem as protobuf JSON. + +CREATE TABLE schema_version ( + version INTEGER NOT NULL, + applied_at INTEGER NOT NULL +); + +CREATE TABLE items ( + lane TEXT NOT NULL, + id TEXT PRIMARY KEY, + seq INTEGER NOT NULL, + type TEXT NOT NULL, + message_id TEXT, + payload JSONB NOT NULL +); +CREATE INDEX items_lane_seq ON items (lane, seq); +CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; + +CREATE TABLE tasks ( + task_id TEXT PRIMARY KEY, + lane TEXT NOT NULL, + seq INTEGER NOT NULL, + status TEXT NOT NULL, + message_id TEXT, + task JSONB NOT NULL, + agent TEXT NOT NULL, + due_at INTEGER, + deadline INTEGER, + ask JSONB, + status_message JSONB, + history JSONB NOT NULL, + artifacts JSONB, + parent TEXT, + push_configs JSONB, + attempts INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL +); +CREATE INDEX tasks_lane_status_due ON tasks (lane, status, due_at); +CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; + +-- One row per agent. +CREATE TABLE lanes ( + lane TEXT PRIMARY KEY, + version INTEGER NOT NULL, + state JSONB, + agent_stack JSONB, + lease_holder TEXT, + lease_token TEXT, + lease_until INTEGER NOT NULL DEFAULT 0, + waiter TEXT, + drained_seq INTEGER NOT NULL DEFAULT 0 +); + +-- The tables below are private to the framework. + +CREATE TABLE events ( + seq INTEGER PRIMARY KEY, + ts_ms INTEGER NOT NULL, + lane TEXT NOT NULL, + run_id TEXT NOT NULL, + author TEXT NOT NULL, + type TEXT NOT NULL, + payload JSONB NOT NULL +); +CREATE INDEX events_lane_seq ON events (lane, seq); + +CREATE TABLE inbox ( + seq INTEGER PRIMARY KEY, + lane TEXT NOT NULL, + task_id TEXT NOT NULL, + message_id TEXT NOT NULL, + message JSONB NOT NULL, + received_at INTEGER NOT NULL +); +CREATE INDEX inbox_lane_seq ON inbox (lane, seq); +CREATE UNIQUE INDEX inbox_lane_message ON inbox (lane, message_id); diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 6efdba5d4..358e94024 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -16,8 +16,8 @@ // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a // LiveKit phone number) can find it again. Served by cloud-protocol's -// AgentThreads. The agents framework writes the conversation into the -// database; the platform reads its history and tasks back. +// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared +// by the agents framework, which writes it, and the platform, which reads it. syntax = "proto3"; @@ -100,8 +100,8 @@ message AgentThread { string after = 2; } - // One chat item of an agent's history, stored by the framework as - // ChatContext.ChatItem protobuf JSON. + // One chat item of an agent's history. Stored as the JSON of + // ChatContext.ChatItem (items.payload in thread_schema_v1.sql). message Item { string agent = 1; // Position in the agent's history. From b69697702a025215461dc3e649430d3c8be27653 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:34:24 +0000 Subject: [PATCH 32/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 9445364c1..703dc2c30 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -22,7 +22,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 74fdf84ce1b56ff6551c4d0013d6bd080b5becac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:44:09 -0700 Subject: [PATCH 33/56] thread schema: tasks keep queried columns, A2A fields in data, framework state apart; items drop type --- livekit/agent/thread_schema_v1.sql | 44 ++++++++++++++---------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index 507a0c7f6..8510ef354 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -1,46 +1,44 @@ -- Tables inside a thread's database, created by the agents framework. The --- platform reads items and tasks. items.payload and tasks.history hold --- livekit.agent.ChatContext.ChatItem as protobuf JSON. +-- platform reads items and tasks. CREATE TABLE schema_version ( version INTEGER NOT NULL, applied_at INTEGER NOT NULL ); +-- One chat item per row, in each agent's order. CREATE TABLE items ( - lane TEXT NOT NULL, id TEXT PRIMARY KEY, + lane TEXT NOT NULL, -- the agent seq INTEGER NOT NULL, - type TEXT NOT NULL, - message_id TEXT, - payload JSONB NOT NULL + message_id TEXT, -- the A2A messageId it came from, for idempotency + payload JSONB NOT NULL -- livekit.agent.ChatContext.ChatItem as protobuf JSON ); CREATE INDEX items_lane_seq ON items (lane, seq); CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; +-- One A2A task per row: the task of an incoming message, or a background +-- AgentTask (name set). The A2A Task is rebuilt as id = task_id, contextId = +-- the thread id, status = (status, status_message, updated_at), and the fields +-- of data. CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, - lane TEXT NOT NULL, + lane TEXT NOT NULL, -- the agent seq INTEGER NOT NULL, - status TEXT NOT NULL, - message_id TEXT, - task JSONB NOT NULL, - agent TEXT NOT NULL, - due_at INTEGER, - deadline INTEGER, - ask JSONB, - status_message JSONB, - history JSONB NOT NULL, - artifacts JSONB, - parent TEXT, - push_configs JSONB, + name TEXT, -- the AgentTask class + message_id TEXT, -- the A2A messageId that created it, for idempotency attempts INTEGER NOT NULL DEFAULT 0, - updated_at INTEGER NOT NULL + status TEXT NOT NULL, -- A2A TaskState + status_message JSONB, -- A2A Message + updated_at INTEGER NOT NULL, + data JSONB NOT NULL, -- A2A Task fields: history, artifacts, metadata + state JSONB -- private to the framework ); -CREATE INDEX tasks_lane_status_due ON tasks (lane, status, due_at); +CREATE INDEX tasks_lane_status ON tasks (lane, status); CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; --- One row per agent. +-- The tables below are private to the framework. + CREATE TABLE lanes ( lane TEXT PRIMARY KEY, version INTEGER NOT NULL, @@ -53,8 +51,6 @@ CREATE TABLE lanes ( drained_seq INTEGER NOT NULL DEFAULT 0 ); --- The tables below are private to the framework. - CREATE TABLE events ( seq INTEGER PRIMARY KEY, ts_ms INTEGER NOT NULL, From 72224f5fc9aab1961cefc30018f212dee6af3725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:45:30 -0700 Subject: [PATCH 34/56] thread schema: say where each table is used --- livekit/agent/thread_schema_v1.sql | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index 8510ef354..09bef0e51 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -1,12 +1,15 @@ --- Tables inside a thread's database, created by the agents framework. The --- platform reads items and tasks. +-- Tables inside a thread's database. The agents framework creates and writes +-- them (lk-agents-storage, statements/sql.rs). The platform reads items and +-- tasks (agent-db's threads service: AgentThreads.GetThreadHistory and +-- ListThreadTasks). CREATE TABLE schema_version ( version INTEGER NOT NULL, applied_at INTEGER NOT NULL ); --- One chat item per row, in each agent's order. +-- One chat item per row, in each agent's order. Used by: the framework, as +-- the model's history, and GetThreadHistory. CREATE TABLE items ( id TEXT PRIMARY KEY, lane TEXT NOT NULL, -- the agent @@ -18,27 +21,30 @@ CREATE INDEX items_lane_seq ON items (lane, seq); CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; -- One A2A task per row: the task of an incoming message, or a background --- AgentTask (name set). The A2A Task is rebuilt as id = task_id, contextId = --- the thread id, status = (status, status_message, updated_at), and the fields --- of data. +-- AgentTask (name set). Used by: the framework's A2A methods (message/send, +-- message/stream, tasks/get, tasks/list, tasks/cancel), which rebuild the A2A +-- Task as id = task_id, contextId = the thread id, status = (status, +-- status_message, updated_at), plus the fields of data. And ListThreadTasks, for the +-- rows with a name. CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, lane TEXT NOT NULL, -- the agent seq INTEGER NOT NULL, name TEXT, -- the AgentTask class message_id TEXT, -- the A2A messageId that created it, for idempotency - attempts INTEGER NOT NULL DEFAULT 0, + attempts INTEGER NOT NULL DEFAULT 0, -- pickups, a second one means the worker was lost status TEXT NOT NULL, -- A2A TaskState status_message JSONB, -- A2A Message updated_at INTEGER NOT NULL, data JSONB NOT NULL, -- A2A Task fields: history, artifacts, metadata - state JSONB -- private to the framework + state JSONB -- the AgentTask's arguments and pending question, for the framework to resume it ); CREATE INDEX tasks_lane_status ON tasks (lane, status); CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; --- The tables below are private to the framework. - +-- One row per agent: its lease (one worker runs an agent at a time), its +-- fencing version, state and agent stack. Used by: the framework, when a run +-- starts, commits and ends. CREATE TABLE lanes ( lane TEXT PRIMARY KEY, version INTEGER NOT NULL, @@ -51,6 +57,8 @@ CREATE TABLE lanes ( drained_seq INTEGER NOT NULL DEFAULT 0 ); +-- The run journal: every AgentSession event, plus run_started and run_ended. +-- Used by: the framework, to tell whether a run ended, and for replay. CREATE TABLE events ( seq INTEGER PRIMARY KEY, ts_ms INTEGER NOT NULL, @@ -62,6 +70,8 @@ CREATE TABLE events ( ); CREATE INDEX events_lane_seq ON events (lane, seq); +-- Messages that arrived while the agent was running. Used by: the framework, +-- which drains them in the next run. CREATE TABLE inbox ( seq INTEGER PRIMARY KEY, lane TEXT NOT NULL, From 6e1a63048e9121241c4b5f89d49921bfac171392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:45:39 -0700 Subject: [PATCH 35/56] thread schema: wrap comments --- livekit/agent/thread_schema_v1.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index 09bef0e51..da2c66bac 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -24,8 +24,8 @@ CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message -- AgentTask (name set). Used by: the framework's A2A methods (message/send, -- message/stream, tasks/get, tasks/list, tasks/cancel), which rebuild the A2A -- Task as id = task_id, contextId = the thread id, status = (status, --- status_message, updated_at), plus the fields of data. And ListThreadTasks, for the --- rows with a name. +-- status_message, updated_at), plus the fields of data. And ListThreadTasks, +-- for the rows with a name. CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, lane TEXT NOT NULL, -- the agent @@ -37,7 +37,7 @@ CREATE TABLE tasks ( status_message JSONB, -- A2A Message updated_at INTEGER NOT NULL, data JSONB NOT NULL, -- A2A Task fields: history, artifacts, metadata - state JSONB -- the AgentTask's arguments and pending question, for the framework to resume it + state JSONB -- AgentTask arguments and pending question, to resume it ); CREATE INDEX tasks_lane_status ON tasks (lane, status); CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; From c3070f23672da2724fe537160c0d84ed589614f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:55:53 -0700 Subject: [PATCH 36/56] thread schema: lane is agent, indexes for every query, no applied_at or events.author --- livekit/agent/thread_schema_v1.sql | 44 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index da2c66bac..b264345d9 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -1,24 +1,26 @@ -- Tables inside a thread's database. The agents framework creates and writes -- them (lk-agents-storage, statements/sql.rs). The platform reads items and -- tasks (agent-db's threads service: AgentThreads.GetThreadHistory and --- ListThreadTasks). +-- ListThreadTasks). An agent is a registered agent taking part in the thread, +-- across every channel. Two agents in one thread, through a delegation, write +-- separate rows. +-- Used by: the framework, which compares it to its own version on open. CREATE TABLE schema_version ( - version INTEGER NOT NULL, - applied_at INTEGER NOT NULL + version INTEGER NOT NULL ); -- One chat item per row, in each agent's order. Used by: the framework, as -- the model's history, and GetThreadHistory. CREATE TABLE items ( id TEXT PRIMARY KEY, - lane TEXT NOT NULL, -- the agent + agent TEXT NOT NULL, seq INTEGER NOT NULL, message_id TEXT, -- the A2A messageId it came from, for idempotency payload JSONB NOT NULL -- livekit.agent.ChatContext.ChatItem as protobuf JSON ); -CREATE INDEX items_lane_seq ON items (lane, seq); -CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; +CREATE INDEX items_agent_seq ON items (agent, seq); +CREATE UNIQUE INDEX items_agent_message ON items (agent, message_id) WHERE message_id IS NOT NULL; -- One A2A task per row: the task of an incoming message, or a background -- AgentTask (name set). Used by: the framework's A2A methods (message/send, @@ -28,7 +30,7 @@ CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message -- for the rows with a name. CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, - lane TEXT NOT NULL, -- the agent + agent TEXT NOT NULL, seq INTEGER NOT NULL, name TEXT, -- the AgentTask class message_id TEXT, -- the A2A messageId that created it, for idempotency @@ -39,14 +41,15 @@ CREATE TABLE tasks ( data JSONB NOT NULL, -- A2A Task fields: history, artifacts, metadata state JSONB -- AgentTask arguments and pending question, to resume it ); -CREATE INDEX tasks_lane_status ON tasks (lane, status); -CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; +CREATE INDEX tasks_agent_seq ON tasks (agent, seq); +CREATE INDEX tasks_agent_status ON tasks (agent, status, seq); +CREATE UNIQUE INDEX tasks_agent_message ON tasks (agent, message_id) WHERE message_id IS NOT NULL; -- One row per agent: its lease (one worker runs an agent at a time), its -- fencing version, state and agent stack. Used by: the framework, when a run -- starts, commits and ends. -CREATE TABLE lanes ( - lane TEXT PRIMARY KEY, +CREATE TABLE agents ( + agent TEXT PRIMARY KEY, version INTEGER NOT NULL, state JSONB, agent_stack JSONB, @@ -54,31 +57,32 @@ CREATE TABLE lanes ( lease_token TEXT, lease_until INTEGER NOT NULL DEFAULT 0, waiter TEXT, - drained_seq INTEGER NOT NULL DEFAULT 0 + drained_seq INTEGER NOT NULL DEFAULT 0 -- the last inbox row a run consumed ); -- The run journal: every AgentSession event, plus run_started and run_ended. --- Used by: the framework, to tell whether a run ended, and for replay. +-- Used by: the framework, to tell whether a run ended. CREATE TABLE events ( seq INTEGER PRIMARY KEY, ts_ms INTEGER NOT NULL, - lane TEXT NOT NULL, + agent TEXT NOT NULL, run_id TEXT NOT NULL, - author TEXT NOT NULL, type TEXT NOT NULL, payload JSONB NOT NULL ); -CREATE INDEX events_lane_seq ON events (lane, seq); +CREATE INDEX events_agent_seq ON events (agent, seq); +CREATE INDEX events_agent_run ON events (agent, run_id); -- Messages that arrived while the agent was running. Used by: the framework, -- which drains them in the next run. CREATE TABLE inbox ( seq INTEGER PRIMARY KEY, - lane TEXT NOT NULL, + agent TEXT NOT NULL, task_id TEXT NOT NULL, - message_id TEXT NOT NULL, + message_id TEXT NOT NULL, -- the A2A messageId, for idempotency message JSONB NOT NULL, received_at INTEGER NOT NULL ); -CREATE INDEX inbox_lane_seq ON inbox (lane, seq); -CREATE UNIQUE INDEX inbox_lane_message ON inbox (lane, message_id); +CREATE INDEX inbox_agent_seq ON inbox (agent, seq); +CREATE INDEX inbox_agent_task ON inbox (agent, task_id); +CREATE UNIQUE INDEX inbox_agent_message ON inbox (agent, message_id); From a1391874b6428553c871cb3c06459e11d5722536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 14:57:24 -0700 Subject: [PATCH 37/56] thread schema: no task attempts, status says a run was lost --- livekit/agent/thread_schema_v1.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index b264345d9..fbfdeba65 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -34,8 +34,7 @@ CREATE TABLE tasks ( seq INTEGER NOT NULL, name TEXT, -- the AgentTask class message_id TEXT, -- the A2A messageId that created it, for idempotency - attempts INTEGER NOT NULL DEFAULT 0, -- pickups, a second one means the worker was lost - status TEXT NOT NULL, -- A2A TaskState + status TEXT NOT NULL, -- A2A TaskState, working when a new run finds it means its worker was lost status_message JSONB, -- A2A Message updated_at INTEGER NOT NULL, data JSONB NOT NULL, -- A2A Task fields: history, artifacts, metadata From 44e0aa959093b418488e7d53c46d7c08931c3f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 15:20:21 -0700 Subject: [PATCH 38/56] agent thread: get by id only --- livekit/agent/livekit_agent_thread.pb.go | 62 ++++------------------ protobufs/agent/livekit_agent_thread.proto | 7 +-- 2 files changed, 11 insertions(+), 58 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index f0e03e269..c6ae1421c 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -15,14 +15,14 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a -// LiveKit phone number) can find it again. Served by cloud-protocol's +// LiveKit phone number) finds it again through create. Served by cloud-protocol's // AgentThreads. The conversation inside follows thread_schema_v1.sql, shared // by the agents framework, which writes it, and the platform, which reads it. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -298,12 +298,8 @@ func (x *AgentThread_CreateResponse) GetCreated() bool { } type AgentThread_GetRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Lookup: - // - // *AgentThread_GetRequest_ThreadId - // *AgentThread_GetRequest_Key - Lookup isAgentThread_GetRequest_Lookup `protobuf_oneof:"lookup"` + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -338,47 +334,13 @@ func (*AgentThread_GetRequest) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 2} } -func (x *AgentThread_GetRequest) GetLookup() isAgentThread_GetRequest_Lookup { - if x != nil { - return x.Lookup - } - return nil -} - func (x *AgentThread_GetRequest) GetThreadId() string { if x != nil { - if x, ok := x.Lookup.(*AgentThread_GetRequest_ThreadId); ok { - return x.ThreadId - } - } - return "" -} - -func (x *AgentThread_GetRequest) GetKey() string { - if x != nil { - if x, ok := x.Lookup.(*AgentThread_GetRequest_Key); ok { - return x.Key - } + return x.ThreadId } return "" } -type isAgentThread_GetRequest_Lookup interface { - isAgentThread_GetRequest_Lookup() -} - -type AgentThread_GetRequest_ThreadId struct { - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3,oneof"` -} - -type AgentThread_GetRequest_Key struct { - Key string `protobuf:"bytes,2,opt,name=key,proto3,oneof"` -} - -func (*AgentThread_GetRequest_ThreadId) isAgentThread_GetRequest_Lookup() {} - -func (*AgentThread_GetRequest_Key) isAgentThread_GetRequest_Lookup() {} - // The threads of one scope, newest first. type AgentThread_ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1157,7 +1119,7 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xe8\x0e\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xc8\x0e\n" + "\vAgentThread\x1a\x90\x02\n" + "\rCreateRequest\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x19\n" + @@ -1171,12 +1133,10 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ae\n" + "\x0eCreateResponse\x129\n" + "\x06thread\x18\x01 \x01(\v2!.livekit.agent.AgentThread.ThreadR\x06thread\x12\x18\n" + - "\acreated\x18\x02 \x01(\bR\acreated\x1aV\n" + + "\acreated\x18\x02 \x01(\bR\acreated\x1a6\n" + "\n" + - "GetRequest\x12*\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDH\x00R\bthreadId\x12\x12\n" + - "\x03key\x18\x02 \x01(\tH\x00R\x03keyB\b\n" + - "\x06lookup\x1aV\n" + + "GetRequest\x12(\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aV\n" + "\vListRequest\x12\x19\n" + "\x05scope\x18\x01 \x01(\tB\x03\xc0P\x01R\x05scope\x12\x16\n" + "\x06before\x18\x02 \x01(\tR\x06before\x12\x14\n" + @@ -1325,10 +1285,6 @@ func file_agent_livekit_agent_thread_proto_init() { return } file_agent_livekit_agent_session_proto_init() - file_agent_livekit_agent_thread_proto_msgTypes[4].OneofWrappers = []any{ - (*AgentThread_GetRequest_ThreadId)(nil), - (*AgentThread_GetRequest_Key)(nil), - } file_agent_livekit_agent_thread_proto_msgTypes[17].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 358e94024..513487959 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -15,7 +15,7 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, // an idle TTL and the AgentDB database created with it. An internal key (a -// LiveKit phone number) can find it again. Served by cloud-protocol's +// LiveKit phone number) finds it again through create. Served by cloud-protocol's // AgentThreads. The conversation inside follows thread_schema_v1.sql, shared // by the agents framework, which writes it, and the platform, which reads it. @@ -50,10 +50,7 @@ message AgentThread { } message GetRequest { - oneof lookup { - string thread_id = 1 [(logger.name) = "threadID"]; - string key = 2; - } + string thread_id = 1 [(logger.name) = "threadID"]; } // The threads of one scope, newest first. From 5f7279e3c76f3eb212bce403a28e2f704c367b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 15:21:01 -0700 Subject: [PATCH 39/56] agent thread: no key in the public messages; create returns the thread --- livekit/agent/livekit_agent_thread.pb.go | 298 ++++++++------------- protobufs/agent/livekit_agent_thread.proto | 36 +-- 2 files changed, 121 insertions(+), 213 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index c6ae1421c..b6ad4c112 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -14,9 +14,8 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, -// an idle TTL and the AgentDB database created with it. An internal key (a -// LiveKit phone number) finds it again through create. Served by cloud-protocol's -// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared +// an idle TTL and the AgentDB database created with it. Served by +// cloud-protocol's AgentThreads. The conversation inside follows thread_schema_v1.sql, shared // by the agents framework, which writes it, and the platform, which reads it. // Code generated by protoc-gen-go. DO NOT EDIT. @@ -173,15 +172,12 @@ func (*AgentTask) Descriptor() ([]byte, []int) { type AgentThread_CreateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Internal: when set, create is get-or-create on key (e.g. a LiveKit phone - // number). Not exposed by the public API. - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Groups threads, e.g. by user: an application-defined value, typically the // user's identity. An AgentThreadGrant is scoped by it. - Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` - Attributes map[string]string `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Scope string `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` + Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Unset takes the server default. - IdleTtl *durationpb.Duration `protobuf:"bytes,4,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + IdleTtl *durationpb.Duration `protobuf:"bytes,3,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -216,13 +212,6 @@ func (*AgentThread_CreateRequest) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 0} } -func (x *AgentThread_CreateRequest) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - func (x *AgentThread_CreateRequest) GetScope() string { if x != nil { return x.Scope @@ -244,59 +233,6 @@ func (x *AgentThread_CreateRequest) GetIdleTtl() *durationpb.Duration { return nil } -type AgentThread_CreateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Thread *AgentThread_Thread `protobuf:"bytes,1,opt,name=thread,proto3" json:"thread,omitempty"` - // False when the key already named a thread, which is returned as is. - Created bool `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AgentThread_CreateResponse) Reset() { - *x = AgentThread_CreateResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AgentThread_CreateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AgentThread_CreateResponse) ProtoMessage() {} - -func (x *AgentThread_CreateResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AgentThread_CreateResponse.ProtoReflect.Descriptor instead. -func (*AgentThread_CreateResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 1} -} - -func (x *AgentThread_CreateResponse) GetThread() *AgentThread_Thread { - if x != nil { - return x.Thread - } - return nil -} - -func (x *AgentThread_CreateResponse) GetCreated() bool { - if x != nil { - return x.Created - } - return false -} - type AgentThread_GetRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` @@ -306,7 +242,7 @@ type AgentThread_GetRequest struct { func (x *AgentThread_GetRequest) Reset() { *x = AgentThread_GetRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -318,7 +254,7 @@ func (x *AgentThread_GetRequest) String() string { func (*AgentThread_GetRequest) ProtoMessage() {} func (x *AgentThread_GetRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -331,7 +267,7 @@ func (x *AgentThread_GetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_GetRequest.ProtoReflect.Descriptor instead. func (*AgentThread_GetRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 2} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 1} } func (x *AgentThread_GetRequest) GetThreadId() string { @@ -354,7 +290,7 @@ type AgentThread_ListRequest struct { func (x *AgentThread_ListRequest) Reset() { *x = AgentThread_ListRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -366,7 +302,7 @@ func (x *AgentThread_ListRequest) String() string { func (*AgentThread_ListRequest) ProtoMessage() {} func (x *AgentThread_ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -379,7 +315,7 @@ func (x *AgentThread_ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_ListRequest.ProtoReflect.Descriptor instead. func (*AgentThread_ListRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 3} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 2} } func (x *AgentThread_ListRequest) GetScope() string { @@ -414,7 +350,7 @@ type AgentThread_ListResponse struct { func (x *AgentThread_ListResponse) Reset() { *x = AgentThread_ListResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -426,7 +362,7 @@ func (x *AgentThread_ListResponse) String() string { func (*AgentThread_ListResponse) ProtoMessage() {} func (x *AgentThread_ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -439,7 +375,7 @@ func (x *AgentThread_ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_ListResponse.ProtoReflect.Descriptor instead. func (*AgentThread_ListResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 4} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 3} } func (x *AgentThread_ListResponse) GetThreads() []*AgentThread_Thread { @@ -468,7 +404,7 @@ type AgentThread_UpdateRequest struct { func (x *AgentThread_UpdateRequest) Reset() { *x = AgentThread_UpdateRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -480,7 +416,7 @@ func (x *AgentThread_UpdateRequest) String() string { func (*AgentThread_UpdateRequest) ProtoMessage() {} func (x *AgentThread_UpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -493,7 +429,7 @@ func (x *AgentThread_UpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_UpdateRequest.ProtoReflect.Descriptor instead. func (*AgentThread_UpdateRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 5} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 4} } func (x *AgentThread_UpdateRequest) GetThreadId() string { @@ -526,7 +462,7 @@ type AgentThread_DeleteRequest struct { func (x *AgentThread_DeleteRequest) Reset() { *x = AgentThread_DeleteRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -538,7 +474,7 @@ func (x *AgentThread_DeleteRequest) String() string { func (*AgentThread_DeleteRequest) ProtoMessage() {} func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -551,7 +487,7 @@ func (x *AgentThread_DeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_DeleteRequest.ProtoReflect.Descriptor instead. func (*AgentThread_DeleteRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 5} } func (x *AgentThread_DeleteRequest) GetThreadId() string { @@ -569,7 +505,7 @@ type AgentThread_DeleteResponse struct { func (x *AgentThread_DeleteResponse) Reset() { *x = AgentThread_DeleteResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -581,7 +517,7 @@ func (x *AgentThread_DeleteResponse) String() string { func (*AgentThread_DeleteResponse) ProtoMessage() {} func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -594,7 +530,7 @@ func (x *AgentThread_DeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_DeleteResponse.ProtoReflect.Descriptor instead. func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} } // The conversation, oldest first. @@ -612,7 +548,7 @@ type AgentThread_HistoryRequest struct { func (x *AgentThread_HistoryRequest) Reset() { *x = AgentThread_HistoryRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -624,7 +560,7 @@ func (x *AgentThread_HistoryRequest) String() string { func (*AgentThread_HistoryRequest) ProtoMessage() {} func (x *AgentThread_HistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -637,7 +573,7 @@ func (x *AgentThread_HistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_HistoryRequest.ProtoReflect.Descriptor instead. func (*AgentThread_HistoryRequest) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} } func (x *AgentThread_HistoryRequest) GetThreadId() string { @@ -679,7 +615,7 @@ type AgentThread_HistoryResponse struct { func (x *AgentThread_HistoryResponse) Reset() { *x = AgentThread_HistoryResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -691,7 +627,7 @@ func (x *AgentThread_HistoryResponse) String() string { func (*AgentThread_HistoryResponse) ProtoMessage() {} func (x *AgentThread_HistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -704,7 +640,7 @@ func (x *AgentThread_HistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_HistoryResponse.ProtoReflect.Descriptor instead. func (*AgentThread_HistoryResponse) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} } func (x *AgentThread_HistoryResponse) GetItems() []*AgentThread_Item { @@ -737,7 +673,7 @@ type AgentThread_Item struct { func (x *AgentThread_Item) Reset() { *x = AgentThread_Item{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -749,7 +685,7 @@ func (x *AgentThread_Item) String() string { func (*AgentThread_Item) ProtoMessage() {} func (x *AgentThread_Item) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -762,7 +698,7 @@ func (x *AgentThread_Item) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_Item.ProtoReflect.Descriptor instead. func (*AgentThread_Item) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 10} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 9} } func (x *AgentThread_Item) GetAgent() string { @@ -794,27 +730,25 @@ func (x *AgentThread_Item) GetItem() *ChatContext_ChatItem { } type AgentThread_Thread struct { - state protoimpl.MessageState `protogen:"open.v1"` - ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // Internal: the key the thread was created with, if any. - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` - Attributes map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - LastActive *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` + Attributes map[string]string `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + LastActive *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` // last_active + idle_ttl; every update pushes it out. The thread and its // database are deleted once it passes. - ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` - IdleTtl *durationpb.Duration `protobuf:"bytes,8,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + IdleTtl *durationpb.Duration `protobuf:"bytes,7,opt,name=idle_ttl,json=idleTtl,proto3" json:"idle_ttl,omitempty"` // The thread's database ("DB_..."), created with the thread. - DatabaseId string `protobuf:"bytes,9,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` + DatabaseId string `protobuf:"bytes,8,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *AgentThread_Thread) Reset() { *x = AgentThread_Thread{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -826,7 +760,7 @@ func (x *AgentThread_Thread) String() string { func (*AgentThread_Thread) ProtoMessage() {} func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[13] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -839,7 +773,7 @@ func (x *AgentThread_Thread) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentThread_Thread.ProtoReflect.Descriptor instead. func (*AgentThread_Thread) Descriptor() ([]byte, []int) { - return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 11} + return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 10} } func (x *AgentThread_Thread) GetThreadId() string { @@ -849,13 +783,6 @@ func (x *AgentThread_Thread) GetThreadId() string { return "" } -func (x *AgentThread_Thread) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - func (x *AgentThread_Thread) GetScope() string { if x != nil { return x.Scope @@ -920,7 +847,7 @@ type AgentTask_ListRequest struct { func (x *AgentTask_ListRequest) Reset() { *x = AgentTask_ListRequest{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -932,7 +859,7 @@ func (x *AgentTask_ListRequest) String() string { func (*AgentTask_ListRequest) ProtoMessage() {} func (x *AgentTask_ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -987,7 +914,7 @@ type AgentTask_ListResponse struct { func (x *AgentTask_ListResponse) Reset() { *x = AgentTask_ListResponse{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[18] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -999,7 +926,7 @@ func (x *AgentTask_ListResponse) String() string { func (*AgentTask_ListResponse) ProtoMessage() {} func (x *AgentTask_ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[18] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1045,7 +972,7 @@ type AgentTask_Task struct { func (x *AgentTask_Task) Reset() { *x = AgentTask_Task{} - mi := &file_agent_livekit_agent_thread_proto_msgTypes[19] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1057,7 +984,7 @@ func (x *AgentTask_Task) String() string { func (*AgentTask_Task) ProtoMessage() {} func (x *AgentTask_Task) ProtoReflect() protoreflect.Message { - mi := &file_agent_livekit_agent_thread_proto_msgTypes[19] + mi := &file_agent_livekit_agent_thread_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1119,21 +1046,17 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xc8\x0e\n" + - "\vAgentThread\x1a\x90\x02\n" + - "\rCreateRequest\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x19\n" + - "\x05scope\x18\x02 \x01(\tB\x03\xc0P\x01R\x05scope\x12]\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xbd\r\n" + + "\vAgentThread\x1a\xfe\x01\n" + + "\rCreateRequest\x12\x19\n" + + "\x05scope\x18\x01 \x01(\tB\x03\xc0P\x01R\x05scope\x12]\n" + "\n" + - "attributes\x18\x03 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + + "attributes\x18\x02 \x03(\v28.livekit.agent.AgentThread.CreateRequest.AttributesEntryB\x03\xc0P\x01R\n" + "attributes\x124\n" + - "\bidle_ttl\x18\x04 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + + "\bidle_ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ae\n" + - "\x0eCreateResponse\x129\n" + - "\x06thread\x18\x01 \x01(\v2!.livekit.agent.AgentThread.ThreadR\x06thread\x12\x18\n" + - "\acreated\x18\x02 \x01(\bR\acreated\x1a6\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a6\n" + "\n" + "GetRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1aV\n" + @@ -1169,22 +1092,21 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\x03seq\x18\x02 \x01(\x03R\x03seq\x12\x1d\n" + "\n" + "message_id\x18\x03 \x01(\tR\tmessageId\x12<\n" + - "\x04item\x18\x04 \x01(\v2#.livekit.agent.ChatContext.ChatItemB\x03\xc0P\x01R\x04item\x1a\x80\x04\n" + + "\x04item\x18\x04 \x01(\v2#.livekit.agent.ChatContext.ChatItemB\x03\xc0P\x01R\x04item\x1a\xee\x03\n" + "\x06Thread\x12(\n" + - "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x10\n" + - "\x03key\x18\x02 \x01(\tR\x03key\x12\x19\n" + - "\x05scope\x18\x03 \x01(\tB\x03\xc0P\x01R\x05scope\x12V\n" + + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x19\n" + + "\x05scope\x18\x02 \x01(\tB\x03\xc0P\x01R\x05scope\x12V\n" + "\n" + - "attributes\x18\x04 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + + "attributes\x18\x03 \x03(\v21.livekit.agent.AgentThread.Thread.AttributesEntryB\x03\xc0P\x01R\n" + "attributes\x129\n" + "\n" + - "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + - "\vlast_active\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + + "\vlast_active\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\n" + "lastActive\x129\n" + "\n" + - "expires_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x124\n" + - "\bidle_ttl\x18\b \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x12\x1f\n" + - "\vdatabase_id\x18\t \x01(\tR\n" + + "expires_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x124\n" + + "\bidle_ttl\x18\a \x01(\v2\x19.google.protobuf.DurationR\aidleTtl\x12\x1f\n" + + "\vdatabase_id\x18\b \x01(\tR\n" + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + @@ -1227,56 +1149,54 @@ func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { } var file_agent_livekit_agent_thread_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_agent_livekit_agent_thread_proto_goTypes = []any{ (AgentTask_Status)(0), // 0: livekit.agent.AgentTask.Status (*AgentThread)(nil), // 1: livekit.agent.AgentThread (*AgentTask)(nil), // 2: livekit.agent.AgentTask (*AgentThread_CreateRequest)(nil), // 3: livekit.agent.AgentThread.CreateRequest - (*AgentThread_CreateResponse)(nil), // 4: livekit.agent.AgentThread.CreateResponse - (*AgentThread_GetRequest)(nil), // 5: livekit.agent.AgentThread.GetRequest - (*AgentThread_ListRequest)(nil), // 6: livekit.agent.AgentThread.ListRequest - (*AgentThread_ListResponse)(nil), // 7: livekit.agent.AgentThread.ListResponse - (*AgentThread_UpdateRequest)(nil), // 8: livekit.agent.AgentThread.UpdateRequest - (*AgentThread_DeleteRequest)(nil), // 9: livekit.agent.AgentThread.DeleteRequest - (*AgentThread_DeleteResponse)(nil), // 10: livekit.agent.AgentThread.DeleteResponse - (*AgentThread_HistoryRequest)(nil), // 11: livekit.agent.AgentThread.HistoryRequest - (*AgentThread_HistoryResponse)(nil), // 12: livekit.agent.AgentThread.HistoryResponse - (*AgentThread_Item)(nil), // 13: livekit.agent.AgentThread.Item - (*AgentThread_Thread)(nil), // 14: livekit.agent.AgentThread.Thread - nil, // 15: livekit.agent.AgentThread.CreateRequest.AttributesEntry - nil, // 16: livekit.agent.AgentThread.UpdateRequest.AttributesEntry - nil, // 17: livekit.agent.AgentThread.Thread.AttributesEntry - (*AgentTask_ListRequest)(nil), // 18: livekit.agent.AgentTask.ListRequest - (*AgentTask_ListResponse)(nil), // 19: livekit.agent.AgentTask.ListResponse - (*AgentTask_Task)(nil), // 20: livekit.agent.AgentTask.Task - (*durationpb.Duration)(nil), // 21: google.protobuf.Duration - (*ChatContext_ChatItem)(nil), // 22: livekit.agent.ChatContext.ChatItem - (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*AgentThread_GetRequest)(nil), // 4: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 5: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 6: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 7: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_DeleteRequest)(nil), // 8: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 9: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_HistoryRequest)(nil), // 10: livekit.agent.AgentThread.HistoryRequest + (*AgentThread_HistoryResponse)(nil), // 11: livekit.agent.AgentThread.HistoryResponse + (*AgentThread_Item)(nil), // 12: livekit.agent.AgentThread.Item + (*AgentThread_Thread)(nil), // 13: livekit.agent.AgentThread.Thread + nil, // 14: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 15: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 16: livekit.agent.AgentThread.Thread.AttributesEntry + (*AgentTask_ListRequest)(nil), // 17: livekit.agent.AgentTask.ListRequest + (*AgentTask_ListResponse)(nil), // 18: livekit.agent.AgentTask.ListResponse + (*AgentTask_Task)(nil), // 19: livekit.agent.AgentTask.Task + (*durationpb.Duration)(nil), // 20: google.protobuf.Duration + (*ChatContext_ChatItem)(nil), // 21: livekit.agent.ChatContext.ChatItem + (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ - 15, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry - 21, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration - 14, // 2: livekit.agent.AgentThread.CreateResponse.thread:type_name -> livekit.agent.AgentThread.Thread - 14, // 3: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread - 16, // 4: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry - 21, // 5: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration - 13, // 6: livekit.agent.AgentThread.HistoryResponse.items:type_name -> livekit.agent.AgentThread.Item - 22, // 7: livekit.agent.AgentThread.Item.item:type_name -> livekit.agent.ChatContext.ChatItem - 17, // 8: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry - 23, // 9: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp - 23, // 10: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp - 23, // 11: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp - 21, // 12: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration - 0, // 13: livekit.agent.AgentTask.ListRequest.status:type_name -> livekit.agent.AgentTask.Status - 20, // 14: livekit.agent.AgentTask.ListResponse.tasks:type_name -> livekit.agent.AgentTask.Task - 0, // 15: livekit.agent.AgentTask.Task.status:type_name -> livekit.agent.AgentTask.Status - 23, // 16: livekit.agent.AgentTask.Task.updated_at:type_name -> google.protobuf.Timestamp - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 14, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry + 20, // 1: livekit.agent.AgentThread.CreateRequest.idle_ttl:type_name -> google.protobuf.Duration + 13, // 2: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread + 15, // 3: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry + 20, // 4: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration + 12, // 5: livekit.agent.AgentThread.HistoryResponse.items:type_name -> livekit.agent.AgentThread.Item + 21, // 6: livekit.agent.AgentThread.Item.item:type_name -> livekit.agent.ChatContext.ChatItem + 16, // 7: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry + 22, // 8: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp + 22, // 9: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp + 22, // 10: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp + 20, // 11: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration + 0, // 12: livekit.agent.AgentTask.ListRequest.status:type_name -> livekit.agent.AgentTask.Status + 19, // 13: livekit.agent.AgentTask.ListResponse.tasks:type_name -> livekit.agent.AgentTask.Task + 0, // 14: livekit.agent.AgentTask.Task.status:type_name -> livekit.agent.AgentTask.Status + 22, // 15: livekit.agent.AgentTask.Task.updated_at:type_name -> google.protobuf.Timestamp + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file_agent_livekit_agent_thread_proto_init() } @@ -1285,14 +1205,14 @@ func file_agent_livekit_agent_thread_proto_init() { return } file_agent_livekit_agent_session_proto_init() - file_agent_livekit_agent_thread_proto_msgTypes[17].OneofWrappers = []any{} + file_agent_livekit_agent_thread_proto_msgTypes[16].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_livekit_agent_thread_proto_rawDesc), len(file_agent_livekit_agent_thread_proto_rawDesc)), NumEnums: 1, - NumMessages: 20, + NumMessages: 19, NumExtensions: 0, NumServices: 0, }, diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 513487959..eb1b86291 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -14,9 +14,8 @@ // AgentThread: the durable conversation between an agent and a person, across // channels and over time. Identified by an "AT_" id. Holds a scope, attributes, -// an idle TTL and the AgentDB database created with it. An internal key (a -// LiveKit phone number) finds it again through create. Served by cloud-protocol's -// AgentThreads. The conversation inside follows thread_schema_v1.sql, shared +// an idle TTL and the AgentDB database created with it. Served by +// cloud-protocol's AgentThreads. The conversation inside follows thread_schema_v1.sql, shared // by the agents framework, which writes it, and the platform, which reads it. syntax = "proto3"; @@ -32,21 +31,12 @@ import "logger/options.proto"; message AgentThread { message CreateRequest { - // Internal: when set, create is get-or-create on key (e.g. a LiveKit phone - // number). Not exposed by the public API. - string key = 1; // Groups threads, e.g. by user: an application-defined value, typically the // user's identity. An AgentThreadGrant is scoped by it. - string scope = 2 [(logger.sensitivity) = SENSITIVITY_PII]; - map attributes = 3 [(logger.sensitivity) = SENSITIVITY_PII]; + string scope = 1 [(logger.sensitivity) = SENSITIVITY_PII]; + map attributes = 2 [(logger.sensitivity) = SENSITIVITY_PII]; // Unset takes the server default. - google.protobuf.Duration idle_ttl = 4; - } - - message CreateResponse { - Thread thread = 1; - // False when the key already named a thread, which is returned as is. - bool created = 2; + google.protobuf.Duration idle_ttl = 3; } message GetRequest { @@ -110,18 +100,16 @@ message AgentThread { message Thread { string thread_id = 1 [(logger.name) = "threadID"]; - // Internal: the key the thread was created with, if any. - string key = 2; - string scope = 3 [(logger.sensitivity) = SENSITIVITY_PII]; - map attributes = 4 [(logger.sensitivity) = SENSITIVITY_PII]; - google.protobuf.Timestamp created_at = 5; - google.protobuf.Timestamp last_active = 6; + string scope = 2 [(logger.sensitivity) = SENSITIVITY_PII]; + map attributes = 3 [(logger.sensitivity) = SENSITIVITY_PII]; + google.protobuf.Timestamp created_at = 4; + google.protobuf.Timestamp last_active = 5; // last_active + idle_ttl; every update pushes it out. The thread and its // database are deleted once it passes. - google.protobuf.Timestamp expires_at = 7; - google.protobuf.Duration idle_ttl = 8; + google.protobuf.Timestamp expires_at = 6; + google.protobuf.Duration idle_ttl = 7; // The thread's database ("DB_..."), created with the thread. - string database_id = 9; + string database_id = 8; } } From 0a02ce59a1bf5d61f21296f16ac06400b3a74838 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 22:21:47 +0000 Subject: [PATCH 40/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index b6ad4c112..2838e65da 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 6de4de21c30dea9e3b33e6e8766afdf40cbd1e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 15:48:31 -0700 Subject: [PATCH 41/56] agent thread: GetItems and GetTasks requests --- .changeset/agent-threads.md | 2 +- livekit/agent/livekit_agent_thread.pb.go | 172 ++++++++++----------- livekit/agent/thread_schema_v1.sql | 8 +- protobufs/agent/livekit_agent_thread.proto | 18 +-- 4 files changed, 100 insertions(+), 100 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index 5dcb34c91..7ce21f187 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its history as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its items as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 2838e65da..0b4854fef 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -533,33 +533,33 @@ func (*AgentThread_DeleteResponse) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 6} } -// The conversation, oldest first. -type AgentThread_HistoryRequest struct { +// The thread's chat items, oldest first. +type AgentThread_GetItemsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // One agent's history; empty for every agent's. + // One agent's items; empty for every agent's. Agent string `protobuf:"bytes,2,opt,name=agent,proto3" json:"agent,omitempty"` - // Cursor from the previous HistoryResponse; empty for the first page. + // Cursor from the previous GetItemsResponse; empty for the first page. After string `protobuf:"bytes,3,opt,name=after,proto3" json:"after,omitempty"` Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AgentThread_HistoryRequest) Reset() { - *x = AgentThread_HistoryRequest{} +func (x *AgentThread_GetItemsRequest) Reset() { + *x = AgentThread_GetItemsRequest{} mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentThread_HistoryRequest) String() string { +func (x *AgentThread_GetItemsRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentThread_HistoryRequest) ProtoMessage() {} +func (*AgentThread_GetItemsRequest) ProtoMessage() {} -func (x *AgentThread_HistoryRequest) ProtoReflect() protoreflect.Message { +func (x *AgentThread_GetItemsRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_livekit_agent_thread_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -571,40 +571,40 @@ func (x *AgentThread_HistoryRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AgentThread_HistoryRequest.ProtoReflect.Descriptor instead. -func (*AgentThread_HistoryRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentThread_GetItemsRequest.ProtoReflect.Descriptor instead. +func (*AgentThread_GetItemsRequest) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 7} } -func (x *AgentThread_HistoryRequest) GetThreadId() string { +func (x *AgentThread_GetItemsRequest) GetThreadId() string { if x != nil { return x.ThreadId } return "" } -func (x *AgentThread_HistoryRequest) GetAgent() string { +func (x *AgentThread_GetItemsRequest) GetAgent() string { if x != nil { return x.Agent } return "" } -func (x *AgentThread_HistoryRequest) GetAfter() string { +func (x *AgentThread_GetItemsRequest) GetAfter() string { if x != nil { return x.After } return "" } -func (x *AgentThread_HistoryRequest) GetLimit() int32 { +func (x *AgentThread_GetItemsRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } -type AgentThread_HistoryResponse struct { +type AgentThread_GetItemsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Items []*AgentThread_Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` // Empty at the end. @@ -613,20 +613,20 @@ type AgentThread_HistoryResponse struct { sizeCache protoimpl.SizeCache } -func (x *AgentThread_HistoryResponse) Reset() { - *x = AgentThread_HistoryResponse{} +func (x *AgentThread_GetItemsResponse) Reset() { + *x = AgentThread_GetItemsResponse{} mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentThread_HistoryResponse) String() string { +func (x *AgentThread_GetItemsResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentThread_HistoryResponse) ProtoMessage() {} +func (*AgentThread_GetItemsResponse) ProtoMessage() {} -func (x *AgentThread_HistoryResponse) ProtoReflect() protoreflect.Message { +func (x *AgentThread_GetItemsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_livekit_agent_thread_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -638,26 +638,26 @@ func (x *AgentThread_HistoryResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AgentThread_HistoryResponse.ProtoReflect.Descriptor instead. -func (*AgentThread_HistoryResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentThread_GetItemsResponse.ProtoReflect.Descriptor instead. +func (*AgentThread_GetItemsResponse) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{0, 8} } -func (x *AgentThread_HistoryResponse) GetItems() []*AgentThread_Item { +func (x *AgentThread_GetItemsResponse) GetItems() []*AgentThread_Item { if x != nil { return x.Items } return nil } -func (x *AgentThread_HistoryResponse) GetAfter() string { +func (x *AgentThread_GetItemsResponse) GetAfter() string { if x != nil { return x.After } return "" } -// One chat item of an agent's history. Stored as the JSON of +// One chat item of an agent. Stored as the JSON of // ChatContext.ChatItem (items.payload in thread_schema_v1.sql). type AgentThread_Item struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -833,32 +833,32 @@ func (x *AgentThread_Thread) GetDatabaseId() string { } // A thread's tasks, oldest first. -type AgentTask_ListRequest struct { +type AgentTask_GetTasksRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` // Unset for every status. Status *AgentTask_Status `protobuf:"varint,2,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status,oneof" json:"status,omitempty"` - // Cursor from the previous ListResponse; empty for the first page. + // Cursor from the previous GetTasksResponse; empty for the first page. After string `protobuf:"bytes,3,opt,name=after,proto3" json:"after,omitempty"` Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AgentTask_ListRequest) Reset() { - *x = AgentTask_ListRequest{} +func (x *AgentTask_GetTasksRequest) Reset() { + *x = AgentTask_GetTasksRequest{} mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentTask_ListRequest) String() string { +func (x *AgentTask_GetTasksRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentTask_ListRequest) ProtoMessage() {} +func (*AgentTask_GetTasksRequest) ProtoMessage() {} -func (x *AgentTask_ListRequest) ProtoReflect() protoreflect.Message { +func (x *AgentTask_GetTasksRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_livekit_agent_thread_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -870,40 +870,40 @@ func (x *AgentTask_ListRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AgentTask_ListRequest.ProtoReflect.Descriptor instead. -func (*AgentTask_ListRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentTask_GetTasksRequest.ProtoReflect.Descriptor instead. +func (*AgentTask_GetTasksRequest) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{1, 0} } -func (x *AgentTask_ListRequest) GetThreadId() string { +func (x *AgentTask_GetTasksRequest) GetThreadId() string { if x != nil { return x.ThreadId } return "" } -func (x *AgentTask_ListRequest) GetStatus() AgentTask_Status { +func (x *AgentTask_GetTasksRequest) GetStatus() AgentTask_Status { if x != nil && x.Status != nil { return *x.Status } return AgentTask_RUNNING } -func (x *AgentTask_ListRequest) GetAfter() string { +func (x *AgentTask_GetTasksRequest) GetAfter() string { if x != nil { return x.After } return "" } -func (x *AgentTask_ListRequest) GetLimit() int32 { +func (x *AgentTask_GetTasksRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } -type AgentTask_ListResponse struct { +type AgentTask_GetTasksResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Tasks []*AgentTask_Task `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"` // Empty at the end. @@ -912,20 +912,20 @@ type AgentTask_ListResponse struct { sizeCache protoimpl.SizeCache } -func (x *AgentTask_ListResponse) Reset() { - *x = AgentTask_ListResponse{} +func (x *AgentTask_GetTasksResponse) Reset() { + *x = AgentTask_GetTasksResponse{} mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentTask_ListResponse) String() string { +func (x *AgentTask_GetTasksResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentTask_ListResponse) ProtoMessage() {} +func (*AgentTask_GetTasksResponse) ProtoMessage() {} -func (x *AgentTask_ListResponse) ProtoReflect() protoreflect.Message { +func (x *AgentTask_GetTasksResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_livekit_agent_thread_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -937,19 +937,19 @@ func (x *AgentTask_ListResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AgentTask_ListResponse.ProtoReflect.Descriptor instead. -func (*AgentTask_ListResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentTask_GetTasksResponse.ProtoReflect.Descriptor instead. +func (*AgentTask_GetTasksResponse) Descriptor() ([]byte, []int) { return file_agent_livekit_agent_thread_proto_rawDescGZIP(), []int{1, 1} } -func (x *AgentTask_ListResponse) GetTasks() []*AgentTask_Task { +func (x *AgentTask_GetTasksResponse) GetTasks() []*AgentTask_Task { if x != nil { return x.Tasks } return nil } -func (x *AgentTask_ListResponse) GetAfter() string { +func (x *AgentTask_GetTasksResponse) GetAfter() string { if x != nil { return x.After } @@ -1046,7 +1046,7 @@ var File_agent_livekit_agent_thread_proto protoreflect.FileDescriptor const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\n" + - " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xbd\r\n" + + " agent/livekit_agent_thread.proto\x12\rlivekit.agent\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!agent/livekit_agent_session.proto\x1a\x14logger/options.proto\"\xbf\r\n" + "\vAgentThread\x1a\xfe\x01\n" + "\rCreateRequest\x12\x19\n" + "\x05scope\x18\x01 \x01(\tB\x03\xc0P\x01R\x05scope\x12]\n" + @@ -1078,13 +1078,13 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a9\n" + "\rDeleteRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x1a\x10\n" + - "\x0eDeleteResponse\x1a|\n" + - "\x0eHistoryRequest\x12(\n" + + "\x0eDeleteResponse\x1a}\n" + + "\x0fGetItemsRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x14\n" + "\x05agent\x18\x02 \x01(\tR\x05agent\x12\x14\n" + "\x05after\x18\x03 \x01(\tR\x05after\x12\x14\n" + - "\x05limit\x18\x04 \x01(\x05R\x05limit\x1a^\n" + - "\x0fHistoryResponse\x125\n" + + "\x05limit\x18\x04 \x01(\x05R\x05limit\x1a_\n" + + "\x10GetItemsResponse\x125\n" + "\x05items\x18\x01 \x03(\v2\x1f.livekit.agent.AgentThread.ItemR\x05items\x12\x14\n" + "\x05after\x18\x02 \x01(\tR\x05after\x1a\x8b\x01\n" + "\x04Item\x12\x14\n" + @@ -1110,15 +1110,15 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbf\x04\n" + - "\tAgentTask\x1a\xac\x01\n" + - "\vListRequest\x12(\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc7\x04\n" + + "\tAgentTask\x1a\xb0\x01\n" + + "\x0fGetTasksRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12<\n" + "\x06status\x18\x02 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusH\x00R\x06status\x88\x01\x01\x12\x14\n" + "\x05after\x18\x03 \x01(\tR\x05after\x12\x14\n" + "\x05limit\x18\x04 \x01(\x05R\x05limitB\t\n" + - "\a_status\x1aY\n" + - "\fListResponse\x123\n" + + "\a_status\x1a]\n" + + "\x10GetTasksResponse\x123\n" + "\x05tasks\x18\x01 \x03(\v2\x1d.livekit.agent.AgentTask.TaskR\x05tasks\x12\x14\n" + "\x05after\x18\x02 \x01(\tR\x05after\x1a\xe7\x01\n" + "\x04Task\x12\x17\n" + @@ -1151,29 +1151,29 @@ func file_agent_livekit_agent_thread_proto_rawDescGZIP() []byte { var file_agent_livekit_agent_thread_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_agent_livekit_agent_thread_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_agent_livekit_agent_thread_proto_goTypes = []any{ - (AgentTask_Status)(0), // 0: livekit.agent.AgentTask.Status - (*AgentThread)(nil), // 1: livekit.agent.AgentThread - (*AgentTask)(nil), // 2: livekit.agent.AgentTask - (*AgentThread_CreateRequest)(nil), // 3: livekit.agent.AgentThread.CreateRequest - (*AgentThread_GetRequest)(nil), // 4: livekit.agent.AgentThread.GetRequest - (*AgentThread_ListRequest)(nil), // 5: livekit.agent.AgentThread.ListRequest - (*AgentThread_ListResponse)(nil), // 6: livekit.agent.AgentThread.ListResponse - (*AgentThread_UpdateRequest)(nil), // 7: livekit.agent.AgentThread.UpdateRequest - (*AgentThread_DeleteRequest)(nil), // 8: livekit.agent.AgentThread.DeleteRequest - (*AgentThread_DeleteResponse)(nil), // 9: livekit.agent.AgentThread.DeleteResponse - (*AgentThread_HistoryRequest)(nil), // 10: livekit.agent.AgentThread.HistoryRequest - (*AgentThread_HistoryResponse)(nil), // 11: livekit.agent.AgentThread.HistoryResponse - (*AgentThread_Item)(nil), // 12: livekit.agent.AgentThread.Item - (*AgentThread_Thread)(nil), // 13: livekit.agent.AgentThread.Thread - nil, // 14: livekit.agent.AgentThread.CreateRequest.AttributesEntry - nil, // 15: livekit.agent.AgentThread.UpdateRequest.AttributesEntry - nil, // 16: livekit.agent.AgentThread.Thread.AttributesEntry - (*AgentTask_ListRequest)(nil), // 17: livekit.agent.AgentTask.ListRequest - (*AgentTask_ListResponse)(nil), // 18: livekit.agent.AgentTask.ListResponse - (*AgentTask_Task)(nil), // 19: livekit.agent.AgentTask.Task - (*durationpb.Duration)(nil), // 20: google.protobuf.Duration - (*ChatContext_ChatItem)(nil), // 21: livekit.agent.ChatContext.ChatItem - (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp + (AgentTask_Status)(0), // 0: livekit.agent.AgentTask.Status + (*AgentThread)(nil), // 1: livekit.agent.AgentThread + (*AgentTask)(nil), // 2: livekit.agent.AgentTask + (*AgentThread_CreateRequest)(nil), // 3: livekit.agent.AgentThread.CreateRequest + (*AgentThread_GetRequest)(nil), // 4: livekit.agent.AgentThread.GetRequest + (*AgentThread_ListRequest)(nil), // 5: livekit.agent.AgentThread.ListRequest + (*AgentThread_ListResponse)(nil), // 6: livekit.agent.AgentThread.ListResponse + (*AgentThread_UpdateRequest)(nil), // 7: livekit.agent.AgentThread.UpdateRequest + (*AgentThread_DeleteRequest)(nil), // 8: livekit.agent.AgentThread.DeleteRequest + (*AgentThread_DeleteResponse)(nil), // 9: livekit.agent.AgentThread.DeleteResponse + (*AgentThread_GetItemsRequest)(nil), // 10: livekit.agent.AgentThread.GetItemsRequest + (*AgentThread_GetItemsResponse)(nil), // 11: livekit.agent.AgentThread.GetItemsResponse + (*AgentThread_Item)(nil), // 12: livekit.agent.AgentThread.Item + (*AgentThread_Thread)(nil), // 13: livekit.agent.AgentThread.Thread + nil, // 14: livekit.agent.AgentThread.CreateRequest.AttributesEntry + nil, // 15: livekit.agent.AgentThread.UpdateRequest.AttributesEntry + nil, // 16: livekit.agent.AgentThread.Thread.AttributesEntry + (*AgentTask_GetTasksRequest)(nil), // 17: livekit.agent.AgentTask.GetTasksRequest + (*AgentTask_GetTasksResponse)(nil), // 18: livekit.agent.AgentTask.GetTasksResponse + (*AgentTask_Task)(nil), // 19: livekit.agent.AgentTask.Task + (*durationpb.Duration)(nil), // 20: google.protobuf.Duration + (*ChatContext_ChatItem)(nil), // 21: livekit.agent.ChatContext.ChatItem + (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp } var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ 14, // 0: livekit.agent.AgentThread.CreateRequest.attributes:type_name -> livekit.agent.AgentThread.CreateRequest.AttributesEntry @@ -1181,15 +1181,15 @@ var file_agent_livekit_agent_thread_proto_depIdxs = []int32{ 13, // 2: livekit.agent.AgentThread.ListResponse.threads:type_name -> livekit.agent.AgentThread.Thread 15, // 3: livekit.agent.AgentThread.UpdateRequest.attributes:type_name -> livekit.agent.AgentThread.UpdateRequest.AttributesEntry 20, // 4: livekit.agent.AgentThread.UpdateRequest.idle_ttl:type_name -> google.protobuf.Duration - 12, // 5: livekit.agent.AgentThread.HistoryResponse.items:type_name -> livekit.agent.AgentThread.Item + 12, // 5: livekit.agent.AgentThread.GetItemsResponse.items:type_name -> livekit.agent.AgentThread.Item 21, // 6: livekit.agent.AgentThread.Item.item:type_name -> livekit.agent.ChatContext.ChatItem 16, // 7: livekit.agent.AgentThread.Thread.attributes:type_name -> livekit.agent.AgentThread.Thread.AttributesEntry 22, // 8: livekit.agent.AgentThread.Thread.created_at:type_name -> google.protobuf.Timestamp 22, // 9: livekit.agent.AgentThread.Thread.last_active:type_name -> google.protobuf.Timestamp 22, // 10: livekit.agent.AgentThread.Thread.expires_at:type_name -> google.protobuf.Timestamp 20, // 11: livekit.agent.AgentThread.Thread.idle_ttl:type_name -> google.protobuf.Duration - 0, // 12: livekit.agent.AgentTask.ListRequest.status:type_name -> livekit.agent.AgentTask.Status - 19, // 13: livekit.agent.AgentTask.ListResponse.tasks:type_name -> livekit.agent.AgentTask.Task + 0, // 12: livekit.agent.AgentTask.GetTasksRequest.status:type_name -> livekit.agent.AgentTask.Status + 19, // 13: livekit.agent.AgentTask.GetTasksResponse.tasks:type_name -> livekit.agent.AgentTask.Task 0, // 14: livekit.agent.AgentTask.Task.status:type_name -> livekit.agent.AgentTask.Status 22, // 15: livekit.agent.AgentTask.Task.updated_at:type_name -> google.protobuf.Timestamp 16, // [16:16] is the sub-list for method output_type diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index fbfdeba65..63e941849 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -1,7 +1,7 @@ -- Tables inside a thread's database. The agents framework creates and writes -- them (lk-agents-storage, statements/sql.rs). The platform reads items and --- tasks (agent-db's threads service: AgentThreads.GetThreadHistory and --- ListThreadTasks). An agent is a registered agent taking part in the thread, +-- tasks (agent-db's threads service: AgentThreads.GetThreadItems and +-- GetThreadTasks). An agent is a registered agent taking part in the thread, -- across every channel. Two agents in one thread, through a delegation, write -- separate rows. @@ -11,7 +11,7 @@ CREATE TABLE schema_version ( ); -- One chat item per row, in each agent's order. Used by: the framework, as --- the model's history, and GetThreadHistory. +-- the model's history, and GetThreadItems. CREATE TABLE items ( id TEXT PRIMARY KEY, agent TEXT NOT NULL, @@ -26,7 +26,7 @@ CREATE UNIQUE INDEX items_agent_message ON items (agent, message_id) WHERE messa -- AgentTask (name set). Used by: the framework's A2A methods (message/send, -- message/stream, tasks/get, tasks/list, tasks/cancel), which rebuild the A2A -- Task as id = task_id, contextId = the thread id, status = (status, --- status_message, updated_at), plus the fields of data. And ListThreadTasks, +-- status_message, updated_at), plus the fields of data. And GetThreadTasks, -- for the rows with a name. CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index eb1b86291..d75e9084a 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -71,23 +71,23 @@ message AgentThread { message DeleteResponse {} - // The conversation, oldest first. - message HistoryRequest { + // The thread's chat items, oldest first. + message GetItemsRequest { string thread_id = 1 [(logger.name) = "threadID"]; - // One agent's history; empty for every agent's. + // One agent's items; empty for every agent's. string agent = 2; - // Cursor from the previous HistoryResponse; empty for the first page. + // Cursor from the previous GetItemsResponse; empty for the first page. string after = 3; int32 limit = 4; } - message HistoryResponse { + message GetItemsResponse { repeated Item items = 1; // Empty at the end. string after = 2; } - // One chat item of an agent's history. Stored as the JSON of + // One chat item of an agent. Stored as the JSON of // ChatContext.ChatItem (items.payload in thread_schema_v1.sql). message Item { string agent = 1; @@ -124,16 +124,16 @@ message AgentTask { } // A thread's tasks, oldest first. - message ListRequest { + message GetTasksRequest { string thread_id = 1 [(logger.name) = "threadID"]; // Unset for every status. optional Status status = 2; - // Cursor from the previous ListResponse; empty for the first page. + // Cursor from the previous GetTasksResponse; empty for the first page. string after = 3; int32 limit = 4; } - message ListResponse { + message GetTasksResponse { repeated Task tasks = 1; // Empty at the end. string after = 2; From e223b92926f5e376b8cf2ed6178b4e6abd4f6030 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 22:49:18 +0000 Subject: [PATCH 42/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 0b4854fef..16a3fda09 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From dab43d902af33dc88b2464a43bd7fbbf2688d7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 15:58:53 -0700 Subject: [PATCH 43/56] thread schema: keep the framework's lane naming --- livekit/agent/thread_schema_v1.sql | 46 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index 63e941849..0c9d198f8 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -1,26 +1,26 @@ -- Tables inside a thread's database. The agents framework creates and writes -- them (lk-agents-storage, statements/sql.rs). The platform reads items and -- tasks (agent-db's threads service: AgentThreads.GetThreadItems and --- GetThreadTasks). An agent is a registered agent taking part in the thread, --- across every channel. Two agents in one thread, through a delegation, write --- separate rows. +-- GetThreadTasks). A lane is one registered agent's part of the thread, named +-- after the agent, across every channel. Two agents in one thread, through a +-- delegation, are two lanes. -- Used by: the framework, which compares it to its own version on open. CREATE TABLE schema_version ( version INTEGER NOT NULL ); --- One chat item per row, in each agent's order. Used by: the framework, as +-- One chat item per row, in each lane's order. Used by: the framework, as -- the model's history, and GetThreadItems. CREATE TABLE items ( id TEXT PRIMARY KEY, - agent TEXT NOT NULL, + lane TEXT NOT NULL, seq INTEGER NOT NULL, message_id TEXT, -- the A2A messageId it came from, for idempotency payload JSONB NOT NULL -- livekit.agent.ChatContext.ChatItem as protobuf JSON ); -CREATE INDEX items_agent_seq ON items (agent, seq); -CREATE UNIQUE INDEX items_agent_message ON items (agent, message_id) WHERE message_id IS NOT NULL; +CREATE INDEX items_lane_seq ON items (lane, seq); +CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; -- One A2A task per row: the task of an incoming message, or a background -- AgentTask (name set). Used by: the framework's A2A methods (message/send, @@ -30,7 +30,7 @@ CREATE UNIQUE INDEX items_agent_message ON items (agent, message_id) WHERE messa -- for the rows with a name. CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, - agent TEXT NOT NULL, + lane TEXT NOT NULL, seq INTEGER NOT NULL, name TEXT, -- the AgentTask class message_id TEXT, -- the A2A messageId that created it, for idempotency @@ -40,15 +40,15 @@ CREATE TABLE tasks ( data JSONB NOT NULL, -- A2A Task fields: history, artifacts, metadata state JSONB -- AgentTask arguments and pending question, to resume it ); -CREATE INDEX tasks_agent_seq ON tasks (agent, seq); -CREATE INDEX tasks_agent_status ON tasks (agent, status, seq); -CREATE UNIQUE INDEX tasks_agent_message ON tasks (agent, message_id) WHERE message_id IS NOT NULL; +CREATE INDEX tasks_lane_seq ON tasks (lane, seq); +CREATE INDEX tasks_lane_status ON tasks (lane, status, seq); +CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; --- One row per agent: its lease (one worker runs an agent at a time), its --- fencing version, state and agent stack. Used by: the framework, when a run +-- One row per lane: its lease (one worker runs a lane at a time), its fencing +-- version, state and agent stack. Used by: the framework, when a run -- starts, commits and ends. -CREATE TABLE agents ( - agent TEXT PRIMARY KEY, +CREATE TABLE lanes ( + lane TEXT PRIMARY KEY, version INTEGER NOT NULL, state JSONB, agent_stack JSONB, @@ -64,24 +64,24 @@ CREATE TABLE agents ( CREATE TABLE events ( seq INTEGER PRIMARY KEY, ts_ms INTEGER NOT NULL, - agent TEXT NOT NULL, + lane TEXT NOT NULL, run_id TEXT NOT NULL, type TEXT NOT NULL, payload JSONB NOT NULL ); -CREATE INDEX events_agent_seq ON events (agent, seq); -CREATE INDEX events_agent_run ON events (agent, run_id); +CREATE INDEX events_lane_seq ON events (lane, seq); +CREATE INDEX events_lane_run ON events (lane, run_id); --- Messages that arrived while the agent was running. Used by: the framework, +-- Messages that arrived while the lane was running. Used by: the framework, -- which drains them in the next run. CREATE TABLE inbox ( seq INTEGER PRIMARY KEY, - agent TEXT NOT NULL, + lane TEXT NOT NULL, task_id TEXT NOT NULL, message_id TEXT NOT NULL, -- the A2A messageId, for idempotency message JSONB NOT NULL, received_at INTEGER NOT NULL ); -CREATE INDEX inbox_agent_seq ON inbox (agent, seq); -CREATE INDEX inbox_agent_task ON inbox (agent, task_id); -CREATE UNIQUE INDEX inbox_agent_message ON inbox (agent, message_id); +CREATE INDEX inbox_lane_seq ON inbox (lane, seq); +CREATE INDEX inbox_lane_task ON inbox (lane, task_id); +CREATE UNIQUE INDEX inbox_lane_message ON inbox (lane, message_id); From f6247090fcaa13c6529cf0a9694f4ccff9107f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 17:00:09 -0700 Subject: [PATCH 44/56] thread schema: every column names its reader; tasks.internal; log ids --- livekit/agent/thread_schema_v1.sql | 117 ++++++++++++++--------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index 0c9d198f8..2dc756547 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -1,87 +1,86 @@ -- Tables inside a thread's database. The agents framework creates and writes --- them (lk-agents-storage, statements/sql.rs). The platform reads items and --- tasks (agent-db's threads service: AgentThreads.GetThreadItems and --- GetThreadTasks). A lane is one registered agent's part of the thread, named --- after the agent, across every channel. Two agents in one thread, through a --- delegation, are two lanes. +-- them (lk-agents-storage, statements/sql.rs). A lane is one registered +-- agent's part of the thread, named after the agent, across every channel. Two +-- agents in one thread, through a delegation, are two lanes. +-- +-- Every column says who reads it: +-- [platform] agent-db's threads service: AgentThreads.GetThreadItems and +-- GetThreadTasks. Changing it breaks the platform API. +-- [a2a] the framework's A2A methods (message/send, message/stream, +-- tasks/get, tasks/list, tasks/cancel), rebuilt into A2A objects. +-- [framework] the framework's own run loop only, free to change with it. --- Used by: the framework, which compares it to its own version on open. CREATE TABLE schema_version ( - version INTEGER NOT NULL + version INTEGER NOT NULL -- [framework] compared to its own version on open ); --- One chat item per row, in each lane's order. Used by: the framework, as --- the model's history, and GetThreadItems. +-- One chat item per row. CREATE TABLE items ( - id TEXT PRIMARY KEY, - lane TEXT NOT NULL, - seq INTEGER NOT NULL, - message_id TEXT, -- the A2A messageId it came from, for idempotency - payload JSONB NOT NULL -- livekit.agent.ChatContext.ChatItem as protobuf JSON + id TEXT PRIMARY KEY, -- [framework] the item's id, its upsert key + lane TEXT NOT NULL, -- [platform, framework] + seq INTEGER NOT NULL, -- [platform, framework] position in the lane + message_id TEXT, -- [platform, framework] the A2A messageId it came from, for idempotency + payload JSONB NOT NULL -- [platform, framework] livekit.agent.ChatContext.ChatItem as protobuf JSON ); CREATE INDEX items_lane_seq ON items (lane, seq); CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message_id IS NOT NULL; -- One A2A task per row: the task of an incoming message, or a background --- AgentTask (name set). Used by: the framework's A2A methods (message/send, --- message/stream, tasks/get, tasks/list, tasks/cancel), which rebuild the A2A --- Task as id = task_id, contextId = the thread id, status = (status, --- status_message, updated_at), plus the fields of data. And GetThreadTasks, --- for the rows with a name. +-- AgentTask (name set). The A2A Task is id = task_id, contextId = the thread +-- id, status = (status, status_message, updated_at), plus the fields of data. CREATE TABLE tasks ( - task_id TEXT PRIMARY KEY, - lane TEXT NOT NULL, - seq INTEGER NOT NULL, - name TEXT, -- the AgentTask class - message_id TEXT, -- the A2A messageId that created it, for idempotency - status TEXT NOT NULL, -- A2A TaskState, working when a new run finds it means its worker was lost - status_message JSONB, -- A2A Message - updated_at INTEGER NOT NULL, - data JSONB NOT NULL, -- A2A Task fields: history, artifacts, metadata - state JSONB -- AgentTask arguments and pending question, to resume it + task_id TEXT PRIMARY KEY, -- [platform, a2a, framework] + lane TEXT NOT NULL, -- [platform, framework] + seq INTEGER NOT NULL, -- [framework] submission order in the lane + name TEXT, -- [platform, framework] the AgentTask class + message_id TEXT, -- [framework] the A2A messageId that created it, for idempotency + status TEXT NOT NULL, -- [platform, a2a, framework] A2A TaskState, working when a new run finds it means its worker was lost + status_message JSONB, -- [a2a] A2A Message + updated_at INTEGER NOT NULL, -- [platform, a2a, framework] + data JSONB NOT NULL, -- [a2a] A2A Task fields: history, artifacts, metadata + internal JSONB -- [framework] AgentTask arguments and pending question, to resume it ); CREATE INDEX tasks_lane_seq ON tasks (lane, seq); CREATE INDEX tasks_lane_status ON tasks (lane, status, seq); CREATE UNIQUE INDEX tasks_lane_message ON tasks (lane, message_id) WHERE message_id IS NOT NULL; --- One row per lane: its lease (one worker runs a lane at a time), its fencing --- version, state and agent stack. Used by: the framework, when a run --- starts, commits and ends. +-- One row per lane: one worker runs a lane at a time. CREATE TABLE lanes ( - lane TEXT PRIMARY KEY, - version INTEGER NOT NULL, - state JSONB, - agent_stack JSONB, - lease_holder TEXT, - lease_token TEXT, - lease_until INTEGER NOT NULL DEFAULT 0, - waiter TEXT, - drained_seq INTEGER NOT NULL DEFAULT 0 -- the last inbox row a run consumed + lane TEXT PRIMARY KEY, -- [framework] + version INTEGER NOT NULL, -- [framework] fencing version, bumped by every commit + state JSONB, -- [framework] the agent's typed state + agent_stack JSONB, -- [framework] who is speaking + lease_holder TEXT, -- [framework] + lease_token TEXT, -- [framework] + lease_until INTEGER NOT NULL DEFAULT 0, -- [framework] + waiter TEXT, -- [framework] a worker waiting to interrupt the run + drained_id INTEGER NOT NULL DEFAULT 0 -- [framework] the last inbox row a run consumed ); --- The run journal: every AgentSession event, plus run_started and run_ended. --- Used by: the framework, to tell whether a run ended. +-- The run journal, append-only: every AgentSession event, plus run_started and +-- run_ended. CREATE TABLE events ( - seq INTEGER PRIMARY KEY, - ts_ms INTEGER NOT NULL, - lane TEXT NOT NULL, - run_id TEXT NOT NULL, - type TEXT NOT NULL, - payload JSONB NOT NULL + id INTEGER PRIMARY KEY, -- [framework] insertion order + ts_ms INTEGER NOT NULL, -- [framework] + lane TEXT NOT NULL, -- [framework] + run_id TEXT NOT NULL, -- [framework] + type TEXT NOT NULL, -- [framework] + payload JSONB NOT NULL -- [framework] ); -CREATE INDEX events_lane_seq ON events (lane, seq); +CREATE INDEX events_lane_id ON events (lane, id); CREATE INDEX events_lane_run ON events (lane, run_id); --- Messages that arrived while the lane was running. Used by: the framework, --- which drains them in the next run. +-- Messages that arrived while the lane was running, drained by the next run. +-- Ids are compared to lanes.drained_id, so the newest row is never deleted +-- (SQLite would hand its id out again). CREATE TABLE inbox ( - seq INTEGER PRIMARY KEY, - lane TEXT NOT NULL, - task_id TEXT NOT NULL, - message_id TEXT NOT NULL, -- the A2A messageId, for idempotency - message JSONB NOT NULL, - received_at INTEGER NOT NULL + id INTEGER PRIMARY KEY, -- [framework] arrival order + lane TEXT NOT NULL, -- [framework] + task_id TEXT NOT NULL, -- [framework] + message_id TEXT NOT NULL, -- [framework] the A2A messageId, for idempotency + message JSONB NOT NULL, -- [framework] the A2A message + received_at INTEGER NOT NULL -- [framework] ); -CREATE INDEX inbox_lane_seq ON inbox (lane, seq); +CREATE INDEX inbox_lane_id ON inbox (lane, id); CREATE INDEX inbox_lane_task ON inbox (lane, task_id); CREATE UNIQUE INDEX inbox_lane_message ON inbox (lane, message_id); From 38d1b2771cdf0dd5604e5bd742a20e4337e26943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 19:22:04 -0700 Subject: [PATCH 45/56] agent task: no agent field, no paging --- livekit/agent/livekit_agent_thread.pb.go | 71 +++++----------------- livekit/agent/thread_schema_v1.sql | 2 +- protobufs/agent/livekit_agent_thread.proto | 13 +--- 3 files changed, 20 insertions(+), 66 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 16a3fda09..3b26618d1 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -837,10 +837,7 @@ type AgentTask_GetTasksRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` // Unset for every status. - Status *AgentTask_Status `protobuf:"varint,2,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status,oneof" json:"status,omitempty"` - // Cursor from the previous GetTasksResponse; empty for the first page. - After string `protobuf:"bytes,3,opt,name=after,proto3" json:"after,omitempty"` - Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + Status *AgentTask_Status `protobuf:"varint,2,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status,oneof" json:"status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -889,25 +886,9 @@ func (x *AgentTask_GetTasksRequest) GetStatus() AgentTask_Status { return AgentTask_RUNNING } -func (x *AgentTask_GetTasksRequest) GetAfter() string { - if x != nil { - return x.After - } - return "" -} - -func (x *AgentTask_GetTasksRequest) GetLimit() int32 { - if x != nil { - return x.Limit - } - return 0 -} - type AgentTask_GetTasksResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Tasks []*AgentTask_Task `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"` - // Empty at the end. - After string `protobuf:"bytes,2,opt,name=after,proto3" json:"after,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Tasks []*AgentTask_Task `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -949,23 +930,14 @@ func (x *AgentTask_GetTasksResponse) GetTasks() []*AgentTask_Task { return nil } -func (x *AgentTask_GetTasksResponse) GetAfter() string { - if x != nil { - return x.After - } - return "" -} - type AgentTask_Task struct { state protoimpl.MessageState `protogen:"open.v1"` TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` ThreadId string `protobuf:"bytes,2,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` - // The agent running it. - Agent string `protobuf:"bytes,3,opt,name=agent,proto3" json:"agent,omitempty"` // The task's name, as the agent defines it. - Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` - Status AgentTask_Status `protobuf:"varint,5,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status" json:"status,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Status AgentTask_Status `protobuf:"varint,4,opt,name=status,proto3,enum=livekit.agent.AgentTask_Status" json:"status,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1014,13 +986,6 @@ func (x *AgentTask_Task) GetThreadId() string { return "" } -func (x *AgentTask_Task) GetAgent() string { - if x != nil { - return x.Agent - } - return "" -} - func (x *AgentTask_Task) GetName() string { if x != nil { return x.Name @@ -1110,25 +1075,21 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc7\x04\n" + - "\tAgentTask\x1a\xb0\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xef\x03\n" + + "\tAgentTask\x1a\x84\x01\n" + "\x0fGetTasksRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12<\n" + - "\x06status\x18\x02 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusH\x00R\x06status\x88\x01\x01\x12\x14\n" + - "\x05after\x18\x03 \x01(\tR\x05after\x12\x14\n" + - "\x05limit\x18\x04 \x01(\x05R\x05limitB\t\n" + - "\a_status\x1a]\n" + + "\x06status\x18\x02 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusH\x00R\x06status\x88\x01\x01B\t\n" + + "\a_status\x1aG\n" + "\x10GetTasksResponse\x123\n" + - "\x05tasks\x18\x01 \x03(\v2\x1d.livekit.agent.AgentTask.TaskR\x05tasks\x12\x14\n" + - "\x05after\x18\x02 \x01(\tR\x05after\x1a\xe7\x01\n" + + "\x05tasks\x18\x01 \x03(\v2\x1d.livekit.agent.AgentTask.TaskR\x05tasks\x1a\xd1\x01\n" + "\x04Task\x12\x17\n" + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12(\n" + - "\tthread_id\x18\x02 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x14\n" + - "\x05agent\x18\x03 \x01(\tR\x05agent\x12\x12\n" + - "\x04name\x18\x04 \x01(\tR\x04name\x127\n" + - "\x06status\x18\x05 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusR\x06status\x129\n" + + "\tthread_id\x18\x02 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x127\n" + + "\x06status\x18\x04 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusR\x06status\x129\n" + "\n" + - "updated_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\">\n" + + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\">\n" + "\x06Status\x12\v\n" + "\aRUNNING\x10\x00\x12\r\n" + "\tCOMPLETED\x10\x01\x12\n" + diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index 2dc756547..1b2a4636f 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -30,7 +30,7 @@ CREATE UNIQUE INDEX items_lane_message ON items (lane, message_id) WHERE message -- id, status = (status, status_message, updated_at), plus the fields of data. CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, -- [platform, a2a, framework] - lane TEXT NOT NULL, -- [platform, framework] + lane TEXT NOT NULL, -- [framework] seq INTEGER NOT NULL, -- [framework] submission order in the lane name TEXT, -- [platform, framework] the AgentTask class message_id TEXT, -- [framework] the A2A messageId that created it, for idempotency diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index d75e9084a..9ab6e4b7e 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -128,25 +128,18 @@ message AgentTask { string thread_id = 1 [(logger.name) = "threadID"]; // Unset for every status. optional Status status = 2; - // Cursor from the previous GetTasksResponse; empty for the first page. - string after = 3; - int32 limit = 4; } message GetTasksResponse { repeated Task tasks = 1; - // Empty at the end. - string after = 2; } message Task { string task_id = 1; string thread_id = 2 [(logger.name) = "threadID"]; - // The agent running it. - string agent = 3; // The task's name, as the agent defines it. - string name = 4; - Status status = 5; - google.protobuf.Timestamp updated_at = 6; + string name = 3; + Status status = 4; + google.protobuf.Timestamp updated_at = 5; } } From f965030874740b13b4d74cac1da254fb790be49a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 02:23:01 +0000 Subject: [PATCH 46/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 3b26618d1..049c4ec0d 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From aca3f2c8c6c316490fa22c878b2b2270557e9988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 20:22:49 -0700 Subject: [PATCH 47/56] agentdb: Export instead of Dump --- .changeset/agent-threads.md | 2 +- livekit/livekit_agentdb.pb.go | 60 ++++++++++++++++----------------- protobufs/livekit_agentdb.proto | 6 ++-- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index 7ce21f187..2a7d310ed 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -5,4 +5,4 @@ Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its items as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. -The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. +The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, `DumpRequest`/`DumpResponse` are now `ExportRequest`/`ExportResponse`, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index f286ed14e..814168d35 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: livekit_agentdb.proto package livekit @@ -580,27 +580,27 @@ func (*AgentDB_DeleteResponse) Descriptor() ([]byte, []int) { return file_livekit_agentdb_proto_rawDescGZIP(), []int{0, 7} } -type AgentDB_DumpRequest struct { +type AgentDB_ExportRequest struct { state protoimpl.MessageState `protogen:"open.v1"` DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AgentDB_DumpRequest) Reset() { - *x = AgentDB_DumpRequest{} +func (x *AgentDB_ExportRequest) Reset() { + *x = AgentDB_ExportRequest{} mi := &file_livekit_agentdb_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentDB_DumpRequest) String() string { +func (x *AgentDB_ExportRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentDB_DumpRequest) ProtoMessage() {} +func (*AgentDB_ExportRequest) ProtoMessage() {} -func (x *AgentDB_DumpRequest) ProtoReflect() protoreflect.Message { +func (x *AgentDB_ExportRequest) ProtoReflect() protoreflect.Message { mi := &file_livekit_agentdb_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -612,41 +612,41 @@ func (x *AgentDB_DumpRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AgentDB_DumpRequest.ProtoReflect.Descriptor instead. -func (*AgentDB_DumpRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentDB_ExportRequest.ProtoReflect.Descriptor instead. +func (*AgentDB_ExportRequest) Descriptor() ([]byte, []int) { return file_livekit_agentdb_proto_rawDescGZIP(), []int{0, 8} } -func (x *AgentDB_DumpRequest) GetDatabaseId() string { +func (x *AgentDB_ExportRequest) GetDatabaseId() string { if x != nil { return x.DatabaseId } return "" } -type AgentDB_DumpResponse struct { +type AgentDB_ExportResponse struct { state protoimpl.MessageState `protogen:"open.v1"` DownloadUrl string `protobuf:"bytes,1,opt,name=download_url,json=downloadUrl,proto3" json:"download_url,omitempty"` // pre-authenticated object-storage URL ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` // of the URL, not the database - Tip int64 `protobuf:"varint,3,opt,name=tip,proto3" json:"tip,omitempty"` // what the dump is consistent at + Tip int64 `protobuf:"varint,3,opt,name=tip,proto3" json:"tip,omitempty"` // what the export is consistent at unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AgentDB_DumpResponse) Reset() { - *x = AgentDB_DumpResponse{} +func (x *AgentDB_ExportResponse) Reset() { + *x = AgentDB_ExportResponse{} mi := &file_livekit_agentdb_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AgentDB_DumpResponse) String() string { +func (x *AgentDB_ExportResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AgentDB_DumpResponse) ProtoMessage() {} +func (*AgentDB_ExportResponse) ProtoMessage() {} -func (x *AgentDB_DumpResponse) ProtoReflect() protoreflect.Message { +func (x *AgentDB_ExportResponse) ProtoReflect() protoreflect.Message { mi := &file_livekit_agentdb_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -658,26 +658,26 @@ func (x *AgentDB_DumpResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AgentDB_DumpResponse.ProtoReflect.Descriptor instead. -func (*AgentDB_DumpResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AgentDB_ExportResponse.ProtoReflect.Descriptor instead. +func (*AgentDB_ExportResponse) Descriptor() ([]byte, []int) { return file_livekit_agentdb_proto_rawDescGZIP(), []int{0, 9} } -func (x *AgentDB_DumpResponse) GetDownloadUrl() string { +func (x *AgentDB_ExportResponse) GetDownloadUrl() string { if x != nil { return x.DownloadUrl } return "" } -func (x *AgentDB_DumpResponse) GetExpiresAt() *timestamppb.Timestamp { +func (x *AgentDB_ExportResponse) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } return nil } -func (x *AgentDB_DumpResponse) GetTip() int64 { +func (x *AgentDB_ExportResponse) GetTip() int64 { if x != nil { return x.Tip } @@ -2103,7 +2103,7 @@ var File_livekit_agentdb_proto protoreflect.FileDescriptor const file_livekit_agentdb_proto_rawDesc = "" + "\n" + - "\x15livekit_agentdb.proto\x12\alivekit\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xbc\x1a\n" + + "\x15livekit_agentdb.proto\x12\alivekit\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc1\x1a\n" + "\aAgentDB\x1a<\n" + "\rCreateRequest\x12+\n" + "\x03ttl\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x1al\n" + @@ -2135,11 +2135,11 @@ const file_livekit_agentdb_proto_rawDesc = "" + "\rDeleteRequest\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + "databaseId\x1a\x10\n" + - "\x0eDeleteResponse\x1a.\n" + - "\vDumpRequest\x12\x1f\n" + + "\x0eDeleteResponse\x1a0\n" + + "\rExportRequest\x12\x1f\n" + "\vdatabase_id\x18\x01 \x01(\tR\n" + - "databaseId\x1a~\n" + - "\fDumpResponse\x12!\n" + + "databaseId\x1a\x80\x01\n" + + "\x0eExportResponse\x12!\n" + "\fdownload_url\x18\x01 \x01(\tR\vdownloadUrl\x129\n" + "\n" + "expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x10\n" + @@ -2271,8 +2271,8 @@ var file_livekit_agentdb_proto_goTypes = []any{ (*AgentDB_AgentDatabase)(nil), // 8: livekit.AgentDB.AgentDatabase (*AgentDB_DeleteRequest)(nil), // 9: livekit.AgentDB.DeleteRequest (*AgentDB_DeleteResponse)(nil), // 10: livekit.AgentDB.DeleteResponse - (*AgentDB_DumpRequest)(nil), // 11: livekit.AgentDB.DumpRequest - (*AgentDB_DumpResponse)(nil), // 12: livekit.AgentDB.DumpResponse + (*AgentDB_ExportRequest)(nil), // 11: livekit.AgentDB.ExportRequest + (*AgentDB_ExportResponse)(nil), // 12: livekit.AgentDB.ExportResponse (*AgentDB_Wire)(nil), // 13: livekit.AgentDB.Wire (*AgentDB_Wire_ClientMessage)(nil), // 14: livekit.AgentDB.Wire.ClientMessage (*AgentDB_Wire_ServerMessage)(nil), // 15: livekit.AgentDB.Wire.ServerMessage @@ -2303,7 +2303,7 @@ var file_livekit_agentdb_proto_depIdxs = []int32{ 8, // 2: livekit.AgentDB.ListResponse.databases:type_name -> livekit.AgentDB.AgentDatabase 35, // 3: livekit.AgentDB.AgentDatabase.created_at:type_name -> google.protobuf.Timestamp 35, // 4: livekit.AgentDB.AgentDatabase.expires_at:type_name -> google.protobuf.Timestamp - 35, // 5: livekit.AgentDB.DumpResponse.expires_at:type_name -> google.protobuf.Timestamp + 35, // 5: livekit.AgentDB.ExportResponse.expires_at:type_name -> google.protobuf.Timestamp 26, // 6: livekit.AgentDB.Wire.ClientMessage.hello:type_name -> livekit.AgentDB.Wire.Hello 17, // 7: livekit.AgentDB.Wire.ClientMessage.exec:type_name -> livekit.AgentDB.Wire.Statement 17, // 8: livekit.AgentDB.Wire.ClientMessage.query:type_name -> livekit.AgentDB.Wire.Statement diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index 36ce95d45..176335b40 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -70,14 +70,14 @@ message AgentDB { message DeleteResponse {} - message DumpRequest { + message ExportRequest { string database_id = 1; } - message DumpResponse { + message ExportResponse { string download_url = 1; // pre-authenticated object-storage URL google.protobuf.Timestamp expires_at = 2; // of the URL, not the database - int64 tip = 3; // what the dump is consistent at + int64 tip = 3; // what the export is consistent at } // Data plane: one WebSocket per database, one message per frame, correlated From 1e517dcd60dd63e799affd671b77804f468e167e Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:23:42 +0000 Subject: [PATCH 48/56] generated protobuf --- livekit/livekit_agentdb.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 814168d35..66261a614 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: livekit_agentdb.proto package livekit From 592b2217c74abccd0c424369fa419d8f957d0052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 20:47:25 -0700 Subject: [PATCH 49/56] agent: tasks.status values and their AgentTask.Status live here --- livekit/agent/thread_schema.go | 30 +++++++++++++++++++++++++++++ livekit/agent/thread_schema_test.go | 27 ++++++++++++++++++++++++++ livekit/agent/thread_schema_v1.sql | 2 +- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 livekit/agent/thread_schema_test.go diff --git a/livekit/agent/thread_schema.go b/livekit/agent/thread_schema.go index fe2c9a8a9..4b709c405 100644 --- a/livekit/agent/thread_schema.go +++ b/livekit/agent/thread_schema.go @@ -25,3 +25,33 @@ const ThreadSchemaVersion = 1 // //go:embed thread_schema_v1.sql var ThreadSchemaV1 string + +// taskStatuses maps the values of tasks.status (A2A task states) to +// AgentTask.Status: a task not yet finished is running, a rejected one failed. +var taskStatuses = map[string]AgentTask_Status{ + "submitted": AgentTask_RUNNING, + "working": AgentTask_RUNNING, + "input-required": AgentTask_RUNNING, + "completed": AgentTask_COMPLETED, + "failed": AgentTask_FAILED, + "rejected": AgentTask_FAILED, + "canceled": AgentTask_CANCELED, +} + +// TaskStatus is the AgentTask.Status of a tasks.status value. +func TaskStatus(state string) (AgentTask_Status, bool) { + status, ok := taskStatuses[state] + return status, ok +} + +// TaskStates are the tasks.status values of an AgentTask.Status. +func TaskStates(status AgentTask_Status) []string { + var states []string + for state, s := range taskStatuses { + if s == status { + states = append(states, state) + } + } + return states +} + diff --git a/livekit/agent/thread_schema_test.go b/livekit/agent/thread_schema_test.go new file mode 100644 index 000000000..57b2f1bb0 --- /dev/null +++ b/livekit/agent/thread_schema_test.go @@ -0,0 +1,27 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package agent + +import "testing" + +// Every AgentTask.Status must be reachable from a stored state, or a filter on +// it would silently match nothing. +func TestEveryTaskStatusHasStates(t *testing.T) { + for value := range AgentTask_Status_name { + if len(TaskStates(AgentTask_Status(value))) == 0 { + t.Errorf("%s has no tasks.status value", AgentTask_Status(value)) + } + } +} diff --git a/livekit/agent/thread_schema_v1.sql b/livekit/agent/thread_schema_v1.sql index 1b2a4636f..4f354d1b3 100644 --- a/livekit/agent/thread_schema_v1.sql +++ b/livekit/agent/thread_schema_v1.sql @@ -34,7 +34,7 @@ CREATE TABLE tasks ( seq INTEGER NOT NULL, -- [framework] submission order in the lane name TEXT, -- [platform, framework] the AgentTask class message_id TEXT, -- [framework] the A2A messageId that created it, for idempotency - status TEXT NOT NULL, -- [platform, a2a, framework] A2A TaskState, working when a new run finds it means its worker was lost + status TEXT NOT NULL, -- [platform, a2a, framework] A2A TaskState (agent.TaskStatus maps it), working when a new run finds it means its worker was lost status_message JSONB, -- [a2a] A2A Message updated_at INTEGER NOT NULL, -- [platform, a2a, framework] data JSONB NOT NULL, -- [a2a] A2A Task fields: history, artifacts, metadata From f7ccd2b2f0bd5465cf0e50b2e2204a7c22e95d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 21:31:56 -0700 Subject: [PATCH 50/56] agent task: zero status is unspecified, so running shows in JSON --- livekit/agent/livekit_agent_thread.pb.go | 49 ++++++++++++---------- livekit/agent/thread_schema_test.go | 2 +- protobufs/agent/livekit_agent_thread.proto | 10 +++-- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 049c4ec0d..08649ab9a 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.35.1 +// protoc v7.36.2 // source: agent/livekit_agent_thread.proto package agent @@ -44,28 +44,32 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// Zero is never a task's status, so every status appears in JSON. type AgentTask_Status int32 const ( - AgentTask_RUNNING AgentTask_Status = 0 - AgentTask_COMPLETED AgentTask_Status = 1 - AgentTask_FAILED AgentTask_Status = 2 - AgentTask_CANCELED AgentTask_Status = 3 + AgentTask_UNSPECIFIED AgentTask_Status = 0 + AgentTask_RUNNING AgentTask_Status = 1 + AgentTask_COMPLETED AgentTask_Status = 2 + AgentTask_FAILED AgentTask_Status = 3 + AgentTask_CANCELED AgentTask_Status = 4 ) // Enum value maps for AgentTask_Status. var ( AgentTask_Status_name = map[int32]string{ - 0: "RUNNING", - 1: "COMPLETED", - 2: "FAILED", - 3: "CANCELED", + 0: "UNSPECIFIED", + 1: "RUNNING", + 2: "COMPLETED", + 3: "FAILED", + 4: "CANCELED", } AgentTask_Status_value = map[string]int32{ - "RUNNING": 0, - "COMPLETED": 1, - "FAILED": 2, - "CANCELED": 3, + "UNSPECIFIED": 0, + "RUNNING": 1, + "COMPLETED": 2, + "FAILED": 3, + "CANCELED": 4, } ) @@ -883,7 +887,7 @@ func (x *AgentTask_GetTasksRequest) GetStatus() AgentTask_Status { if x != nil && x.Status != nil { return *x.Status } - return AgentTask_RUNNING + return AgentTask_UNSPECIFIED } type AgentTask_GetTasksResponse struct { @@ -997,7 +1001,7 @@ func (x *AgentTask_Task) GetStatus() AgentTask_Status { if x != nil { return x.Status } - return AgentTask_RUNNING + return AgentTask_UNSPECIFIED } func (x *AgentTask_Task) GetUpdatedAt() *timestamppb.Timestamp { @@ -1075,7 +1079,7 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "databaseId\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xef\x03\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x80\x04\n" + "\tAgentTask\x1a\x84\x01\n" + "\x0fGetTasksRequest\x12(\n" + "\tthread_id\x18\x01 \x01(\tB\v\xbaP\bthreadIDR\bthreadId\x12<\n" + @@ -1089,13 +1093,14 @@ const file_agent_livekit_agent_thread_proto_rawDesc = "" + "\x04name\x18\x03 \x01(\tR\x04name\x127\n" + "\x06status\x18\x04 \x01(\x0e2\x1f.livekit.agent.AgentTask.StatusR\x06status\x129\n" + "\n" + - "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\">\n" + - "\x06Status\x12\v\n" + - "\aRUNNING\x10\x00\x12\r\n" + - "\tCOMPLETED\x10\x01\x12\n" + + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"O\n" + + "\x06Status\x12\x0f\n" + + "\vUNSPECIFIED\x10\x00\x12\v\n" + + "\aRUNNING\x10\x01\x12\r\n" + + "\tCOMPLETED\x10\x02\x12\n" + "\n" + - "\x06FAILED\x10\x02\x12\f\n" + - "\bCANCELED\x10\x03B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" + "\x06FAILED\x10\x03\x12\f\n" + + "\bCANCELED\x10\x04B+Z)github.com/livekit/protocol/livekit/agentb\x06proto3" var ( file_agent_livekit_agent_thread_proto_rawDescOnce sync.Once diff --git a/livekit/agent/thread_schema_test.go b/livekit/agent/thread_schema_test.go index 57b2f1bb0..861f73f2c 100644 --- a/livekit/agent/thread_schema_test.go +++ b/livekit/agent/thread_schema_test.go @@ -20,7 +20,7 @@ import "testing" // it would silently match nothing. func TestEveryTaskStatusHasStates(t *testing.T) { for value := range AgentTask_Status_name { - if len(TaskStates(AgentTask_Status(value))) == 0 { + if value != 0 && len(TaskStates(AgentTask_Status(value))) == 0 { t.Errorf("%s has no tasks.status value", AgentTask_Status(value)) } } diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index 9ab6e4b7e..a918311d8 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -116,11 +116,13 @@ message AgentThread { // AgentTask: background work an agent runs within a thread, such as a booking // that takes minutes. message AgentTask { + // Zero is never a task's status, so every status appears in JSON. enum Status { - RUNNING = 0; - COMPLETED = 1; - FAILED = 2; - CANCELED = 3; + UNSPECIFIED = 0; + RUNNING = 1; + COMPLETED = 2; + FAILED = 3; + CANCELED = 4; } // A thread's tasks, oldest first. From 2ad11c85ff06853ceac68f355d6a5fa4001304bd Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 04:32:43 +0000 Subject: [PATCH 51/56] generated protobuf --- livekit/agent/livekit_agent_thread.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 08649ab9a..0914be2c7 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -21,7 +21,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.12 -// protoc v7.36.2 +// protoc v7.35.1 // source: agent/livekit_agent_thread.proto package agent From 85b4c1c1b5d8af1b17e1324e32ffc5c357f29ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 8 Oct 2026 21:33:11 -0700 Subject: [PATCH 52/56] agent task: status doc --- livekit/agent/livekit_agent_thread.pb.go | 2 +- protobufs/agent/livekit_agent_thread.proto | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit/agent/livekit_agent_thread.pb.go b/livekit/agent/livekit_agent_thread.pb.go index 08649ab9a..6b1443299 100644 --- a/livekit/agent/livekit_agent_thread.pb.go +++ b/livekit/agent/livekit_agent_thread.pb.go @@ -44,7 +44,7 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// Zero is never a task's status, so every status appears in JSON. +// A task is never UNSPECIFIED. type AgentTask_Status int32 const ( diff --git a/protobufs/agent/livekit_agent_thread.proto b/protobufs/agent/livekit_agent_thread.proto index a918311d8..971765e2a 100644 --- a/protobufs/agent/livekit_agent_thread.proto +++ b/protobufs/agent/livekit_agent_thread.proto @@ -116,7 +116,7 @@ message AgentThread { // AgentTask: background work an agent runs within a thread, such as a booking // that takes minutes. message AgentTask { - // Zero is never a task's status, so every status appears in JSON. + // A task is never UNSPECIFIED. enum Status { UNSPECIFIED = 0; RUNNING = 1; From 01c6062e9c36a809e985004cf2c79163c7f03d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 9 Oct 2026 09:29:07 -0700 Subject: [PATCH 53/56] auth: AgentThreadGrant.All for project-wide thread access --- .changeset/agent-threads.md | 2 +- auth/grants.go | 29 +++++++++++++++++++++-------- auth/grants_test.go | 19 ++++++++++++++++--- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index 2a7d310ed..0409a7b6e 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -3,6 +3,6 @@ "@livekit/protocol": minor --- -Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its items as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. +Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its items as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id, or every thread of the project with `all`); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, `DumpRequest`/`DumpResponse` are now `ExportRequest`/`ExportResponse`, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. diff --git a/auth/grants.go b/auth/grants.go index 041ef8269..fd052ff84 100644 --- a/auth/grants.go +++ b/auth/grants.go @@ -717,9 +717,11 @@ func (s *AgentEndpointGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { // AgentThreadGrant gives a token access to threads, across the project's // agents: list and create within Scope, and read, write and delete the threads -// in ThreadIDs. It grants thread data only; which endpoints a token may call is -// agentEndpoint's. +// in ThreadIDs. All extends it to every thread and scope of the project; the +// action flags still apply. It grants thread data only; which endpoints a token +// may call is agentEndpoint's. type AgentThreadGrant struct { + All bool `json:"all,omitempty"` Scope string `json:"scope,omitempty"` ThreadIDs []string `json:"threadIds,omitempty"` List bool `json:"list,omitempty"` @@ -729,15 +731,21 @@ type AgentThreadGrant struct { Delete bool `json:"delete,omitempty"` } -// CanCreate reports whether the holder may create threads. They are created in -// the grant's scope, so a grant without one cannot create. -func (s *AgentThreadGrant) CanCreate() bool { - return s != nil && s.Create && s.Scope != "" +// CanCreate reports whether the holder may create threads in scope. A scoped +// grant creates only in its own scope. +func (s *AgentThreadGrant) CanCreate(scope string) bool { + return s != nil && s.Create && s.inScope(scope) } // CanList reports whether the holder may list the threads of scope. func (s *AgentThreadGrant) CanList(scope string) bool { - return s != nil && s.List && s.Scope != "" && s.Scope == scope + return s != nil && s.List && s.inScope(scope) +} + +// CanUpdate reports whether the holder may change any thread's attributes and +// idle TTL, which only a project-wide grant may. +func (s *AgentThreadGrant) CanUpdate() bool { + return s != nil && s.All && s.Write } // CanRead reports whether the holder may read threadID. @@ -755,8 +763,12 @@ func (s *AgentThreadGrant) CanDelete(threadID string) bool { return s != nil && s.Delete && s.includes(threadID) } +func (s *AgentThreadGrant) inScope(scope string) bool { + return s.All || (s.Scope != "" && s.Scope == scope) +} + func (s *AgentThreadGrant) includes(threadID string) bool { - return threadID != "" && slices.Contains(s.ThreadIDs, threadID) + return threadID != "" && (s.All || slices.Contains(s.ThreadIDs, threadID)) } func (s *AgentThreadGrant) Clone() *AgentThreadGrant { @@ -777,6 +789,7 @@ func (s *AgentThreadGrant) MarshalLogObject(e zapcore.ObjectEncoder) error { return nil } + e.AddBool("All", s.All) e.AddString("Scope", s.Scope) zap.Strings("ThreadIDs", s.ThreadIDs).AddTo(e) e.AddBool("List", s.List) diff --git a/auth/grants_test.go b/auth/grants_test.go index 532c56a94..b490e46df 100644 --- a/auth/grants_test.go +++ b/auth/grants_test.go @@ -542,15 +542,28 @@ func TestAgentThreadGrantChecks(t *testing.T) { require.True(t, g.CanList("u1")) require.False(t, g.CanList("u2")) require.False(t, g.CanList("")) - require.True(t, g.CanCreate()) + require.True(t, g.CanCreate("u1")) + require.False(t, g.CanCreate("u2")) + require.False(t, g.CanUpdate()) idsOnly := &AgentThreadGrant{ThreadIDs: []string{"AT_4"}, Create: true, Write: true} require.True(t, idsOnly.CanWrite("AT_4")) - require.False(t, idsOnly.CanCreate()) + require.False(t, idsOnly.CanCreate("")) + + // All covers every thread and scope, still gated by the action flags. + all := &AgentThreadGrant{All: true, Read: true, List: true} + require.True(t, all.CanRead("AT_9")) + require.True(t, all.CanList("any")) + require.False(t, all.CanRead("")) + require.False(t, all.CanDelete("AT_9")) + require.False(t, all.CanCreate("any")) + require.False(t, all.CanUpdate()) + require.True(t, (&AgentThreadGrant{All: true, Write: true}).CanUpdate()) var none *AgentThreadGrant require.False(t, none.CanRead("AT_1")) - require.False(t, none.CanCreate()) + require.False(t, none.CanCreate("u1")) + require.False(t, none.CanUpdate()) } func TestAgentThreadGrantCloneIndependent(t *testing.T) { From 008dfd7ff18778d7779dcf66953cef469db43a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 5 Oct 2026 23:26:22 -0700 Subject: [PATCH 54/56] agentdb: tag batch results with their statement index A Batch answers one result per statement, in order: Columns and ColumnBatch frames for a statement that returns rows, one ExecResult for one that does not, then a single Done for the whole batch. The new statement field on Columns, ColumnBatch and ExecResult is the 0-based index within the batch; a plain Exec or Query leaves it 0. (cherry picked from commit 39517ac574a6b3a6508ab4e8e959729014cf94c5) --- protobufs/livekit_agentdb.proto | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/protobufs/livekit_agentdb.proto b/protobufs/livekit_agentdb.proto index 176335b40..a30be2c24 100644 --- a/protobufs/livekit_agentdb.proto +++ b/protobufs/livekit_agentdb.proto @@ -90,7 +90,7 @@ message AgentDB { Hello hello = 2; // must be first on the socket Statement exec = 3; // answers ExecResult Statement query = 4; // answers Columns + ColumnBatch* + Done - Batch batch = 5; + Batch batch = 5; // answers one result per statement, then Done Begin begin = 6; Commit commit = 7; Rollback rollback = 8; @@ -135,8 +135,14 @@ message AgentDB { QueryLang lang = 3; } + // Applied atomically, in order, and answered with one result per + // statement, in order: Columns then ColumnBatch frames for a statement + // that returns rows, one ExecResult for one that does not, each tagged + // with the statement's 0-based index, then a single Done for the whole + // batch. A statement that returns no rows still sends its Columns. Credit + // paces the ColumnBatch frames of the batch as a whole. message Batch { - repeated Statement statements = 1; // applied atomically, in order + repeated Statement statements = 1; } message Begin {} @@ -170,6 +176,7 @@ message AgentDB { message Columns { repeated string names = 1; + uint32 statement = 2; // index within a Batch; 0 and unused for a Query } enum ValueType { @@ -198,12 +205,14 @@ message AgentDB { message ColumnBatch { repeated Column columns = 1; // one per name in Columns uint32 rows = 2; + uint32 statement = 3; // index within a Batch; 0 and unused for a Query } message ExecResult { int64 rows_affected = 1; int64 last_insert_id = 2; int64 tip = 3; + uint32 statement = 4; // index within a Batch; 0 and unused for an Exec } message Done { From 089df37e9ce0730dce915470faf3d5f7f89537d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 9 Oct 2026 10:20:50 -0700 Subject: [PATCH 55/56] changeset: per-statement batch results --- .changeset/agent-threads.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/agent-threads.md b/.changeset/agent-threads.md index 0409a7b6e..9f16bfd2a 100644 --- a/.changeset/agent-threads.md +++ b/.changeset/agent-threads.md @@ -5,4 +5,4 @@ Agent threads, the durable conversation an agent has with a person across channels (`agent/livekit_agent_thread.proto`): `AgentThread` (the thread, its items as `ChatContext.ChatItem`) and `AgentTask` (background work an agent runs in it); the conversation tables in `livekit/agent/thread_schema_v1.sql`; `auth.AgentThreadGrant` under the `agentThread` claim (list and create by scope, read, write and delete by thread id, or every thread of the project with `all`); the `AT_` (thread) and `AST_` (agent stream) id prefixes in `utils/guid`. -The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, `DumpRequest`/`DumpResponse` are now `ExportRequest`/`ExportResponse`, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. +The agent-db and thread services are internal gRPC in cloud-protocol, so `AgentDBService` and its Twirp code are removed; the public API is public-api-server. A database lives in its project's data region: `AgentDB.CreateRequest.region` is removed, `DumpRequest`/`DumpResponse` are now `ExportRequest`/`ExportResponse`, and AgentDB times are `google.protobuf.Timestamp` / `Duration`. A batch answers one result per statement, tagged with its 0-based `statement` index on `Columns`, `ColumnBatch` and `ExecResult`. From 18e535d44256b570768266144ffd1390014b5b8d Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:21:35 +0000 Subject: [PATCH 56/56] generated protobuf --- livekit/livekit_agentdb.pb.go | 51 ++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/livekit/livekit_agentdb.pb.go b/livekit/livekit_agentdb.pb.go index 66261a614..7828f7161 100644 --- a/livekit/livekit_agentdb.pb.go +++ b/livekit/livekit_agentdb.pb.go @@ -894,7 +894,7 @@ type AgentDB_Wire_ClientMessage_Query struct { } type AgentDB_Wire_ClientMessage_Batch struct { - Batch *AgentDB_Wire_Batch `protobuf:"bytes,5,opt,name=batch,proto3,oneof"` + Batch *AgentDB_Wire_Batch `protobuf:"bytes,5,opt,name=batch,proto3,oneof"` // answers one result per statement, then Done } type AgentDB_Wire_ClientMessage_Begin struct { @@ -1302,9 +1302,15 @@ func (x *AgentDB_Wire_Statement) GetLang() AgentDB_Wire_QueryLang { return AgentDB_Wire_SQL } +// Applied atomically, in order, and answered with one result per +// statement, in order: Columns then ColumnBatch frames for a statement +// that returns rows, one ExecResult for one that does not, each tagged +// with the statement's 0-based index, then a single Done for the whole +// batch. A statement that returns no rows still sends its Columns. Credit +// paces the ColumnBatch frames of the batch as a whole. type AgentDB_Wire_Batch struct { state protoimpl.MessageState `protogen:"open.v1"` - Statements []*AgentDB_Wire_Statement `protobuf:"bytes,1,rep,name=statements,proto3" json:"statements,omitempty"` // applied atomically, in order + Statements []*AgentDB_Wire_Statement `protobuf:"bytes,1,rep,name=statements,proto3" json:"statements,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1745,6 +1751,7 @@ func (x *AgentDB_Wire_HelloOk) GetPingTimeoutMs() uint32 { type AgentDB_Wire_Columns struct { state protoimpl.MessageState `protogen:"open.v1"` Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` + Statement uint32 `protobuf:"varint,2,opt,name=statement,proto3" json:"statement,omitempty"` // index within a Batch; 0 and unused for a Query unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1786,6 +1793,13 @@ func (x *AgentDB_Wire_Columns) GetNames() []string { return nil } +func (x *AgentDB_Wire_Columns) GetStatement() uint32 { + if x != nil { + return x.Statement + } + return 0 +} + // Values are typed per row, as SQLite types them, so a column may mix // classes: walk `types` and take the next entry from the matching array. Text // and blobs are concatenated with one exclusive end offset each, so value i @@ -1887,6 +1901,7 @@ type AgentDB_Wire_ColumnBatch struct { state protoimpl.MessageState `protogen:"open.v1"` Columns []*AgentDB_Wire_Column `protobuf:"bytes,1,rep,name=columns,proto3" json:"columns,omitempty"` // one per name in Columns Rows uint32 `protobuf:"varint,2,opt,name=rows,proto3" json:"rows,omitempty"` + Statement uint32 `protobuf:"varint,3,opt,name=statement,proto3" json:"statement,omitempty"` // index within a Batch; 0 and unused for a Query unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1935,11 +1950,19 @@ func (x *AgentDB_Wire_ColumnBatch) GetRows() uint32 { return 0 } +func (x *AgentDB_Wire_ColumnBatch) GetStatement() uint32 { + if x != nil { + return x.Statement + } + return 0 +} + type AgentDB_Wire_ExecResult struct { state protoimpl.MessageState `protogen:"open.v1"` RowsAffected int64 `protobuf:"varint,1,opt,name=rows_affected,json=rowsAffected,proto3" json:"rows_affected,omitempty"` LastInsertId int64 `protobuf:"varint,2,opt,name=last_insert_id,json=lastInsertId,proto3" json:"last_insert_id,omitempty"` Tip int64 `protobuf:"varint,3,opt,name=tip,proto3" json:"tip,omitempty"` + Statement uint32 `protobuf:"varint,4,opt,name=statement,proto3" json:"statement,omitempty"` // index within a Batch; 0 and unused for an Exec unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1995,6 +2018,13 @@ func (x *AgentDB_Wire_ExecResult) GetTip() int64 { return 0 } +func (x *AgentDB_Wire_ExecResult) GetStatement() uint32 { + if x != nil { + return x.Statement + } + return 0 +} + type AgentDB_Wire_Done struct { state protoimpl.MessageState `protogen:"open.v1"` Tip int64 `protobuf:"varint,1,opt,name=tip,proto3" json:"tip,omitempty"` // the snapshot the query ran at @@ -2103,7 +2133,7 @@ var File_livekit_agentdb_proto protoreflect.FileDescriptor const file_livekit_agentdb_proto_rawDesc = "" + "\n" + - "\x15livekit_agentdb.proto\x12\alivekit\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc1\x1a\n" + + "\x15livekit_agentdb.proto\x12\alivekit\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x9c\x1b\n" + "\aAgentDB\x1a<\n" + "\rCreateRequest\x12+\n" + "\x03ttl\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\x03ttl\x1al\n" + @@ -2143,7 +2173,7 @@ const file_livekit_agentdb_proto_rawDesc = "" + "\fdownload_url\x18\x01 \x01(\tR\vdownloadUrl\x129\n" + "\n" + "expires_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x10\n" + - "\x03tip\x18\x03 \x01(\x03R\x03tip\x1a\xcd\x13\n" + + "\x03tip\x18\x03 \x01(\x03R\x03tip\x1a\xa8\x14\n" + "\x04Wire\x1a\xe0\x04\n" + "\rClientMessage\x12\x1d\n" + "\n" + @@ -2209,9 +2239,10 @@ const file_livekit_agentdb_proto_rawDesc = "" + "\aHelloOk\x12\x10\n" + "\x03tip\x18\x01 \x01(\x03R\x03tip\x12(\n" + "\x10ping_interval_ms\x18\x02 \x01(\rR\x0epingIntervalMs\x12&\n" + - "\x0fping_timeout_ms\x18\x03 \x01(\rR\rpingTimeoutMs\x1a\x1f\n" + + "\x0fping_timeout_ms\x18\x03 \x01(\rR\rpingTimeoutMs\x1a=\n" + "\aColumns\x12\x14\n" + - "\x05names\x18\x01 \x03(\tR\x05names\x1a\xc0\x01\n" + + "\x05names\x18\x01 \x03(\tR\x05names\x12\x1c\n" + + "\tstatement\x18\x02 \x01(\rR\tstatement\x1a\xc0\x01\n" + "\x06Column\x12\x14\n" + "\x05types\x18\x01 \x01(\fR\x05types\x12\x12\n" + "\x04ints\x18\x02 \x03(\x12R\x04ints\x12\x18\n" + @@ -2219,15 +2250,17 @@ const file_livekit_agentdb_proto_rawDesc = "" + "\ttext_data\x18\x04 \x01(\fR\btextData\x12\x1b\n" + "\ttext_ends\x18\x05 \x03(\rR\btextEnds\x12\x1b\n" + "\tblob_data\x18\x06 \x01(\fR\bblobData\x12\x1b\n" + - "\tblob_ends\x18\a \x03(\rR\bblobEnds\x1aY\n" + + "\tblob_ends\x18\a \x03(\rR\bblobEnds\x1aw\n" + "\vColumnBatch\x126\n" + "\acolumns\x18\x01 \x03(\v2\x1c.livekit.AgentDB.Wire.ColumnR\acolumns\x12\x12\n" + - "\x04rows\x18\x02 \x01(\rR\x04rows\x1ai\n" + + "\x04rows\x18\x02 \x01(\rR\x04rows\x12\x1c\n" + + "\tstatement\x18\x03 \x01(\rR\tstatement\x1a\x87\x01\n" + "\n" + "ExecResult\x12#\n" + "\rrows_affected\x18\x01 \x01(\x03R\frowsAffected\x12$\n" + "\x0elast_insert_id\x18\x02 \x01(\x03R\flastInsertId\x12\x10\n" + - "\x03tip\x18\x03 \x01(\x03R\x03tip\x1a7\n" + + "\x03tip\x18\x03 \x01(\x03R\x03tip\x12\x1c\n" + + "\tstatement\x18\x04 \x01(\rR\tstatement\x1a7\n" + "\x04Done\x12\x10\n" + "\x03tip\x18\x01 \x01(\x03R\x03tip\x12\x1d\n" + "\n" +