@poncho-ai/harness 0.22.0 → 0.22.1

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/harness@0.22.0 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
2
+ > @poncho-ai/harness@0.22.1 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
3
3
  > node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
4
4
 
5
5
  [embed-docs] Generated poncho-docs.ts with 4 topics
@@ -8,8 +8,8 @@
8
8
  CLI tsup v8.5.1
9
9
  CLI Target: es2022
10
10
  ESM Build start
11
- ESM dist/index.js 257.83 KB
12
- ESM ⚡️ Build success in 146ms
11
+ ESM dist/index.js 260.03 KB
12
+ ESM ⚡️ Build success in 131ms
13
13
  DTS Build start
14
- DTS ⚡️ Build success in 7680ms
15
- DTS dist/index.d.ts 27.07 KB
14
+ DTS ⚡️ Build success in 6723ms
15
+ DTS dist/index.d.ts 27.15 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @poncho-ai/harness
2
2
 
3
+ ## 0.22.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`096953d`](https://github.com/cesr/poncho-ai/commit/096953d5a64a785950ea0a7f09e2183e481afd29) Thanks [@cesr](https://github.com/cesr)! - Improve time-to-first-token by lazy-loading the recall corpus
8
+
9
+ The recall corpus (past conversation summaries) is now fetched on-demand only when the LLM invokes the `conversation_recall` tool, instead of blocking every message with ~1.3s of upfront I/O. Also adds batch `mget` support to Upstash/Redis/DynamoDB conversation stores, parallelizes memory fetch with skill refresh, debounces skill refresh in dev mode, and caches message conversions across multi-step runs.
10
+
3
11
  ## 0.22.0
4
12
 
