@radaros/core 0.3.6 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +316 -12
  2. package/dist/index.js +1148 -76
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -65,9 +65,11 @@ var InMemoryStorage = class {
65
65
  // src/session/session-manager.ts
66
66
  var NAMESPACE = "sessions";
67
67
  var SessionManager = class {
68
- constructor(storage) {
68
+ constructor(storage, config) {
69
69
  this.storage = storage;
70
+ this.maxMessages = config?.maxMessages;
70
71
  }
72
+ maxMessages;
71
73
  async getOrCreate(sessionId, userId) {
72
74
  const existing = await this.storage.get(NAMESPACE, sessionId);
73
75
  if (existing) return existing;
@@ -83,16 +85,21 @@ var SessionManager = class {
83
85
  return session;
84
86
  }
85
87
  async appendMessage(sessionId, msg) {
86
- const session = await this.getOrCreate(sessionId);
87
- session.messages.push(msg);
88
- session.updatedAt = /* @__PURE__ */ new Date();
89
- await this.storage.set(NAMESPACE, sessionId, session);
88
+ return this.appendMessages(sessionId, [msg]);
90
89
  }
91
90
  async appendMessages(sessionId, msgs) {
92
91
  const session = await this.getOrCreate(sessionId);
93
92
  session.messages.push(...msgs);
93
+ let overflow = [];
94
+ if (this.maxMessages && session.messages.length > this.maxMessages) {
95
+ overflow = session.messages.splice(
96
+ 0,
97
+ session.messages.length - this.maxMessages
98
+ );
99
+ }
94
100
  session.updatedAt = /* @__PURE__ */ new Date();
95
101
  await this.storage.set(NAMESPACE, sessionId, session);
102
+ return { overflow };
96
103
  }
97
104
  async getHistory(sessionId, limit) {
98
105
  const session = await this.storage.get(NAMESPACE, sessionId);
@@ -782,7 +789,9 @@ var Agent = class {
782
789
  if (typeof storage.initialize === "function") {
783
790
  this.storageInitPromise = storage.initialize();
784
791
  }
785
- this.sessionManager = new SessionManager(storage);
792
+ this.sessionManager = new SessionManager(storage, {
793
+ maxMessages: config.numHistoryRuns ? config.numHistoryRuns * 2 : void 0
794
+ });
786
795
  this.logger = new Logger({
787
796
  level: config.logLevel ?? "silent",
788
797
  prefix: config.name
@@ -844,26 +853,24 @@ var Agent = class {
844
853
  }
845
854
  }
846
855
  }
847
- await this.sessionManager.appendMessages(sessionId, [
856
+ const newMessages = [
848
857
  { role: "user", content: inputText },
849
858
  { role: "assistant", content: output.text }
850
- ]);
859
+ ];
860
+ const { overflow } = await this.sessionManager.appendMessages(
861
+ sessionId,
862
+ newMessages
863
+ );
851
864
  await this.sessionManager.updateState(sessionId, ctx.sessionState);
852
- if (this.config.memory) {
853
- await this.config.memory.addMessages(sessionId, [
854
- { role: "user", content: inputText },
855
- { role: "assistant", content: output.text }
856
- ]);
865
+ if (this.config.memory && overflow.length > 0) {
866
+ this.config.memory.summarize(sessionId, overflow, this.config.model).catch(
867
+ (e) => this.logger.warn(`Memory summarization failed: ${e}`)
868
+ );
857
869
  }
858
870
  if (this.config.userMemory && userId) {
859
- this.config.userMemory.extractAndStore(
860
- userId,
861
- [
862
- { role: "user", content: inputText },
863
- { role: "assistant", content: output.text }
864
- ],
865
- this.config.model
866
- ).catch((e) => this.logger.warn(`UserMemory extraction failed: ${e}`));
871
+ this.config.userMemory.extractAndStore(userId, newMessages, this.config.model).catch(
872
+ (e) => this.logger.warn(`UserMemory extraction failed: ${e}`)
873
+ );
867
874
  }
868
875
  if (this.config.hooks?.afterRun) {
869
876
  await this.config.hooks.afterRun(ctx, output);
@@ -956,26 +963,24 @@ var Agent = class {
956
963
  throw err;
957
964
  } finally {
958
965
  if (streamOk) {
959
- await this.sessionManager.appendMessages(sessionId, [
966
+ const newMessages = [
960
967
  { role: "user", content: inputText },
961
968
  { role: "assistant", content: fullText }
962
- ]);
969
+ ];
970
+ const { overflow } = await this.sessionManager.appendMessages(
971
+ sessionId,
972
+ newMessages
973
+ );
963
974
  await this.sessionManager.updateState(sessionId, ctx.sessionState);
964
- if (this.config.memory) {
965
- await this.config.memory.addMessages(sessionId, [
966
- { role: "user", content: inputText },
967
- { role: "assistant", content: fullText }
968
- ]);
975
+ if (this.config.memory && overflow.length > 0) {
976
+ this.config.memory.summarize(sessionId, overflow, this.config.model).catch(
977
+ (e) => this.logger.warn(`Memory summarization failed: ${e}`)
978
+ );
969
979
  }
970
980
  if (this.config.userMemory && userId) {
971
- this.config.userMemory.extractAndStore(
972
- userId,
973
- [
974
- { role: "user", content: inputText },
975
- { role: "assistant", content: fullText }
976
- ],
977
- this.config.model
978
- ).catch((e) => this.logger.warn(`UserMemory extraction failed: ${e}`));
981
+ this.config.userMemory.extractAndStore(userId, newMessages, this.config.model).catch(
982
+ (e) => this.logger.warn(`UserMemory extraction failed: ${e}`)
983
+ );
979
984
  }
980
985
  this.eventBus.emit("run.complete", {
981
986
  runId: ctx.runId,
@@ -3879,15 +3884,235 @@ var GoogleEmbedding = class {
3879
3884
 
3880
3885
  // src/knowledge/knowledge-base.ts
3881
3886
  import { z } from "zod";
3887
+
3888
+ // src/vector/bm25.ts
3889
+ var STOP_WORDS = /* @__PURE__ */ new Set([
3890
+ "a",
3891
+ "an",
3892
+ "and",
3893
+ "are",
3894
+ "as",
3895
+ "at",
3896
+ "be",
3897
+ "but",
3898
+ "by",
3899
+ "for",
3900
+ "from",
3901
+ "had",
3902
+ "has",
3903
+ "have",
3904
+ "he",
3905
+ "her",
3906
+ "his",
3907
+ "how",
3908
+ "i",
3909
+ "in",
3910
+ "is",
3911
+ "it",
3912
+ "its",
3913
+ "my",
3914
+ "no",
3915
+ "not",
3916
+ "of",
3917
+ "on",
3918
+ "or",
3919
+ "our",
3920
+ "she",
3921
+ "so",
3922
+ "than",
3923
+ "that",
3924
+ "the",
3925
+ "their",
3926
+ "them",
3927
+ "then",
3928
+ "there",
3929
+ "these",
3930
+ "they",
3931
+ "this",
3932
+ "to",
3933
+ "up",
3934
+ "was",
3935
+ "we",
3936
+ "were",
3937
+ "what",
3938
+ "when",
3939
+ "which",
3940
+ "who",
3941
+ "will",
3942
+ "with",
3943
+ "you",
3944
+ "your"
3945
+ ]);
3946
+ function tokenize(text) {
3947
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 1 && !STOP_WORDS.has(t));
3948
+ }
3949
+ var BM25Index = class {
3950
+ docs = /* @__PURE__ */ new Map();
3951
+ docFreqs = /* @__PURE__ */ new Map();
3952
+ avgDocLength = 0;
3953
+ /** BM25 free parameter: term frequency saturation. */
3954
+ k1;
3955
+ /** BM25 free parameter: document length normalization. */
3956
+ b;
3957
+ constructor(opts) {
3958
+ this.k1 = opts?.k1 ?? 1.5;
3959
+ this.b = opts?.b ?? 0.75;
3960
+ }
3961
+ get size() {
3962
+ return this.docs.size;
3963
+ }
3964
+ add(doc) {
3965
+ this.remove(doc.id);
3966
+ const tokens = tokenize(doc.content);
3967
+ const termFreqs = /* @__PURE__ */ new Map();
3968
+ for (const token of tokens) {
3969
+ termFreqs.set(token, (termFreqs.get(token) ?? 0) + 1);
3970
+ }
3971
+ const entry = {
3972
+ id: doc.id,
3973
+ content: doc.content,
3974
+ metadata: doc.metadata,
3975
+ termFreqs,
3976
+ length: tokens.length
3977
+ };
3978
+ this.docs.set(doc.id, entry);
3979
+ for (const term of termFreqs.keys()) {
3980
+ this.docFreqs.set(term, (this.docFreqs.get(term) ?? 0) + 1);
3981
+ }
3982
+ this.recomputeAvgLength();
3983
+ }
3984
+ addBatch(docs) {
3985
+ for (const doc of docs) {
3986
+ this.add(doc);
3987
+ }
3988
+ }
3989
+ remove(id) {
3990
+ const existing = this.docs.get(id);
3991
+ if (!existing) return;
3992
+ for (const term of existing.termFreqs.keys()) {
3993
+ const count = this.docFreqs.get(term) ?? 1;
3994
+ if (count <= 1) {
3995
+ this.docFreqs.delete(term);
3996
+ } else {
3997
+ this.docFreqs.set(term, count - 1);
3998
+ }
3999
+ }
4000
+ this.docs.delete(id);
4001
+ this.recomputeAvgLength();
4002
+ }
4003
+ clear() {
4004
+ this.docs.clear();
4005
+ this.docFreqs.clear();
4006
+ this.avgDocLength = 0;
4007
+ }
4008
+ search(query, opts) {
4009
+ const queryTokens = tokenize(query);
4010
+ if (queryTokens.length === 0) return [];
4011
+ const N = this.docs.size;
4012
+ if (N === 0) return [];
4013
+ const topK = opts?.topK ?? 10;
4014
+ const results = [];
4015
+ for (const doc of this.docs.values()) {
4016
+ if (opts?.filter) {
4017
+ let match = true;
4018
+ for (const [k, v] of Object.entries(opts.filter)) {
4019
+ if (doc.metadata?.[k] !== v) {
4020
+ match = false;
4021
+ break;
4022
+ }
4023
+ }
4024
+ if (!match) continue;
4025
+ }
4026
+ let score = 0;
4027
+ for (const term of queryTokens) {
4028
+ const tf = doc.termFreqs.get(term) ?? 0;
4029
+ if (tf === 0) continue;
4030
+ const df = this.docFreqs.get(term) ?? 0;
4031
+ const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
4032
+ const numerator = tf * (this.k1 + 1);
4033
+ const denominator = tf + this.k1 * (1 - this.b + this.b * (doc.length / this.avgDocLength));
4034
+ score += idf * (numerator / denominator);
4035
+ }
4036
+ if (score > 0 && (opts?.minScore == null || score >= opts.minScore)) {
4037
+ results.push({
4038
+ id: doc.id,
4039
+ content: doc.content,
4040
+ score,
4041
+ metadata: doc.metadata
4042
+ });
4043
+ }
4044
+ }
4045
+ results.sort((a, b) => b.score - a.score);
4046
+ return results.slice(0, topK);
4047
+ }
4048
+ recomputeAvgLength() {
4049
+ if (this.docs.size === 0) {
4050
+ this.avgDocLength = 0;
4051
+ return;
4052
+ }
4053
+ let total = 0;
4054
+ for (const doc of this.docs.values()) {
4055
+ total += doc.length;
4056
+ }
4057
+ this.avgDocLength = total / this.docs.size;
4058
+ }
4059
+ };
4060
+
4061
+ // src/vector/rrf.ts
4062
+ function reciprocalRankFusion(rankedLists, options) {
4063
+ const k = options?.k ?? 60;
4064
+ const topK = options?.topK ?? 10;
4065
+ const weights = options?.weights ?? rankedLists.map(() => 1);
4066
+ const fusedScores = /* @__PURE__ */ new Map();
4067
+ for (let listIdx = 0; listIdx < rankedLists.length; listIdx++) {
4068
+ const list = rankedLists[listIdx];
4069
+ const weight = weights[listIdx] ?? 1;
4070
+ for (let rank = 0; rank < list.length; rank++) {
4071
+ const item = list[rank];
4072
+ const rrfScore = weight / (k + rank + 1);
4073
+ const existing = fusedScores.get(item.id);
4074
+ if (existing) {
4075
+ existing.score += rrfScore;
4076
+ } else {
4077
+ fusedScores.set(item.id, {
4078
+ score: rrfScore,
4079
+ item: { ...item }
4080
+ });
4081
+ }
4082
+ }
4083
+ }
4084
+ const results = Array.from(fusedScores.values()).map(({ score, item }) => ({
4085
+ ...item,
4086
+ score
4087
+ }));
4088
+ results.sort((a, b) => b.score - a.score);
4089
+ if (options?.minScore != null) {
4090
+ const min = options.minScore;
4091
+ return results.filter((r) => r.score >= min).slice(0, topK);
4092
+ }
4093
+ return results.slice(0, topK);
4094
+ }
4095
+
4096
+ // src/knowledge/knowledge-base.ts
3882
4097
  var KnowledgeBase = class {
3883
4098
  name;
3884
4099
  collection;
3885
4100
  store;
3886
4101
  initialized = false;
4102
+ bm25;
4103
+ defaultSearchMode;
4104
+ hybridConfig;
3887
4105
  constructor(config) {
3888
4106
  this.name = config.name;
3889
4107
  this.store = config.vectorStore;
3890
4108
  this.collection = config.collection ?? config.name.toLowerCase().replace(/\s+/g, "_");
4109
+ this.defaultSearchMode = config.searchMode ?? "vector";
4110
+ this.hybridConfig = {
4111
+ vectorWeight: config.hybridConfig?.vectorWeight ?? 1,
4112
+ keywordWeight: config.hybridConfig?.keywordWeight ?? 1,
4113
+ rrfK: config.hybridConfig?.rrfK ?? 60
4114
+ };
4115
+ this.bm25 = new BM25Index();
3891
4116
  }
3892
4117
  async initialize() {
3893
4118
  if (this.initialized) return;
@@ -3897,14 +4122,27 @@ var KnowledgeBase = class {
3897
4122
  async add(doc) {
3898
4123
  await this.ensureInit();
3899
4124
  await this.store.upsert(this.collection, doc);
4125
+ this.bm25.add({ id: doc.id, content: doc.content, metadata: doc.metadata });
3900
4126
  }
3901
4127
  async addDocuments(docs) {
3902
4128
  await this.ensureInit();
3903
4129
  await this.store.upsertBatch(this.collection, docs);
4130
+ this.bm25.addBatch(
4131
+ docs.map((d) => ({ id: d.id, content: d.content, metadata: d.metadata }))
4132
+ );
3904
4133
  }
3905
4134
  async search(query, options) {
3906
4135
  await this.ensureInit();
3907
- return this.store.search(this.collection, query, options);
4136
+ const mode = options?.searchMode ?? this.defaultSearchMode;
4137
+ switch (mode) {
4138
+ case "keyword":
4139
+ return this.keywordSearch(query, options);
4140
+ case "hybrid":
4141
+ return this.hybridSearch(query, options);
4142
+ case "vector":
4143
+ default:
4144
+ return this.store.search(this.collection, query, options);
4145
+ }
3908
4146
  }
3909
4147
  async get(id) {
3910
4148
  await this.ensureInit();
@@ -3913,9 +4151,11 @@ var KnowledgeBase = class {
3913
4151
  async delete(id) {
3914
4152
  await this.ensureInit();
3915
4153
  await this.store.delete(this.collection, id);
4154
+ this.bm25.remove(id);
3916
4155
  }
3917
4156
  async clear() {
3918
4157
  await this.store.dropCollection(this.collection);
4158
+ this.bm25.clear();
3919
4159
  }
3920
4160
  async close() {
3921
4161
  await this.store.close();
@@ -3928,6 +4168,7 @@ var KnowledgeBase = class {
3928
4168
  const topK = config.topK ?? 5;
3929
4169
  const minScore = config.minScore;
3930
4170
  const filter = config.filter;
4171
+ const searchMode = config.searchMode;
3931
4172
  const toolName = config.toolName ?? `search_${this.collection}`;
3932
4173
  const description = config.description ?? `Search the "${this.name}" knowledge base for relevant information. Use this before answering questions related to ${this.name}.`;
3933
4174
  const formatResults = config.formatResults ?? defaultFormatResults;
@@ -3942,7 +4183,8 @@ var KnowledgeBase = class {
3942
4183
  const results = await kb.search(args.query, {
3943
4184
  topK,
3944
4185
  minScore,
3945
- filter
4186
+ filter,
4187
+ searchMode
3946
4188
  });
3947
4189
  if (results.length === 0) {
3948
4190
  return "No relevant documents found in the knowledge base.";
@@ -3951,6 +4193,60 @@ var KnowledgeBase = class {
3951
4193
  }
3952
4194
  };
3953
4195
  }
4196
+ keywordSearch(query, options) {
4197
+ const results = this.bm25.search(query, {
4198
+ topK: options?.topK ?? 10,
4199
+ filter: options?.filter
4200
+ });
4201
+ return results.map((r) => ({
4202
+ id: r.id,
4203
+ content: r.content,
4204
+ score: r.score,
4205
+ metadata: r.metadata
4206
+ }));
4207
+ }
4208
+ async hybridSearch(query, options) {
4209
+ const topK = options?.topK ?? 10;
4210
+ const fetchK = topK * 2;
4211
+ const [vectorResults, keywordResults] = await Promise.all([
4212
+ this.store.search(this.collection, query, {
4213
+ ...options,
4214
+ topK: fetchK
4215
+ }),
4216
+ Promise.resolve(
4217
+ this.bm25.search(query, {
4218
+ topK: fetchK,
4219
+ filter: options?.filter
4220
+ })
4221
+ )
4222
+ ]);
4223
+ const vectorRanked = vectorResults.map((r) => ({
4224
+ id: r.id,
4225
+ content: r.content,
4226
+ score: r.score,
4227
+ metadata: r.metadata
4228
+ }));
4229
+ const keywordRanked = keywordResults.map((r) => ({
4230
+ id: r.id,
4231
+ content: r.content,
4232
+ score: r.score,
4233
+ metadata: r.metadata
4234
+ }));
4235
+ const fused = reciprocalRankFusion(
4236
+ [vectorRanked, keywordRanked],
4237
+ {
4238
+ k: this.hybridConfig.rrfK,
4239
+ topK,
4240
+ weights: [this.hybridConfig.vectorWeight, this.hybridConfig.keywordWeight]
4241
+ }
4242
+ );
4243
+ return fused.map((r) => ({
4244
+ id: r.id,
4245
+ content: r.content,
4246
+ score: r.score,
4247
+ metadata: r.metadata
4248
+ }));
4249
+ }
3954
4250
  async ensureInit() {
3955
4251
  if (!this.initialized) await this.initialize();
3956
4252
  }
@@ -3968,61 +4264,105 @@ ${lines.join("\n\n")}`;
3968
4264
  }
3969
4265
 
3970
4266
  // src/memory/memory.ts
3971
- var SHORT_TERM_NS = "memory:short";
3972
4267
  var LONG_TERM_NS = "memory:long";
4268
+ var SUMMARIZE_PROMPT = `Summarize the following conversation chunk in 2-3 concise sentences. Focus on key topics discussed, decisions made, and important information shared. Do not include greetings or filler.
4269
+
4270
+ Conversation:
4271
+ {conversation}
4272
+
4273
+ Summary:`;
3973
4274
  var Memory = class {
3974
4275
  storage;
3975
- maxShortTermMessages;
3976
- enableLongTerm;
4276
+ model;
4277
+ maxSummaries;
4278
+ initPromise = null;
3977
4279
  constructor(config) {
3978
4280
  this.storage = config?.storage ?? new InMemoryStorage();
3979
- this.maxShortTermMessages = config?.maxShortTermMessages ?? 50;
3980
- this.enableLongTerm = config?.enableLongTerm ?? false;
3981
- }
3982
- async addMessages(sessionId, messages) {
3983
- const existing = await this.storage.get(SHORT_TERM_NS, sessionId) ?? [];
3984
- const updated = [...existing, ...messages];
3985
- if (updated.length > this.maxShortTermMessages) {
3986
- const overflow = updated.splice(
3987
- 0,
3988
- updated.length - this.maxShortTermMessages
4281
+ this.model = config?.model;
4282
+ this.maxSummaries = config?.maxSummaries ?? 20;
4283
+ }
4284
+ ensureInitialized() {
4285
+ if (!this.initPromise) {
4286
+ this.initPromise = (async () => {
4287
+ if (typeof this.storage.initialize === "function") {
4288
+ await this.storage.initialize();
4289
+ }
4290
+ })();
4291
+ }
4292
+ return this.initPromise;
4293
+ }
4294
+ /**
4295
+ * Summarize overflow messages and store the summary.
4296
+ * Uses the configured LLM model; falls back to basic concatenation if no model.
4297
+ */
4298
+ async summarize(sessionId, messages, fallbackModel) {
4299
+ await this.ensureInitialized();
4300
+ const textParts = messages.filter((m) => m.content).map((m) => `${m.role}: ${getTextContent(m.content)}`);
4301
+ if (textParts.length === 0) return;
4302
+ const model = this.model ?? fallbackModel;
4303
+ let summary;
4304
+ if (model) {
4305
+ try {
4306
+ const prompt = SUMMARIZE_PROMPT.replace(
4307
+ "{conversation}",
4308
+ textParts.join("\n")
4309
+ );
4310
+ const response = await model.generate(
4311
+ [{ role: "user", content: prompt }],
4312
+ { temperature: 0, maxTokens: 300 }
4313
+ );
4314
+ summary = typeof response.message.content === "string" ? response.message.content.trim() : textParts.join(" | ").slice(0, 500);
4315
+ } catch {
4316
+ summary = textParts.join(" | ").slice(0, 500);
4317
+ }
4318
+ } else {
4319
+ summary = textParts.join(" | ").slice(0, 500);
4320
+ }
4321
+ const entry = {
4322
+ key: `${sessionId}:${Date.now()}`,
4323
+ summary,
4324
+ createdAt: /* @__PURE__ */ new Date()
4325
+ };
4326
+ await this.storage.set(LONG_TERM_NS, entry.key, entry);
4327
+ const all = await this.storage.list(LONG_TERM_NS, sessionId);
4328
+ if (all.length > this.maxSummaries) {
4329
+ const sorted = all.sort(
4330
+ (a, b) => new Date(a.value.createdAt).getTime() - new Date(b.value.createdAt).getTime()
3989
4331
  );
3990
- if (this.enableLongTerm && overflow.length > 0) {
3991
- await this.summarizeAndStore(sessionId, overflow);
4332
+ const toDelete = sorted.slice(0, all.length - this.maxSummaries);
4333
+ for (const item of toDelete) {
4334
+ await this.storage.delete(LONG_TERM_NS, item.key);
3992
4335
  }
3993
4336
  }
3994
- await this.storage.set(SHORT_TERM_NS, sessionId, updated);
3995
- }
3996
- async getMessages(sessionId) {
3997
- return await this.storage.get(SHORT_TERM_NS, sessionId) ?? [];
3998
4337
  }
4338
+ /** Get all stored summaries for a session. */
3999
4339
  async getSummaries(sessionId) {
4000
- if (!this.enableLongTerm) return [];
4340
+ await this.ensureInitialized();
4001
4341
  const entries = await this.storage.list(
4002
4342
  LONG_TERM_NS,
4003
4343
  sessionId
4004
4344
  );
4005
- return entries.map((e) => e.value.summary);
4345
+ return entries.sort(
4346
+ (a, b) => new Date(a.value.createdAt).getTime() - new Date(b.value.createdAt).getTime()
4347
+ ).map((e) => e.value.summary);
4006
4348
  }
4349
+ /** Get summaries formatted as a context string for injection into the system prompt. */
4007
4350
  async getContextString(sessionId) {
4008
4351
  const summaries = await this.getSummaries(sessionId);
4009
4352
  if (summaries.length === 0) return "";
4010
- return `Previous context:
4353
+ return `Previous conversation context:
4011
4354
  ${summaries.join("\n")}`;
4012
4355
  }
4013
- async summarizeAndStore(sessionId, messages) {
4014
- const textParts = messages.filter((m) => m.content).map((m) => `${m.role}: ${getTextContent(m.content)}`);
4015
- if (textParts.length === 0) return;
4016
- const summary = textParts.join(" | ").slice(0, 500);
4017
- const entry = {
4018
- key: `${sessionId}:${Date.now()}`,
4019
- summary,
4020
- createdAt: /* @__PURE__ */ new Date()
4021
- };
4022
- await this.storage.set(LONG_TERM_NS, entry.key, entry);
4023
- }
4356
+ /** Clear all summaries for a session. */
4024
4357
  async clear(sessionId) {
4025
- await this.storage.delete(SHORT_TERM_NS, sessionId);
4358
+ await this.ensureInitialized();
4359
+ const entries = await this.storage.list(
4360
+ LONG_TERM_NS,
4361
+ sessionId
4362
+ );
4363
+ for (const entry of entries) {
4364
+ await this.storage.delete(LONG_TERM_NS, entry.key);
4365
+ }
4026
4366
  }
4027
4367
  };
4028
4368
 
@@ -4556,6 +4896,733 @@ var A2ARemoteAgent = class {
4556
4896
  }
4557
4897
  };
4558
4898
 
4899
+ // src/voice/voice-agent.ts
4900
+ import { EventEmitter as EventEmitter2 } from "events";
4901
+ import { v4 as uuidv46 } from "uuid";
4902
+ var VoiceSessionImpl = class extends EventEmitter2 {
4903
+ connection;
4904
+ constructor(connection) {
4905
+ super();
4906
+ this.connection = connection;
4907
+ }
4908
+ sendAudio(data) {
4909
+ this.connection.sendAudio(data);
4910
+ }
4911
+ sendText(text) {
4912
+ this.connection.sendText(text);
4913
+ }
4914
+ interrupt() {
4915
+ this.connection.interrupt();
4916
+ }
4917
+ async close() {
4918
+ await this.connection.close();
4919
+ }
4920
+ on(event, handler) {
4921
+ return super.on(event, handler);
4922
+ }
4923
+ off(event, handler) {
4924
+ return super.off(event, handler);
4925
+ }
4926
+ };
4927
+ var VoiceAgent = class {
4928
+ name;
4929
+ config;
4930
+ eventBus;
4931
+ logger;
4932
+ toolExecutor;
4933
+ constructor(config) {
4934
+ this.name = config.name;
4935
+ this.config = config;
4936
+ this.eventBus = config.eventBus ?? new EventBus();
4937
+ this.logger = new Logger({
4938
+ level: config.logLevel ?? "silent",
4939
+ prefix: config.name
4940
+ });
4941
+ this.toolExecutor = config.tools && config.tools.length > 0 ? new ToolExecutor(config.tools) : null;
4942
+ }
4943
+ async connect(opts) {
4944
+ const toolDefs = this.toolExecutor?.getToolDefinitions() ?? [];
4945
+ const sessionId = opts?.sessionId ?? this.config.sessionId ?? `voice_${uuidv46()}`;
4946
+ const userId = opts?.userId ?? this.config.userId;
4947
+ let instructions = this.config.instructions ?? "";
4948
+ if (this.config.userMemory && userId) {
4949
+ const userContext = await this.config.userMemory.getContextString(userId);
4950
+ if (userContext) {
4951
+ instructions = instructions ? `${instructions}
4952
+
4953
+ ${userContext}` : userContext;
4954
+ const facts = await this.config.userMemory.getFacts(userId);
4955
+ this.logger.info(
4956
+ `Loaded ${facts.length} user facts for "${userId}"`
4957
+ );
4958
+ }
4959
+ }
4960
+ const sessionConfig = {
4961
+ instructions,
4962
+ voice: this.config.voice,
4963
+ tools: toolDefs,
4964
+ inputAudioFormat: this.config.inputAudioFormat,
4965
+ outputAudioFormat: this.config.outputAudioFormat,
4966
+ turnDetection: this.config.turnDetection,
4967
+ temperature: this.config.temperature,
4968
+ maxResponseOutputTokens: this.config.maxResponseOutputTokens,
4969
+ apiKey: opts?.apiKey
4970
+ };
4971
+ this.logger.info("Connecting to realtime provider...");
4972
+ const connection = await this.config.provider.connect(sessionConfig);
4973
+ const session = new VoiceSessionImpl(connection);
4974
+ const ctx = new RunContext({
4975
+ sessionId,
4976
+ userId,
4977
+ eventBus: this.eventBus
4978
+ });
4979
+ const transcripts = [];
4980
+ let persisted = false;
4981
+ this.wireEvents(connection, session, ctx, transcripts);
4982
+ const onSessionEnd = async () => {
4983
+ if (persisted) return;
4984
+ persisted = true;
4985
+ await this.persistSession(sessionId, userId, transcripts);
4986
+ };
4987
+ session.on("disconnected", () => {
4988
+ onSessionEnd().catch(
4989
+ (e) => this.logger.warn(`Session persist failed: ${e}`)
4990
+ );
4991
+ });
4992
+ const originalClose = session.close.bind(session);
4993
+ session.close = async () => {
4994
+ await originalClose();
4995
+ await onSessionEnd();
4996
+ };
4997
+ this.eventBus.emit("voice.connected", { agentName: this.name });
4998
+ this.logger.info(
4999
+ `Voice session connected (session=${sessionId}, user=${userId ?? "anonymous"})`
5000
+ );
5001
+ return session;
5002
+ }
5003
+ /**
5004
+ * Consolidate transcript deltas into full messages.
5005
+ * Voice transcripts arrive as many small chunks (one per word/syllable).
5006
+ * Consecutive entries with the same role are merged into a single message.
5007
+ */
5008
+ consolidateTranscripts(transcripts) {
5009
+ const consolidated = [];
5010
+ let current = null;
5011
+ for (const t of transcripts) {
5012
+ if (current && current.role === t.role) {
5013
+ current.content += t.text;
5014
+ } else {
5015
+ if (current && current.content.trim()) {
5016
+ consolidated.push(current);
5017
+ }
5018
+ current = { role: t.role, content: t.text };
5019
+ }
5020
+ }
5021
+ if (current && current.content.trim()) {
5022
+ consolidated.push(current);
5023
+ }
5024
+ return consolidated;
5025
+ }
5026
+ async persistSession(_sessionId, userId, transcripts) {
5027
+ if (transcripts.length === 0) return;
5028
+ const messages = this.consolidateTranscripts(transcripts);
5029
+ this.logger.info(
5030
+ `Consolidated ${transcripts.length} transcript deltas into ${messages.length} messages`
5031
+ );
5032
+ for (const m of messages) {
5033
+ this.logger.info(` [${m.role}] ${m.content.substring(0, 100)}`);
5034
+ }
5035
+ if (this.config.userMemory && userId) {
5036
+ try {
5037
+ await this.config.userMemory.extractAndStore(
5038
+ userId,
5039
+ messages,
5040
+ this.config.model
5041
+ );
5042
+ const facts = await this.config.userMemory.getFacts(userId);
5043
+ this.logger.info(
5044
+ `Extracted user facts for "${userId}" \u2014 total: ${facts.length}`
5045
+ );
5046
+ } catch (e) {
5047
+ this.logger.warn(`UserMemory extraction failed: ${e.message ?? e}`);
5048
+ }
5049
+ }
5050
+ }
5051
+ wireEvents(connection, session, ctx, transcripts) {
5052
+ connection.on("audio", (data) => {
5053
+ session.emit("audio", data);
5054
+ this.eventBus.emit("voice.audio", {
5055
+ agentName: this.name,
5056
+ data: data.data
5057
+ });
5058
+ });
5059
+ connection.on("text", (data) => {
5060
+ session.emit("text", data);
5061
+ });
5062
+ connection.on("transcript", (data) => {
5063
+ session.emit("transcript", data);
5064
+ transcripts.push({ role: data.role, text: data.text });
5065
+ this.eventBus.emit("voice.transcript", {
5066
+ agentName: this.name,
5067
+ text: data.text,
5068
+ role: data.role
5069
+ });
5070
+ this.logger.info(`[${data.role}] ${data.text}`);
5071
+ });
5072
+ connection.on("tool_call", (toolCall) => {
5073
+ this.handleToolCall(connection, session, ctx, toolCall);
5074
+ });
5075
+ connection.on("interrupted", () => {
5076
+ session.emit("interrupted", {});
5077
+ this.logger.debug("Response interrupted by user speech");
5078
+ });
5079
+ connection.on("error", (data) => {
5080
+ session.emit("error", data);
5081
+ this.eventBus.emit("voice.error", {
5082
+ agentName: this.name,
5083
+ error: data.error
5084
+ });
5085
+ this.logger.error(`Error: ${data.error.message}`);
5086
+ });
5087
+ connection.on("disconnected", () => {
5088
+ session.emit("disconnected", {});
5089
+ this.eventBus.emit("voice.disconnected", { agentName: this.name });
5090
+ this.logger.info("Voice session disconnected");
5091
+ });
5092
+ }
5093
+ async handleToolCall(connection, session, ctx, toolCall) {
5094
+ if (!this.toolExecutor) {
5095
+ this.logger.warn(
5096
+ `Tool call "${toolCall.name}" received but no tools registered`
5097
+ );
5098
+ connection.sendToolResult(
5099
+ toolCall.id,
5100
+ JSON.stringify({ error: "No tools available" })
5101
+ );
5102
+ return;
5103
+ }
5104
+ let parsedArgs = {};
5105
+ try {
5106
+ parsedArgs = JSON.parse(toolCall.arguments || "{}");
5107
+ } catch {
5108
+ parsedArgs = {};
5109
+ }
5110
+ session.emit("tool_call_start", {
5111
+ name: toolCall.name,
5112
+ args: parsedArgs
5113
+ });
5114
+ this.eventBus.emit("voice.tool.call", {
5115
+ agentName: this.name,
5116
+ toolName: toolCall.name,
5117
+ args: parsedArgs
5118
+ });
5119
+ this.logger.info(`Tool call: ${toolCall.name}`);
5120
+ try {
5121
+ const results = await this.toolExecutor.executeAll(
5122
+ [{ id: toolCall.id, name: toolCall.name, arguments: parsedArgs }],
5123
+ ctx
5124
+ );
5125
+ const result = results[0];
5126
+ const resultContent = typeof result.result === "string" ? result.result : result.result.content;
5127
+ connection.sendToolResult(toolCall.id, resultContent);
5128
+ session.emit("tool_result", {
5129
+ name: toolCall.name,
5130
+ result: resultContent
5131
+ });
5132
+ this.eventBus.emit("voice.tool.result", {
5133
+ agentName: this.name,
5134
+ toolName: toolCall.name,
5135
+ result: resultContent
5136
+ });
5137
+ this.logger.info(
5138
+ `Tool result: ${toolCall.name} -> ${resultContent.substring(0, 100)}`
5139
+ );
5140
+ } catch (error) {
5141
+ const errMsg = error?.message ?? "Tool execution failed";
5142
+ connection.sendToolResult(
5143
+ toolCall.id,
5144
+ JSON.stringify({ error: errMsg })
5145
+ );
5146
+ this.logger.error(`Tool error: ${toolCall.name} -> ${errMsg}`);
5147
+ }
5148
+ }
5149
+ };
5150
+
5151
+ // src/voice/providers/openai-realtime.ts
5152
+ import { EventEmitter as EventEmitter3 } from "events";
5153
+ import { createRequire as createRequire16 } from "module";
5154
+ var _require16 = createRequire16(import.meta.url);
5155
+ function toOpenAIAudioFormat(fmt) {
5156
+ if (!fmt) return "pcm16";
5157
+ switch (fmt) {
5158
+ case "pcm16":
5159
+ return "pcm16";
5160
+ case "g711_ulaw":
5161
+ return "g711_ulaw";
5162
+ case "g711_alaw":
5163
+ return "g711_alaw";
5164
+ default:
5165
+ return "pcm16";
5166
+ }
5167
+ }
5168
+ var OpenAIRealtimeConnection = class extends EventEmitter3 {
5169
+ ws;
5170
+ closed = false;
5171
+ constructor(ws) {
5172
+ super();
5173
+ this.ws = ws;
5174
+ }
5175
+ sendAudio(data) {
5176
+ if (this.closed) return;
5177
+ this.send({
5178
+ type: "input_audio_buffer.append",
5179
+ audio: data.toString("base64")
5180
+ });
5181
+ }
5182
+ sendText(text) {
5183
+ if (this.closed) return;
5184
+ this.send({
5185
+ type: "conversation.item.create",
5186
+ item: {
5187
+ type: "message",
5188
+ role: "user",
5189
+ content: [{ type: "input_text", text }]
5190
+ }
5191
+ });
5192
+ this.send({ type: "response.create" });
5193
+ }
5194
+ sendToolResult(callId, result) {
5195
+ if (this.closed) return;
5196
+ this.send({
5197
+ type: "conversation.item.create",
5198
+ item: {
5199
+ type: "function_call_output",
5200
+ call_id: callId,
5201
+ output: result
5202
+ }
5203
+ });
5204
+ this.send({ type: "response.create" });
5205
+ }
5206
+ interrupt() {
5207
+ if (this.closed) return;
5208
+ this.send({ type: "response.cancel" });
5209
+ }
5210
+ async close() {
5211
+ if (this.closed) return;
5212
+ this.closed = true;
5213
+ try {
5214
+ this.ws.close();
5215
+ } catch {
5216
+ }
5217
+ this.emit("disconnected", {});
5218
+ }
5219
+ on(event, handler) {
5220
+ return super.on(event, handler);
5221
+ }
5222
+ off(event, handler) {
5223
+ return super.off(event, handler);
5224
+ }
5225
+ send(event) {
5226
+ if (this.ws.readyState === 1) {
5227
+ this.ws.send(JSON.stringify(event));
5228
+ }
5229
+ }
5230
+ /** Called by the provider to set up server event handling. */
5231
+ _bindServerEvents() {
5232
+ this.ws.on("message", (raw) => {
5233
+ try {
5234
+ const data = JSON.parse(typeof raw === "string" ? raw : raw.toString());
5235
+ this.handleServerEvent(data);
5236
+ } catch {
5237
+ }
5238
+ });
5239
+ this.ws.on("error", (err) => {
5240
+ this.emit("error", { error: err });
5241
+ });
5242
+ this.ws.on("close", () => {
5243
+ if (!this.closed) {
5244
+ this.closed = true;
5245
+ this.emit("disconnected", {});
5246
+ }
5247
+ });
5248
+ }
5249
+ pendingFunctionCalls = /* @__PURE__ */ new Map();
5250
+ handleServerEvent(event) {
5251
+ switch (event.type) {
5252
+ case "session.created":
5253
+ this.emit("connected", {});
5254
+ break;
5255
+ case "response.audio.delta":
5256
+ if (event.delta) {
5257
+ this.emit("audio", {
5258
+ data: Buffer.from(event.delta, "base64"),
5259
+ mimeType: "audio/pcm"
5260
+ });
5261
+ }
5262
+ break;
5263
+ case "response.audio_transcript.delta":
5264
+ if (event.delta) {
5265
+ this.emit("transcript", { text: event.delta, role: "assistant" });
5266
+ }
5267
+ break;
5268
+ case "response.text.delta":
5269
+ if (event.delta) {
5270
+ this.emit("text", { text: event.delta });
5271
+ }
5272
+ break;
5273
+ case "input_audio_buffer.speech_started":
5274
+ this.emit("interrupted", {});
5275
+ break;
5276
+ case "conversation.item.input_audio_transcription.completed":
5277
+ if (event.transcript) {
5278
+ this.emit("transcript", { text: event.transcript, role: "user" });
5279
+ }
5280
+ break;
5281
+ case "response.function_call_arguments.delta":
5282
+ if (event.item_id) {
5283
+ const pending = this.pendingFunctionCalls.get(event.item_id);
5284
+ if (pending) {
5285
+ pending.args += event.delta ?? "";
5286
+ }
5287
+ }
5288
+ break;
5289
+ case "response.output_item.added":
5290
+ if (event.item?.type === "function_call") {
5291
+ this.pendingFunctionCalls.set(event.item.id, {
5292
+ name: event.item.name ?? "",
5293
+ args: ""
5294
+ });
5295
+ }
5296
+ break;
5297
+ case "response.output_item.done":
5298
+ if (event.item?.type === "function_call") {
5299
+ const pending = this.pendingFunctionCalls.get(event.item.id);
5300
+ const toolCall = {
5301
+ id: event.item.call_id ?? event.item.id,
5302
+ name: pending?.name ?? event.item.name ?? "",
5303
+ arguments: pending?.args ?? event.item.arguments ?? "{}"
5304
+ };
5305
+ this.pendingFunctionCalls.delete(event.item.id);
5306
+ this.emit("tool_call", toolCall);
5307
+ }
5308
+ break;
5309
+ case "error":
5310
+ this.emit("error", {
5311
+ error: new Error(event.error?.message ?? "Realtime API error")
5312
+ });
5313
+ break;
5314
+ }
5315
+ }
5316
+ };
5317
+ var OpenAIRealtimeProvider = class {
5318
+ providerId = "openai-realtime";
5319
+ modelId;
5320
+ apiKey;
5321
+ baseURL;
5322
+ constructor(modelId, config) {
5323
+ this.modelId = modelId ?? "gpt-4o-realtime-preview";
5324
+ this.apiKey = config?.apiKey;
5325
+ this.baseURL = config?.baseURL;
5326
+ }
5327
+ async connect(config) {
5328
+ let WebSocket;
5329
+ try {
5330
+ WebSocket = _require16("ws");
5331
+ } catch {
5332
+ throw new Error("ws package is required for OpenAIRealtimeProvider. Install it: npm install ws");
5333
+ }
5334
+ const key = config.apiKey ?? this.apiKey ?? process.env.OPENAI_API_KEY;
5335
+ if (!key) {
5336
+ throw new Error(
5337
+ "No OpenAI API key provided for realtime connection. Set OPENAI_API_KEY env var or pass apiKey."
5338
+ );
5339
+ }
5340
+ const base = this.baseURL ?? "wss://api.openai.com";
5341
+ const url = `${base}/v1/realtime?model=${encodeURIComponent(this.modelId)}`;
5342
+ const ws = new WebSocket(url, {
5343
+ headers: {
5344
+ Authorization: `Bearer ${key}`,
5345
+ "OpenAI-Beta": "realtime=v1"
5346
+ }
5347
+ });
5348
+ const connection = new OpenAIRealtimeConnection(ws);
5349
+ return new Promise((resolve, reject) => {
5350
+ const TIMEOUT_MS = 3e4;
5351
+ const timeout = setTimeout(() => {
5352
+ reject(new Error(`OpenAI Realtime connection timed out after ${TIMEOUT_MS / 1e3}s`));
5353
+ try {
5354
+ ws.close();
5355
+ } catch {
5356
+ }
5357
+ }, TIMEOUT_MS);
5358
+ ws.on("open", () => {
5359
+ clearTimeout(timeout);
5360
+ connection._bindServerEvents();
5361
+ const sessionUpdate = {
5362
+ type: "session.update",
5363
+ session: this.buildSessionPayload(config)
5364
+ };
5365
+ ws.send(JSON.stringify(sessionUpdate));
5366
+ resolve(connection);
5367
+ });
5368
+ ws.on("error", (err) => {
5369
+ clearTimeout(timeout);
5370
+ reject(new Error(`OpenAI Realtime WebSocket error: ${err.message}`));
5371
+ });
5372
+ ws.on("unexpected-response", (_req, res) => {
5373
+ clearTimeout(timeout);
5374
+ let body = "";
5375
+ res.on("data", (chunk) => {
5376
+ body += chunk.toString();
5377
+ });
5378
+ res.on("end", () => {
5379
+ reject(new Error(`OpenAI Realtime rejected (HTTP ${res.statusCode}): ${body}`));
5380
+ });
5381
+ });
5382
+ });
5383
+ }
5384
+ buildSessionPayload(config) {
5385
+ const session = {
5386
+ modalities: ["text", "audio"],
5387
+ model: this.modelId
5388
+ };
5389
+ if (config.instructions) {
5390
+ session.instructions = config.instructions;
5391
+ }
5392
+ if (config.voice) {
5393
+ session.voice = config.voice;
5394
+ }
5395
+ if (config.inputAudioFormat) {
5396
+ session.input_audio_format = toOpenAIAudioFormat(config.inputAudioFormat);
5397
+ }
5398
+ if (config.outputAudioFormat) {
5399
+ session.output_audio_format = toOpenAIAudioFormat(config.outputAudioFormat);
5400
+ }
5401
+ if (config.turnDetection !== void 0) {
5402
+ if (config.turnDetection === null) {
5403
+ session.turn_detection = null;
5404
+ } else {
5405
+ session.turn_detection = {
5406
+ type: config.turnDetection.type,
5407
+ ...config.turnDetection.threshold !== void 0 && {
5408
+ threshold: config.turnDetection.threshold
5409
+ },
5410
+ ...config.turnDetection.prefixPaddingMs !== void 0 && {
5411
+ prefix_padding_ms: config.turnDetection.prefixPaddingMs
5412
+ },
5413
+ ...config.turnDetection.silenceDurationMs !== void 0 && {
5414
+ silence_duration_ms: config.turnDetection.silenceDurationMs
5415
+ }
5416
+ };
5417
+ }
5418
+ }
5419
+ if (config.temperature !== void 0) {
5420
+ session.temperature = config.temperature;
5421
+ }
5422
+ if (config.maxResponseOutputTokens !== void 0) {
5423
+ session.max_response_output_tokens = config.maxResponseOutputTokens;
5424
+ }
5425
+ session.input_audio_transcription = { model: "whisper-1" };
5426
+ if (config.tools && config.tools.length > 0) {
5427
+ session.tools = config.tools.map((t) => ({
5428
+ type: "function",
5429
+ name: t.name,
5430
+ description: t.description,
5431
+ parameters: t.parameters
5432
+ }));
5433
+ }
5434
+ return session;
5435
+ }
5436
+ };
5437
+
5438
+ // src/voice/providers/google-live.ts
5439
+ import { EventEmitter as EventEmitter4 } from "events";
5440
+ import { createRequire as createRequire17 } from "module";
5441
+ var _require17 = createRequire17(import.meta.url);
5442
+ var GoogleLiveConnection = class extends EventEmitter4 {
5443
+ session;
5444
+ closed = false;
5445
+ constructor(session) {
5446
+ super();
5447
+ this.session = session;
5448
+ }
5449
+ sendAudio(data) {
5450
+ if (this.closed) return;
5451
+ this.session.sendRealtimeInput({
5452
+ audio: {
5453
+ data: data.toString("base64"),
5454
+ mimeType: "audio/pcm;rate=16000"
5455
+ }
5456
+ });
5457
+ }
5458
+ sendText(text) {
5459
+ if (this.closed) return;
5460
+ this.session.sendClientContent({
5461
+ turns: text,
5462
+ turnComplete: true
5463
+ });
5464
+ }
5465
+ sendToolResult(callId, result) {
5466
+ if (this.closed) return;
5467
+ let responseObj;
5468
+ try {
5469
+ responseObj = JSON.parse(result);
5470
+ } catch {
5471
+ responseObj = { result };
5472
+ }
5473
+ this.session.sendToolResponse({
5474
+ functionResponses: [
5475
+ {
5476
+ id: callId,
5477
+ name: callId,
5478
+ response: responseObj
5479
+ }
5480
+ ]
5481
+ });
5482
+ }
5483
+ interrupt() {
5484
+ }
5485
+ async close() {
5486
+ if (this.closed) return;
5487
+ this.closed = true;
5488
+ try {
5489
+ this.session.close();
5490
+ } catch {
5491
+ }
5492
+ this.emit("disconnected", {});
5493
+ }
5494
+ on(event, handler) {
5495
+ return super.on(event, handler);
5496
+ }
5497
+ off(event, handler) {
5498
+ return super.off(event, handler);
5499
+ }
5500
+ /** Internal: handle server messages from the Live API. */
5501
+ _handleMessage(message) {
5502
+ if (message.serverContent?.interrupted) {
5503
+ this.emit("interrupted", {});
5504
+ return;
5505
+ }
5506
+ if (message.toolCall?.functionCalls) {
5507
+ for (const fc of message.toolCall.functionCalls) {
5508
+ const toolCall = {
5509
+ id: fc.id ?? fc.name,
5510
+ name: fc.name,
5511
+ arguments: JSON.stringify(fc.args ?? {})
5512
+ };
5513
+ this.emit("tool_call", toolCall);
5514
+ }
5515
+ return;
5516
+ }
5517
+ if (message.serverContent?.modelTurn?.parts) {
5518
+ for (const part of message.serverContent.modelTurn.parts) {
5519
+ if (part.inlineData && part.inlineData.data) {
5520
+ const mimeType = part.inlineData.mimeType ?? "audio/pcm";
5521
+ const buf = typeof part.inlineData.data === "string" ? Buffer.from(part.inlineData.data, "base64") : Buffer.from(part.inlineData.data);
5522
+ this.emit("audio", { data: buf, mimeType });
5523
+ }
5524
+ if (part.text) {
5525
+ this.emit("text", { text: part.text });
5526
+ this.emit("transcript", { text: part.text, role: "assistant" });
5527
+ }
5528
+ if (part.functionCall) {
5529
+ const toolCall = {
5530
+ id: part.functionCall.id ?? part.functionCall.name,
5531
+ name: part.functionCall.name,
5532
+ arguments: JSON.stringify(part.functionCall.args ?? {})
5533
+ };
5534
+ this.emit("tool_call", toolCall);
5535
+ }
5536
+ }
5537
+ }
5538
+ }
5539
+ };
5540
+ var GoogleLiveProvider = class {
5541
+ providerId = "google-live";
5542
+ modelId;
5543
+ apiKey;
5544
+ constructor(modelId, config) {
5545
+ this.modelId = modelId ?? "gemini-2.5-flash-native-audio-preview-12-2025";
5546
+ this.apiKey = config?.apiKey;
5547
+ }
5548
+ async connect(config) {
5549
+ let GoogleGenAI;
5550
+ let Modality;
5551
+ try {
5552
+ const mod = _require17("@google/genai");
5553
+ GoogleGenAI = mod.GoogleGenAI;
5554
+ Modality = mod.Modality;
5555
+ } catch {
5556
+ throw new Error(
5557
+ "@google/genai package is required for GoogleLiveProvider. Install it: npm install @google/genai"
5558
+ );
5559
+ }
5560
+ const key = config.apiKey ?? this.apiKey ?? process.env.GOOGLE_API_KEY;
5561
+ if (!key) {
5562
+ throw new Error(
5563
+ "No Google API key provided for live connection. Set GOOGLE_API_KEY env var or pass apiKey."
5564
+ );
5565
+ }
5566
+ const ai = new GoogleGenAI({ apiKey: key });
5567
+ const liveConfig = {
5568
+ responseModalities: [Modality.AUDIO]
5569
+ };
5570
+ if (config.instructions) {
5571
+ liveConfig.systemInstruction = config.instructions;
5572
+ }
5573
+ if (config.voice) {
5574
+ liveConfig.speechConfig = { voiceConfig: { prebuiltVoiceConfig: { voiceName: config.voice } } };
5575
+ }
5576
+ if (config.temperature !== void 0) {
5577
+ liveConfig.temperature = config.temperature;
5578
+ }
5579
+ if (config.tools && config.tools.length > 0) {
5580
+ liveConfig.tools = [
5581
+ {
5582
+ functionDeclarations: config.tools.map((t) => ({
5583
+ name: t.name,
5584
+ description: t.description,
5585
+ parameters: t.parameters
5586
+ }))
5587
+ }
5588
+ ];
5589
+ }
5590
+ const connection = new GoogleLiveConnection(null);
5591
+ return new Promise((resolve, reject) => {
5592
+ const timeout = setTimeout(() => {
5593
+ reject(new Error("Google Live connection timed out after 15s"));
5594
+ }, 15e3);
5595
+ ai.live.connect({
5596
+ model: this.modelId,
5597
+ config: liveConfig,
5598
+ callbacks: {
5599
+ onopen: () => {
5600
+ clearTimeout(timeout);
5601
+ connection.emit("connected", {});
5602
+ },
5603
+ onmessage: (message) => {
5604
+ connection._handleMessage(message);
5605
+ },
5606
+ onerror: (e) => {
5607
+ const err = e?.error ?? e?.message ?? e;
5608
+ const error = err instanceof Error ? err : new Error(String(err));
5609
+ connection.emit("error", { error });
5610
+ },
5611
+ onclose: () => {
5612
+ connection.emit("disconnected", {});
5613
+ }
5614
+ }
5615
+ }).then((session) => {
5616
+ connection.session = session;
5617
+ resolve(connection);
5618
+ }).catch((err) => {
5619
+ clearTimeout(timeout);
5620
+ reject(err);
5621
+ });
5622
+ });
5623
+ }
5624
+ };
5625
+
4559
5626
  // src/toolkits/base.ts
4560
5627
  var Toolkit = class {
4561
5628
  };
@@ -4666,8 +5733,8 @@ Snippet: ${r.snippet ?? ""}
4666
5733
 
4667
5734
  // src/toolkits/gmail.ts
4668
5735
  import { z as z5 } from "zod";
4669
- import { createRequire as createRequire16 } from "module";
4670
- var _require16 = createRequire16(import.meta.url);
5736
+ import { createRequire as createRequire18 } from "module";
5737
+ var _require18 = createRequire18(import.meta.url);
4671
5738
  var GmailToolkit = class extends Toolkit {
4672
5739
  name = "gmail";
4673
5740
  config;
@@ -4679,7 +5746,7 @@ var GmailToolkit = class extends Toolkit {
4679
5746
  async getGmailClient() {
4680
5747
  if (this.gmail) return this.gmail;
4681
5748
  if (this.config.authClient) {
4682
- const { google: google3 } = _require16("googleapis");
5749
+ const { google: google3 } = _require18("googleapis");
4683
5750
  this.gmail = google3.gmail({ version: "v1", auth: this.config.authClient });
4684
5751
  return this.gmail;
4685
5752
  }
@@ -4690,7 +5757,7 @@ var GmailToolkit = class extends Toolkit {
4690
5757
  "GmailToolkit: Provide credentialsPath + tokenPath, or an authClient. Set GMAIL_CREDENTIALS_PATH and GMAIL_TOKEN_PATH env vars, or pass them in config."
4691
5758
  );
4692
5759
  }
4693
- const { google: google2 } = _require16("googleapis");
5760
+ const { google: google2 } = _require18("googleapis");
4694
5761
  const fs = await import("fs");
4695
5762
  const creds = JSON.parse(fs.readFileSync(credPath, "utf-8"));
4696
5763
  const token = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
@@ -5221,11 +6288,13 @@ export {
5221
6288
  A2ARemoteAgent,
5222
6289
  Agent,
5223
6290
  AnthropicProvider,
6291
+ BM25Index,
5224
6292
  BaseVectorStore,
5225
6293
  DuckDuckGoToolkit,
5226
6294
  EventBus,
5227
6295
  GmailToolkit,
5228
6296
  GoogleEmbedding,
6297
+ GoogleLiveProvider,
5229
6298
  GoogleProvider,
5230
6299
  HackerNewsToolkit,
5231
6300
  InMemoryStorage,
@@ -5241,6 +6310,7 @@ export {
5241
6310
  OllamaProvider,
5242
6311
  OpenAIEmbedding,
5243
6312
  OpenAIProvider,
6313
+ OpenAIRealtimeProvider,
5244
6314
  PgVectorStore,
5245
6315
  PostgresStorage,
5246
6316
  QdrantVectorStore,
@@ -5253,6 +6323,7 @@ export {
5253
6323
  Toolkit,
5254
6324
  UserMemory,
5255
6325
  VertexAIProvider,
6326
+ VoiceAgent,
5256
6327
  WebSearchToolkit,
5257
6328
  WhatsAppToolkit,
5258
6329
  Workflow,
@@ -5263,6 +6334,7 @@ export {
5263
6334
  isMultiModal,
5264
6335
  ollama,
5265
6336
  openai,
6337
+ reciprocalRankFusion,
5266
6338
  registry,
5267
6339
  vertex,
5268
6340
  withRetry