5
13
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -562,6 +562,7 @@ declare class AgentHarness {
562
562
  private loadedConfig?;
563
563
  private loadedSkills;
564
564
  private skillFingerprint;
565
+ private lastSkillRefreshAt;
565
566
  private readonly activeSkillNames;
566
567
  private readonly registeredMcpToolNames;
567
568
  private latitudeTelemetry?;
@@ -601,6 +602,7 @@ declare class AgentHarness {
601
602
  private refreshMcpTools;
602
603
  private buildSkillFingerprint;
603
604
  private registerSkillTools;
605
+ private static readonly SKILL_REFRESH_DEBOUNCE_MS;
604
606
  private refreshSkillsIfChanged;
605
607
  initialize(): Promise<void>;
606
608
  private buildBrowserStoragePersistence;
package/dist/index.js CHANGED
@@ -2771,10 +2771,9 @@ var createMemoryTools = (store, options) => {
2771
2771
  Math.min(5, typeof input.limit === "number" ? input.limit : 3)
2772
2772
  );
2773
2773
  const excludeConversationId = typeof input.excludeConversationId === "string" ? input.excludeConversationId : "";
2774
- const corpus = asRecallCorpus(context.parameters.__conversationRecallCorpus).slice(
2775
- 0,
2776
- maxRecallConversations
2777
- );
2774
+ const rawCorpus = context.parameters.__conversationRecallCorpus;
2775
+ const resolvedCorpus = typeof rawCorpus === "function" ? await rawCorpus() : rawCorpus;
2776
+ const corpus = asRecallCorpus(resolvedCorpus).slice(0, maxRecallConversations);
2778
2777
  const results = corpus.filter(
2779
2778
  (item) => excludeConversationId ? item.conversationId !== excludeConversationId : true
2780
2779
  ).map((item) => ({
@@ -4599,7 +4598,7 @@ function extractMediaFromToolOutput(output) {
4599
4598
  const strippedOutput = walk(output);
4600
4599
  return { mediaItems, strippedOutput };
4601
4600
  }
4602
- var AgentHarness = class {
4601
+ var AgentHarness = class _AgentHarness {
4603
4602
  workingDir;
4604
4603
  environment;
4605
4604
  modelProvider;
@@ -4611,6 +4610,7 @@ var AgentHarness = class {
4611
4610
  loadedConfig;
4612
4611
  loadedSkills = [];
4613
4612
  skillFingerprint = "";
4613
+ lastSkillRefreshAt = 0;
4614
4614
  activeSkillNames = /* @__PURE__ */ new Set();
4615
4615
  registeredMcpToolNames = /* @__PURE__ */ new Set();
4616
4616
  latitudeTelemetry;
@@ -4899,10 +4899,16 @@ var AgentHarness = class {
4899
4899
  })
4900
4900
  );
4901
4901
  }
4902
+ static SKILL_REFRESH_DEBOUNCE_MS = 3e3;
4902
4903
  async refreshSkillsIfChanged() {
4903
4904
  if (this.environment !== "development") {
4904
4905
  return;
4905
4906
  }
4907
+ const elapsed = Date.now() - this.lastSkillRefreshAt;
4908
+ if (this.lastSkillRefreshAt > 0 && elapsed < _AgentHarness.SKILL_REFRESH_DEBOUNCE_MS) {
4909
+ return;
4910
+ }
4911
+ this.lastSkillRefreshAt = Date.now();
4906
4912
  try {
4907
4913
  const latestSkills = await loadSkillMetadata(
4908
4914
  this.workingDir,
@@ -5228,6 +5234,7 @@ var AgentHarness = class {
5228
5234
  if (!this.parsedAgent) {
5229
5235
  await this.initialize();
5230
5236
  }
5237
+ const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
5231
5238
  await this.refreshSkillsIfChanged();
5232
5239
  this._currentRunConversationId = input.conversationId;
5233
5240
  const ownerParam = input.parameters?.__ownerId;
@@ -5280,7 +5287,7 @@ Each conversation gets its own browser tab sharing a single browser instance. Ca
5280
5287
  const promptWithSkills = this.skillContextWindow ? `${systemPrompt}${developmentContext}
5281
5288
 
5282
5289
  ${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentContext}${browserContext}`;
5283
- const mainMemory = this.memoryStore ? await this.memoryStore.getMainMemory() : void 0;
5290
+ const mainMemory = await memoryPromise;
5284
5291
  const boundedMainMemory = mainMemory && mainMemory.content.length > 4e3 ? `${mainMemory.content.slice(0, 4e3)}
5285
5292
  ...[truncated]` : mainMemory?.content;
5286
5293
  const memoryContext = boundedMainMemory && boundedMainMemory.trim().length > 0 ? `
@@ -5375,6 +5382,8 @@ ${boundedMainMemory.trim()}` : "";
5375
5382
  let totalOutputTokens = 0;
5376
5383
  let totalCachedTokens = 0;
5377
5384
  let transientStepRetryCount = 0;
5385
+ let cachedCoreMessages = [];
5386
+ let convertedUpTo = 0;
5378
5387
  for (let step = 1; step <= maxSteps; step += 1) {
5379
5388
  try {
5380
5389
  yield* drainBrowserEvents();
@@ -5651,7 +5660,15 @@ ${textContent}` };
5651
5660
  }
5652
5661
  }
5653
5662
  }
5654
- const coreMessages = (await Promise.all(messages.map(convertMessage))).flat();
5663
+ if (convertedUpTo > messages.length) {
5664
+ cachedCoreMessages = [];
5665
+ convertedUpTo = 0;
5666
+ }
5667
+ const newMessages = messages.slice(convertedUpTo);
5668
+ const newCoreMessages = newMessages.length > 0 ? (await Promise.all(newMessages.map(convertMessage))).flat() : [];
5669
+ cachedCoreMessages = [...cachedCoreMessages, ...newCoreMessages];
5670
+ convertedUpTo = messages.length;
5671
+ const coreMessages = cachedCoreMessages;
5655
5672
  const temperature = agent.frontmatter.model?.temperature ?? 0.2;
5656
5673
  const maxTokens = agent.frontmatter.model?.maxTokens;
5657
5674
  const cachedMessages = addPromptCacheBreakpoints(coreMessages, modelInstance);
@@ -6750,12 +6767,12 @@ var KeyValueConversationStoreBase = class {
6750
6767
  return [];
6751
6768
  }
6752
6769
  const ids = await this.getOwnerConversationIds(ownerId);
6770
+ if (ids.length === 0) return [];
6771
+ const convKeys = await Promise.all(ids.map((id) => this.conversationKey(id)));
6772
+ const rawValues = await kv.mget(convKeys);
6753
6773
  const conversations = [];
6754
- for (const id of ids) {
6755
- const raw = await kv.get(await this.conversationKey(id));
6756
- if (!raw) {
6757
- continue;
6758
- }
6774
+ for (const raw of rawValues) {
6775
+ if (!raw) continue;
6759
6776
  try {
6760
6777
  conversations.push(JSON.parse(raw));
6761
6778
  } catch {
@@ -6772,20 +6789,27 @@ var KeyValueConversationStoreBase = class {
6772
6789
  return [];
6773
6790
  }
6774
6791
  const ids = await this.getOwnerConversationIds(ownerId);
6792
+ if (ids.length === 0) return [];
6793
+ const metaKeys = await Promise.all(ids.map((id) => this.conversationMetaKey(id)));
6794
+ const rawValues = await kv.mget(metaKeys);
6775
6795
  const summaries = [];
6776
- for (const id of ids) {
6777
- const meta = await this.getConversationMeta(id);
6778
- if (meta && meta.ownerId === ownerId) {
6779
- summaries.push({
6780
- conversationId: meta.conversationId,
6781
- title: meta.title,
6782
- updatedAt: meta.updatedAt,
6783
- createdAt: meta.createdAt,
6784
- ownerId: meta.ownerId,
6785
- parentConversationId: meta.parentConversationId,
6786
- messageCount: meta.messageCount,
6787
- hasPendingApprovals: meta.hasPendingApprovals
6788
- });
6796
+ for (const raw of rawValues) {
6797
+ if (!raw) continue;
6798
+ try {
6799
+ const meta = JSON.parse(raw);
6800
+ if (meta.ownerId === ownerId) {
6801
+ summaries.push({
6802
+ conversationId: meta.conversationId,
6803
+ title: meta.title,
6804
+ updatedAt: meta.updatedAt,
6805
+ createdAt: meta.createdAt,
6806
+ ownerId: meta.ownerId,
6807
+ parentConversationId: meta.parentConversationId,
6808
+ messageCount: meta.messageCount,
6809
+ hasPendingApprovals: meta.hasPendingApprovals
6810
+ });
6811
+ }
6812
+ } catch {
6789
6813
  }
6790
6814
  }
6791
6815
  return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
@@ -6923,6 +6947,19 @@ var UpstashConversationStore = class extends KeyValueConversationStoreBase {
6923
6947
  const payload = await response.json();
6924
6948
  return payload.result ?? void 0;
6925
6949
  },
6950
+ mget: async (keys) => {
6951
+ if (keys.length === 0) return [];
6952
+ const path = keys.map((k) => encodeURIComponent(k)).join("/");
6953
+ const response = await fetch(`${this.baseUrl}/mget/${path}`, {
6954
+ method: "POST",
6955
+ headers: this.headers()
6956
+ });
6957
+ if (!response.ok) {
6958
+ return keys.map(() => void 0);
6959
+ }
6960
+ const payload = await response.json();
6961
+ return (payload.result ?? []).map((v) => v ?? void 0);
6962
+ },
6926
6963
  set: async (key, value, ttl) => {
6927
6964
  const endpoint = typeof ttl === "number" ? `${this.baseUrl}/setex/${encodeURIComponent(key)}/${Math.max(
6928
6965
  1,
@@ -7015,6 +7052,11 @@ var RedisLikeConversationStore = class extends KeyValueConversationStoreBase {
7015
7052
  const value = await client.get(key);
7016
7053
  return value ?? void 0;
7017
7054
  },
7055
+ mget: async (keys) => {
7056
+ if (keys.length === 0) return [];
7057
+ const values = await client.mGet(keys);
7058
+ return values.map((v) => v ?? void 0);
7059
+ },
7018
7060
  set: async (key, value, ttl) => {
7019
7061
  if (typeof ttl === "number") {
7020
7062
  await client.set(key, value, { EX: Math.max(1, ttl) });
@@ -7149,6 +7191,18 @@ var DynamoDbConversationStore = class extends KeyValueConversationStoreBase {
7149
7191
  })
7150
7192
  );
7151
7193
  },
7194
+ mget: async (keys) => {
7195
+ if (keys.length === 0) return [];
7196
+ return Promise.all(keys.map(async (key) => {
7197
+ const result = await client.send(
7198
+ new client.GetItemCommand({
7199
+ TableName: this.table,
7200
+ Key: { runId: { S: key } }
7201
+ })
7202
+ );
7203
+ return result.Item?.value?.S;
7204
+ }));
7205
+ },
7152
7206
  del: async (key) => {
7153
7207
  await client.send(
7154
7208
  new client.DeleteItemCommand({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.22.0",
3
+ "version": "0.22.1",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
package/src/harness.ts CHANGED
@@ -545,6 +545,7 @@ export class AgentHarness {
545
545
  private loadedConfig?: PonchoConfig;
546
546
  private loadedSkills: SkillMetadata[] = [];
547
547
  private skillFingerprint = "";
548
+ private lastSkillRefreshAt = 0;
548
549
  private readonly activeSkillNames = new Set<string>();
549
550
  private readonly registeredMcpToolNames = new Set<string>();
550
551
  private latitudeTelemetry?: LatitudeTelemetry;
@@ -879,10 +880,17 @@ export class AgentHarness {
879
880
  );
880
881
  }
881
882
 
883
+ private static readonly SKILL_REFRESH_DEBOUNCE_MS = 3000;
884
+
882
885
  private async refreshSkillsIfChanged(): Promise<void> {
883
886
  if (this.environment !== "development") {
884
887
  return;
885
888
  }
889
+ const elapsed = Date.now() - this.lastSkillRefreshAt;
890
+ if (this.lastSkillRefreshAt > 0 && elapsed < AgentHarness.SKILL_REFRESH_DEBOUNCE_MS) {
891
+ return;
892
+ }
893
+ this.lastSkillRefreshAt = Date.now();
886
894
  try {
887
895
  const latestSkills = await loadSkillMetadata(
888
896
  this.workingDir,
@@ -1269,6 +1277,10 @@ export class AgentHarness {
1269
1277
  if (!this.parsedAgent) {
1270
1278
  await this.initialize();
1271
1279
  }
1280
+ // Start memory fetch early so it overlaps with skill refresh I/O
1281
+ const memoryPromise = this.memoryStore
1282
+ ? this.memoryStore.getMainMemory()
1283
+ : undefined;
1272
1284
  await this.refreshSkillsIfChanged();
1273
1285
 
1274
1286
  // Track which conversation/owner this run belongs to so browser & subagent tools resolve correctly
@@ -1328,9 +1340,7 @@ Each conversation gets its own browser tab sharing a single browser instance. Ca
1328
1340
  const promptWithSkills = this.skillContextWindow
1329
1341
  ? `${systemPrompt}${developmentContext}\n\n${this.skillContextWindow}${browserContext}`
1330
1342
  : `${systemPrompt}${developmentContext}${browserContext}`;
1331
- const mainMemory = this.memoryStore
1332
- ? await this.memoryStore.getMainMemory()
1333
- : undefined;
1343
+ const mainMemory = await memoryPromise;
1334
1344
  const boundedMainMemory =
1335
1345
  mainMemory && mainMemory.content.length > 4000
1336
1346
  ? `${mainMemory.content.slice(0, 4000)}\n...[truncated]`
@@ -1445,6 +1455,8 @@ ${boundedMainMemory.trim()}`
1445
1455
  let totalOutputTokens = 0;
1446
1456
  let totalCachedTokens = 0;
1447
1457
  let transientStepRetryCount = 0;
1458
+ let cachedCoreMessages: ModelMessage[] = [];
1459
+ let convertedUpTo = 0;
1448
1460
 
1449
1461
  for (let step = 1; step <= maxSteps; step += 1) {
1450
1462
  try {
@@ -1784,9 +1796,19 @@ ${boundedMainMemory.trim()}`
1784
1796
  }
1785
1797
  }
1786
1798
 
1787
- const coreMessages: ModelMessage[] = (
1788
- await Promise.all(messages.map(convertMessage))
1789
- ).flat();
1799
+ // Only convert messages added since the last step
1800
+ if (convertedUpTo > messages.length) {
1801
+ // Compaction replaced the array — invalidate cache
1802
+ cachedCoreMessages = [];
1803
+ convertedUpTo = 0;
1804
+ }
1805
+ const newMessages = messages.slice(convertedUpTo);
1806
+ const newCoreMessages: ModelMessage[] = newMessages.length > 0
1807
+ ? (await Promise.all(newMessages.map(convertMessage))).flat()
1808
+ : [];
1809
+ cachedCoreMessages = [...cachedCoreMessages, ...newCoreMessages];
1810
+ convertedUpTo = messages.length;
1811
+ const coreMessages = cachedCoreMessages;
1790
1812
 
1791
1813
  const temperature = agent.frontmatter.model?.temperature ?? 0.2;
1792
1814
  const maxTokens = agent.frontmatter.model?.maxTokens;
@@ -1794,6 +1816,7 @@ ${boundedMainMemory.trim()}`
1794
1816
 
1795
1817
  const telemetryEnabled = this.loadedConfig?.telemetry?.enabled !== false;
1796
1818
 
1819
+
1797
1820
  const result = await streamText({
1798
1821
  model: modelInstance,
1799
1822
  system: integrityPrompt,
package/src/memory.ts CHANGED
@@ -658,10 +658,10 @@ export const createMemoryTools = (
658
658
  typeof input.excludeConversationId === "string"
659
659
  ? input.excludeConversationId
660
660
  : "";
661
- const corpus = asRecallCorpus(context.parameters.__conversationRecallCorpus).slice(
662
- 0,
663
- maxRecallConversations,
664
- );
661
+ const rawCorpus = context.parameters.__conversationRecallCorpus;
662
+ const resolvedCorpus =
663
+ typeof rawCorpus === "function" ? await (rawCorpus as () => Promise<unknown>)() : rawCorpus;
664
+ const corpus = asRecallCorpus(resolvedCorpus).slice(0, maxRecallConversations);
665
665
  const results = corpus
666
666
  .filter((item) =>
667
667
  excludeConversationId ? item.conversationId !== excludeConversationId : true,
package/src/state.ts CHANGED
@@ -646,6 +646,7 @@ class FileStateStore implements StateStore {
646
646
 
647
647
  interface RawKeyValueClient {
648
648
  get(key: string): Promise<string | undefined>;
649
+ mget(keys: string[]): Promise<(string | undefined)[]>;
649
650
  set(key: string, value: string, ttl?: number): Promise<void>;
650
651
  del(key: string): Promise<void>;
651
652
  }
@@ -760,21 +761,18 @@ abstract class KeyValueConversationStoreBase implements ConversationStore {
760
761
  return await this.memoryFallback.list(ownerId);
761
762
  }
762
763
  if (!ownerId) {
763
- // KV stores index per-owner; cross-owner listing not supported
764
764
  return [];
765
765
  }
766
766
  const ids = await this.getOwnerConversationIds(ownerId);
767
+ if (ids.length === 0) return [];
768
+ const convKeys = await Promise.all(ids.map((id) => this.conversationKey(id)));
769
+ const rawValues = await kv.mget(convKeys);
767
770
  const conversations: Conversation[] = [];
768
- for (const id of ids) {
769
- const raw = await kv.get(await this.conversationKey(id));
770
- if (!raw) {
771
- continue;
772
- }
771
+ for (const raw of rawValues) {
772
+ if (!raw) continue;
773
773
  try {
774
774
  conversations.push(JSON.parse(raw) as Conversation);
775
- } catch {
776
- // Skip invalid records.
777
- }
775
+ } catch { /* skip invalid records */ }
778
776
  }
779
777
  return conversations.sort((a, b) => b.updatedAt - a.updatedAt);
780
778
  }
@@ -788,21 +786,27 @@ abstract class KeyValueConversationStoreBase implements ConversationStore {
788
786
  return [];
789
787
  }
790
788
  const ids = await this.getOwnerConversationIds(ownerId);
789
+ if (ids.length === 0) return [];
790
+ const metaKeys = await Promise.all(ids.map((id) => this.conversationMetaKey(id)));
791
+ const rawValues = await kv.mget(metaKeys);
791
792
  const summaries: ConversationSummary[] = [];
792
- for (const id of ids) {
793
- const meta = await this.getConversationMeta(id);
794
- if (meta && meta.ownerId === ownerId) {
795
- summaries.push({
796
- conversationId: meta.conversationId,
797
- title: meta.title,
798
- updatedAt: meta.updatedAt,
799
- createdAt: meta.createdAt,
800
- ownerId: meta.ownerId,
801
- parentConversationId: meta.parentConversationId,
802
- messageCount: meta.messageCount,
803
- hasPendingApprovals: meta.hasPendingApprovals,
804
- });
805
- }
793
+ for (const raw of rawValues) {
794
+ if (!raw) continue;
795
+ try {
796
+ const meta = JSON.parse(raw) as ConversationMeta;
797
+ if (meta.ownerId === ownerId) {
798
+ summaries.push({
799
+ conversationId: meta.conversationId,
800
+ title: meta.title,
801
+ updatedAt: meta.updatedAt,
802
+ createdAt: meta.createdAt,
803
+ ownerId: meta.ownerId,
804
+ parentConversationId: meta.parentConversationId,
805
+ messageCount: meta.messageCount,
806
+ hasPendingApprovals: meta.hasPendingApprovals,
807
+ });
808
+ }
809
+ } catch { /* skip invalid records */ }
806
810
  }
807
811
  return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
808
812
  }
@@ -948,6 +952,19 @@ class UpstashConversationStore extends KeyValueConversationStoreBase {
948
952
  const payload = (await response.json()) as { result?: string | null };
949
953
  return payload.result ?? undefined;
950
954
  },
955
+ mget: async (keys: string[]) => {
956
+ if (keys.length === 0) return [];
957
+ const path = keys.map((k) => encodeURIComponent(k)).join("/");
958
+ const response = await fetch(`${this.baseUrl}/mget/${path}`, {
959
+ method: "POST",
960
+ headers: this.headers(),
961
+ });
962
+ if (!response.ok) {
963
+ return keys.map(() => undefined);
964
+ }
965
+ const payload = (await response.json()) as { result?: (string | null)[] };
966
+ return (payload.result ?? []).map((v) => v ?? undefined);
967
+ },
951
968
  set: async (key: string, value: string, ttl?: number) => {
952
969
  const endpoint =
953
970
  typeof ttl === "number"
@@ -1042,6 +1059,7 @@ class RedisLikeConversationStore extends KeyValueConversationStoreBase {
1042
1059
  private readonly clientPromise: Promise<
1043
1060
  | {
1044
1061
  get: (key: string) => Promise<string | null>;
1062
+ mGet: (keys: readonly string[]) => Promise<(string | null)[]>;
1045
1063
  set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
1046
1064
  del: (key: string) => Promise<unknown>;
1047
1065
  }
@@ -1056,6 +1074,7 @@ class RedisLikeConversationStore extends KeyValueConversationStoreBase {
1056
1074
  createClient: (options: { url: string }) => {
1057
1075
  connect: () => Promise<unknown>;
1058
1076
  get: (key: string) => Promise<string | null>;
1077
+ mGet: (keys: readonly string[]) => Promise<(string | null)[]>;
1059
1078
  set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
1060
1079
  del: (key: string) => Promise<unknown>;
1061
1080
  };
@@ -1079,6 +1098,11 @@ class RedisLikeConversationStore extends KeyValueConversationStoreBase {
1079
1098
  const value = await client.get(key);
1080
1099
  return value ?? undefined;
1081
1100
  },
1101
+ mget: async (keys: string[]) => {
1102
+ if (keys.length === 0) return [];
1103
+ const values = await client.mGet(keys);
1104
+ return values.map((v: string | null) => v ?? undefined);
1105
+ },
1082
1106
  set: async (key: string, value: string, ttl?: number) => {
1083
1107
  if (typeof ttl === "number") {
1084
1108
  await client.set(key, value, { EX: Math.max(1, ttl) });
@@ -1261,6 +1285,18 @@ class DynamoDbConversationStore extends KeyValueConversationStoreBase {
1261
1285
  }),
1262
1286
  );
1263
1287
  },
1288
+ mget: async (keys: string[]) => {
1289
+ if (keys.length === 0) return [];
1290
+ return Promise.all(keys.map(async (key) => {
1291
+ const result = (await client.send(
1292
+ new client.GetItemCommand({
1293
+ TableName: this.table,
1294
+ Key: { runId: { S: key } },
1295
+ }),
1296
+ )) as { Item?: { value?: { S?: string } } };
1297
+ return result.Item?.value?.S;
1298
+ }));
1299
+ },
1264
1300
  del: async (key: string) => {
1265
1301
  await client.send(
1266
1302
  new client.DeleteItemCommand({