@ponraaj/dimple-sdk 0.0.0-alpha.11 → 0.0.0-alpha.13

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.
package/dist/index.js CHANGED
@@ -33,7 +33,7 @@ import { Effect as Effect39, Layer as Layer15, Schedule as Schedule2, Schema as
33
33
 
34
34
  // ../store/src/store.ts
35
35
  import { Config as Config4, Effect as Effect25, Layer as Layer10, Schema as Schema23 } from "effect";
36
- import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, like, sql as sql7 } from "drizzle-orm";
36
+ import { eq as eq8, and as and8, desc as desc2, asc as asc5, inArray as inArray4, ne as ne4, or, isNull as isNull2, like, sql as sql8 } from "drizzle-orm";
37
37
 
38
38
  // ../store/src/db-backend.ts
39
39
  import { Context as Context2 } from "effect";
@@ -840,6 +840,43 @@ var IndexConfig = Schema8.Struct({
840
840
  maxNeighbors: Schema8.optional(Schema8.Number),
841
841
  searchL: Schema8.optional(Schema8.Number)
842
842
  });
843
+ var ThreadCount = Schema8.Number.pipe(Schema8.refine((n) => Number.isInteger(n) && n >= 0 && n <= 1024, {
844
+ identifier: "ThreadCount"
845
+ }));
846
+ var SessionOptions = Schema8.Struct({
847
+ intraOpNumThreads: Schema8.optional(ThreadCount),
848
+ interOpNumThreads: Schema8.optional(ThreadCount),
849
+ executionMode: Schema8.optional(Schema8.Literals(["sequential", "parallel"])),
850
+ graphOptimizationLevel: Schema8.optional(Schema8.Literals(["disabled", "basic", "extended", "all", "layout"])),
851
+ enableCpuMemArena: Schema8.optional(Schema8.Boolean),
852
+ enableMemPattern: Schema8.optional(Schema8.Boolean),
853
+ gpuMemLimit: Schema8.optional(Schema8.Number),
854
+ arenaExtendStrategy: Schema8.optional(Schema8.Literals([0, 1]))
855
+ });
856
+ var mergeSessionOptions = (global, model) => {
857
+ const pick = (key) => model?.[key] ?? global?.[key];
858
+ const intraOpNumThreads = pick("intraOpNumThreads");
859
+ const interOpNumThreads = pick("interOpNumThreads");
860
+ const executionMode = pick("executionMode");
861
+ const graphOptimizationLevel = pick("graphOptimizationLevel");
862
+ const enableCpuMemArena = pick("enableCpuMemArena");
863
+ const enableMemPattern = pick("enableMemPattern");
864
+ const gpuMemLimit = pick("gpuMemLimit");
865
+ const arenaExtendStrategy = pick("arenaExtendStrategy");
866
+ const empty = intraOpNumThreads === undefined && interOpNumThreads === undefined && executionMode === undefined && graphOptimizationLevel === undefined && enableCpuMemArena === undefined && enableMemPattern === undefined && gpuMemLimit === undefined && arenaExtendStrategy === undefined;
867
+ if (empty)
868
+ return;
869
+ return {
870
+ ...intraOpNumThreads !== undefined ? { intraOpNumThreads } : undefined,
871
+ ...interOpNumThreads !== undefined ? { interOpNumThreads } : undefined,
872
+ ...executionMode !== undefined ? { executionMode } : undefined,
873
+ ...graphOptimizationLevel !== undefined ? { graphOptimizationLevel } : undefined,
874
+ ...enableCpuMemArena !== undefined ? { enableCpuMemArena } : undefined,
875
+ ...enableMemPattern !== undefined ? { enableMemPattern } : undefined,
876
+ ...gpuMemLimit !== undefined ? { gpuMemLimit } : undefined,
877
+ ...arenaExtendStrategy !== undefined ? { arenaExtendStrategy } : undefined
878
+ };
879
+ };
843
880
  var EmbeddingModelConfig = Schema8.Struct({
844
881
  id: Schema8.String,
845
882
  provider: Schema8.String,
@@ -877,14 +914,13 @@ var EmbeddingModelConfig = Schema8.Struct({
877
914
  pooling: Schema8.optional(Schema8.Literals(["mean", "cls", "none"])),
878
915
  normalize: Schema8.optional(Schema8.Boolean),
879
916
  cacheDir: Schema8.optional(Schema8.String),
880
- sessionOptions: Schema8.optional(Schema8.Struct({
881
- gpuMemLimit: Schema8.optional(Schema8.Number),
882
- arenaExtendStrategy: Schema8.optional(Schema8.Literals([0, 1]))
883
- })),
917
+ sessionOptions: Schema8.optional(SessionOptions),
918
+ wasm: Schema8.optional(Schema8.Struct({ numThreads: Schema8.optional(ThreadCount) })),
884
919
  autoInstall: Schema8.optional(Schema8.Boolean)
885
920
  });
886
921
  var EmbeddingConfig = Schema8.Struct({
887
922
  defaultModel: Schema8.String,
923
+ sessionOptions: Schema8.optional(SessionOptions),
888
924
  models: Schema8.Array(EmbeddingModelConfig)
889
925
  });
890
926
  var MaxAttempts = Schema8.Number.pipe(Schema8.refine((n) => Number.isInteger(n) && n > 0 && n <= 100, {
@@ -2736,8 +2772,8 @@ var resolveTopics = Effect11.fn("topics.resolveTopics")(function* (queryVector,
2736
2772
  });
2737
2773
 
2738
2774
  // ../store/src/topics/lifecycle.ts
2739
- import { Clock as Clock3, Effect as Effect20 } from "effect";
2740
- import { and as and3, eq as eq3, inArray, ne } from "drizzle-orm";
2775
+ import { Clock as Clock3, Cause as Cause3, Effect as Effect20, Exit as Exit6 } from "effect";
2776
+ import { and as and3, eq as eq3, inArray, ne, sql as sql3 } from "drizzle-orm";
2741
2777
 
2742
2778
  // ../workflow/src/queue.ts
2743
2779
  import { Effect as Effect12, Schema as Schema13 } from "effect";
@@ -2820,6 +2856,16 @@ var MemoryEmbed = JobDefinition.make("memory.embed", {
2820
2856
  error: Schema13.String,
2821
2857
  idempotencyKey: (p) => `embed:${p.memoryId}:${p.modelKey}`
2822
2858
  });
2859
+ var MemoryEmbedBatch = JobDefinition.make("memory.embed.batch", {
2860
+ payload: {
2861
+ memoryIds: Schema13.Array(Schema13.String),
2862
+ modelKey: Schema13.String,
2863
+ nonce: Schema13.String
2864
+ },
2865
+ success: Schema13.Void,
2866
+ error: Schema13.String,
2867
+ idempotencyKey: (p) => `embed-batch:${p.modelKey}:${p.nonce}`
2868
+ });
2823
2869
  var TopicMerge = JobDefinition.make("topic.merge", {
2824
2870
  payload: { topicId: Schema13.String, modelKey: Schema13.String },
2825
2871
  success: Schema13.Void,
@@ -2877,6 +2923,7 @@ var AuditWrite = JobDefinition.make("audit.write", {
2877
2923
  var queues = [
2878
2924
  TopicSplit,
2879
2925
  MemoryEmbed,
2926
+ MemoryEmbedBatch,
2880
2927
  TopicMerge,
2881
2928
  TopicRebalance,
2882
2929
  ConceptCluster,
@@ -3703,46 +3750,136 @@ var setActiveModel = Effect20.fn("topics.setActiveModel")(function* (model, conf
3703
3750
  }).pipe(routingFailure3("topics.setActiveModel.deactivate"));
3704
3751
  return { routed, alreadyInTree, deactivated };
3705
3752
  });
3753
+ var embedOneMemory = (deps, payload, ctx) => Effect20.gen(function* () {
3754
+ const envelope = payload;
3755
+ const model = deps.getModelConfig(envelope.modelKey);
3756
+ if (!model) {
3757
+ return yield* Effect20.fail(new JobFailure({
3758
+ jobId: ctx.jobId,
3759
+ message: `memory.embed: unknown model "${envelope.modelKey}"`
3760
+ }));
3761
+ }
3762
+ const content = yield* deps.getMemoryContent(envelope.memoryId).pipe(Effect20.catchTag("Store.NotFound", () => Effect20.succeed(undefined)), Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: load content: ${e}` })));
3763
+ if (content === undefined) {
3764
+ yield* Effect20.logDebug("embed.stale-reoffer").pipe(Effect20.annotateLogs({
3765
+ scope: "@dimple/store",
3766
+ event: "embed.stale-reoffer",
3767
+ memoryId: envelope.memoryId,
3768
+ modelKey: envelope.modelKey
3769
+ }));
3770
+ return;
3771
+ }
3772
+ const vector = yield* deps.embedOne(content, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: embed: ${e}` })));
3773
+ const backend = yield* Service2;
3774
+ const db = backend.db;
3775
+ const existing = yield* Effect20.tryPromise({
3776
+ try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and3(eq3(topicMemberships.memoryId, envelope.memoryId), eq3(topicMemberships.modelKey, model.id))),
3777
+ catch: (cause) => new StoreError({ message: `memory.embed: membership check: ${cause}` })
3778
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: membership check: ${e}` })));
3779
+ yield* ensureTopicTree(model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: seed tree: ${e}` })));
3780
+ yield* backend.ensureVectorTable(model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: vector table: ${e}` })));
3781
+ yield* backend.insertVector(envelope.memoryId, [...vector], model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: insert vector: ${e}` })));
3782
+ yield* Effect20.tryPromise({
3783
+ try: () => db.update(memories).set({ embedding: [...vector], embeddingModel: model.id }).where(eq3(memories.id, envelope.memoryId)),
3784
+ catch: (cause) => new StoreError({ message: `memory.embed: stamp embedding: ${cause}` })
3785
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: stamp embedding: ${e}` })));
3786
+ if (existing.length === 0) {
3787
+ yield* routeInsert(envelope.memoryId, [...vector], model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: route: ${e}` })));
3788
+ }
3789
+ yield* Effect20.logInfo("embed.completed").pipe(Effect20.annotateLogs({
3790
+ scope: "@dimple/store",
3791
+ event: "embed.completed",
3792
+ memoryId: envelope.memoryId,
3793
+ modelKey: envelope.modelKey,
3794
+ reroute: existing.length === 0
3795
+ }));
3796
+ return;
3797
+ });
3706
3798
  function memoryEmbedHandler(deps) {
3799
+ const run = (payload, ctx) => embedOneMemory(deps, payload, ctx);
3800
+ return deps.backend ? (payload, ctx) => run(payload, ctx).pipe(Effect20.provideService(Service2, deps.backend)) : run;
3801
+ }
3802
+ var logBatchCompleted = (modelKey, embedded, failed, ctx) => Effect20.logInfo("embed.batch.completed").pipe(Effect20.annotateLogs({
3803
+ scope: "@dimple/store",
3804
+ event: "embed.batch.completed",
3805
+ jobId: ctx.jobId,
3806
+ modelKey,
3807
+ embedded,
3808
+ failed
3809
+ }));
3810
+ var batchFailure = (ctx, failed, total, first) => Effect20.fail(new JobFailure({
3811
+ jobId: ctx.jobId,
3812
+ message: `memory.embed.batch: ${failed}/${total} failed — ${first}`
3813
+ }));
3814
+ function memoryEmbedBatchHandler(deps) {
3707
3815
  const run = (payload, ctx) => Effect20.gen(function* () {
3708
- const envelope = payload;
3709
- const model = deps.getModelConfig(envelope.modelKey);
3816
+ const model = deps.getModelConfig(payload.modelKey);
3710
3817
  if (!model) {
3711
3818
  return yield* Effect20.fail(new JobFailure({
3712
3819
  jobId: ctx.jobId,
3713
- message: `memory.embed: unknown model "${envelope.modelKey}"`
3820
+ message: `memory.embed: unknown model "${payload.modelKey}"`
3714
3821
  }));
3715
3822
  }
3716
- const content = yield* deps.getMemoryContent(envelope.memoryId).pipe(Effect20.catchTag("Store.NotFound", () => Effect20.succeed(undefined)), Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: load content: ${e}` })));
3717
- if (content === undefined) {
3823
+ const backend = yield* Service2;
3824
+ const db = backend.db;
3825
+ let failed = 0;
3826
+ let first;
3827
+ const embedded = [];
3828
+ const stale = [];
3829
+ yield* Effect20.forEach(payload.memoryIds, (memoryId) => Effect20.gen(function* () {
3830
+ const content = yield* deps.getMemoryContent(memoryId).pipe(Effect20.catchTag("Store.NotFound", () => Effect20.succeed(undefined)), Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: load content: ${e}` })));
3831
+ if (content === undefined) {
3832
+ stale.push(memoryId);
3833
+ return;
3834
+ }
3835
+ const vector = yield* deps.embedOne(content, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: embed: ${e}` })));
3836
+ embedded.push({ memoryId, vector });
3837
+ }).pipe(Effect20.exit, Effect20.tap((exit) => {
3838
+ if (Exit6.isSuccess(exit))
3839
+ return Effect20.void;
3840
+ failed += 1;
3841
+ first ??= Cause3.pretty(exit.cause);
3842
+ return Effect20.void;
3843
+ })), { concurrency: 1, discard: true });
3844
+ for (const memoryId of stale) {
3718
3845
  yield* Effect20.logDebug("embed.stale-reoffer").pipe(Effect20.annotateLogs({
3719
3846
  scope: "@dimple/store",
3720
3847
  event: "embed.stale-reoffer",
3721
- memoryId: envelope.memoryId,
3722
- modelKey: envelope.modelKey
3848
+ memoryId,
3849
+ modelKey: payload.modelKey
3723
3850
  }));
3851
+ }
3852
+ if (embedded.length === 0) {
3853
+ yield* logBatchCompleted(payload.modelKey, 0, failed, ctx);
3854
+ if (failed > 0) {
3855
+ return yield* batchFailure(ctx, failed, payload.memoryIds.length, first);
3856
+ }
3724
3857
  return;
3725
3858
  }
3726
- const vector = yield* deps.embedOne(content, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: embed: ${e}` })));
3727
- const backend = yield* Service2;
3728
- const db = backend.db;
3729
- const existing = yield* Effect20.tryPromise({
3730
- try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and3(eq3(topicMemberships.memoryId, envelope.memoryId), eq3(topicMemberships.modelKey, model.id))),
3731
- catch: (cause) => new StoreError({ message: `memory.embed: membership check: ${cause}` })
3732
- }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: membership check: ${e}` })));
3733
3859
  yield* ensureTopicTree(model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: seed tree: ${e}` })));
3734
3860
  yield* backend.ensureVectorTable(model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: vector table: ${e}` })));
3735
- yield* backend.insertVector(envelope.memoryId, [...vector], model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: insert vector: ${e}` })));
3736
- if (existing.length === 0) {
3737
- yield* routeInsert(envelope.memoryId, [...vector], model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: route: ${e}` })));
3861
+ const ids = embedded.map((e) => e.memoryId);
3862
+ const members = yield* Effect20.tryPromise({
3863
+ try: () => db.select({ memoryId: topicMemberships.memoryId }).from(topicMemberships).where(and3(eq3(topicMemberships.modelKey, model.id), inArray(topicMemberships.memoryId, ids))),
3864
+ catch: (cause) => new StoreError({ message: `memory.embed: membership check: ${cause}` })
3865
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: membership check: ${e}` })));
3866
+ const memberSet = new Set(members.map((r) => r.memoryId));
3867
+ const entries = embedded.map((e) => ({ memoryId: e.memoryId, vector: [...e.vector] }));
3868
+ yield* backend.insertVectors(entries, model).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: insert vectors: ${e}` })));
3869
+ yield* Effect20.tryPromise({
3870
+ try: () => db.transaction(async (tx) => {
3871
+ await tx.run(sql3`UPDATE ${memories} SET embedding = CASE id ${sql3.join(entries.map((entry) => sql3`WHEN ${entry.memoryId} THEN ${new Float32Array(entry.vector).buffer}`), sql3` `)} END, embedding_model = ${model.id} WHERE id IN (${sql3.join(entries.map((entry) => sql3`${entry.memoryId}`), sql3`, `)})`);
3872
+ }),
3873
+ catch: (cause) => new StoreError({ message: `memory.embed: stamp embeddings: ${cause}` })
3874
+ }).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: stamp embeddings: ${e}` })));
3875
+ const fresh = entries.filter((entry) => !memberSet.has(entry.memoryId));
3876
+ if (fresh.length > 0) {
3877
+ yield* routeInsertMany(fresh, model, deps.config).pipe(Effect20.mapError((e) => new JobFailure({ jobId: ctx.jobId, message: `memory.embed: route: ${e}` })));
3878
+ }
3879
+ yield* logBatchCompleted(payload.modelKey, embedded.length, failed, ctx);
3880
+ if (failed > 0) {
3881
+ return yield* batchFailure(ctx, failed, payload.memoryIds.length, first);
3738
3882
  }
3739
- yield* Effect20.logInfo("embed.completed").pipe(Effect20.annotateLogs({
3740
- scope: "@dimple/store",
3741
- event: "embed.completed",
3742
- memoryId: envelope.memoryId,
3743
- modelKey: envelope.modelKey,
3744
- reroute: existing.length === 0
3745
- }));
3746
3883
  return;
3747
3884
  });
3748
3885
  return deps.backend ? (payload, ctx) => run(payload, ctx).pipe(Effect20.provideService(Service2, deps.backend)) : run;
@@ -3750,19 +3887,19 @@ function memoryEmbedHandler(deps) {
3750
3887
 
3751
3888
  // ../store/src/topics/jobs.ts
3752
3889
  import { Clock as Clock4, Effect as Effect21 } from "effect";
3753
- import { and as and4, asc as asc3, eq as eq4, inArray as inArray2, sql as sql4 } from "drizzle-orm";
3890
+ import { and as and4, asc as asc3, eq as eq4, inArray as inArray2, sql as sql5 } from "drizzle-orm";
3754
3891
 
3755
3892
  // ../store/src/topics/topic-vectors.ts
3756
- import { sql as sql3 } from "drizzle-orm";
3893
+ import { sql as sql4 } from "drizzle-orm";
3757
3894
  var topicVectorTableName2 = (model) => `topic_vec_${model.dimensions}_${model.id.replace(/[^a-zA-Z0-9_]/g, "_")}`;
3758
3895
  var deleteTopicVectorRow = async (runner, model, topicKey) => {
3759
3896
  const table = topicVectorTableName2(model);
3760
- await runner.run(sql3`DELETE FROM ${sql3.raw(table)} WHERE topic_key = ${topicKey}`);
3897
+ await runner.run(sql4`DELETE FROM ${sql4.raw(table)} WHERE topic_key = ${topicKey}`);
3761
3898
  };
3762
3899
  var writeTopicVector = async (runner, model, topicKey, ls) => {
3763
3900
  const table = topicVectorTableName2(model);
3764
- await runner.run(sql3`
3765
- INSERT OR REPLACE INTO ${sql3.raw(table)} (topic_key, sum_vector, model_key)
3901
+ await runner.run(sql4`
3902
+ INSERT OR REPLACE INTO ${sql4.raw(table)} (topic_key, sum_vector, model_key)
3766
3903
  VALUES (${topicKey}, vector32(${JSON.stringify([...ls])}), ${model.id})
3767
3904
  `);
3768
3905
  };
@@ -3853,7 +3990,7 @@ var splitTopic = Effect21.fn("TopicSplit.splitTopic")(function* (topicKey, model
3853
3990
  }).onConflictDoUpdate({
3854
3991
  target: [topicGrowthState.topicKey, topicGrowthState.modelKey],
3855
3992
  set: {
3856
- splitRejectedCount: sql4`${topicGrowthState.splitRejectedCount} + 1`,
3993
+ splitRejectedCount: sql5`${topicGrowthState.splitRejectedCount} + 1`,
3857
3994
  splitDeferred: 1
3858
3995
  }
3859
3996
  });
@@ -4543,7 +4680,7 @@ var staleNoOp = (notFound, model) => Effect22.gen(function* () {
4543
4680
 
4544
4681
  // ../store/src/topics/observability.ts
4545
4682
  import { Effect as Effect23 } from "effect";
4546
- import { and as and6, desc, eq as eq6, inArray as inArray3, ne as ne3, sql as sql5 } from "drizzle-orm";
4683
+ import { and as and6, desc, eq as eq6, inArray as inArray3, ne as ne3, sql as sql6 } from "drizzle-orm";
4547
4684
  var DEFAULT_MAX_LEAF_ITEMS3 = 128;
4548
4685
  var DEFAULT_SPLIT_DEFERRED_HARD_LIMIT2 = 512;
4549
4686
  var DEFAULT_CHANGE_LOG_LIMIT = 50;
@@ -4587,7 +4724,7 @@ var topicMetrics = Effect23.fn("topics.topicMetrics")(function* (modelKey, confi
4587
4724
  }
4588
4725
  leafMemberCounts.sort((a, b) => a - b);
4589
4726
  const growth = yield* Effect23.tryPromise({
4590
- try: () => db.select({ total: sql5`coalesce(sum(${topicGrowthState.splitRejectedCount}), 0)` }).from(topicGrowthState).where(eq6(topicGrowthState.modelKey, modelKey)),
4727
+ try: () => db.select({ total: sql6`coalesce(sum(${topicGrowthState.splitRejectedCount}), 0)` }).from(topicGrowthState).where(eq6(topicGrowthState.modelKey, modelKey)),
4591
4728
  catch: (cause) => cause
4592
4729
  }).pipe(routingFailure4("topics.topicMetrics.growth"));
4593
4730
  const splitRejectedTotal = Number(growth[0]?.total ?? 0);
@@ -4643,7 +4780,7 @@ var topicHealth = Effect23.fn("topics.topicHealth")(function* (modelKey, config)
4643
4780
  catch: (cause) => cause
4644
4781
  }).pipe(routingFailure4("topics.topicHealth.topics"));
4645
4782
  const countRows = yield* Effect23.tryPromise({
4646
- try: () => db.select({ topicKey: topicMemberships.topicKey, count: sql5`count(*)` }).from(topicMemberships).where(eq6(topicMemberships.modelKey, modelKey)).groupBy(topicMemberships.topicKey),
4783
+ try: () => db.select({ topicKey: topicMemberships.topicKey, count: sql6`count(*)` }).from(topicMemberships).where(eq6(topicMemberships.modelKey, modelKey)).groupBy(topicMemberships.topicKey),
4647
4784
  catch: (cause) => cause
4648
4785
  }).pipe(routingFailure4("topics.topicHealth.memberships"));
4649
4786
  const membershipCount = new Map(countRows.map((r) => [r.topicKey, Number(r.count)]));
@@ -4668,7 +4805,7 @@ var topicHealth = Effect23.fn("topics.topicHealth")(function* (modelKey, config)
4668
4805
 
4669
4806
  // ../store/src/graph/observability.ts
4670
4807
  import { Effect as Effect24 } from "effect";
4671
- import { and as and7, eq as eq7, isNull, sql as sql6 } from "drizzle-orm";
4808
+ import { and as and7, eq as eq7, isNull, sql as sql7 } from "drizzle-orm";
4672
4809
  var graphCoverage = Effect24.fn("GraphCoverage.run")(function* (modelKey) {
4673
4810
  const backend = yield* Service2;
4674
4811
  const db = backend.db;
@@ -4682,7 +4819,7 @@ var graphCoverage = Effect24.fn("GraphCoverage.run")(function* (modelKey) {
4682
4819
  const coveredMemories = totalMemories - isolatedMemories;
4683
4820
  const coveragePercent = totalMemories === 0 ? 0 : Math.round(coveredMemories / totalMemories * 1000) / 10;
4684
4821
  const pendingRows = yield* Effect24.tryPromise({
4685
- try: () => db.select({ count: sql6`count(*)` }).from(artifactEdges).where(and7(eq7(artifactEdges.status, "pending"), isNull(artifactEdges.expiredAt))),
4822
+ try: () => db.select({ count: sql7`count(*)` }).from(artifactEdges).where(and7(eq7(artifactEdges.status, "pending"), isNull(artifactEdges.expiredAt))),
4686
4823
  catch: (cause) => new StoreError({ message: `graphCoverage: pending: ${cause}` })
4687
4824
  });
4688
4825
  const pendingCandidates = totalMemories === 0 ? 0 : Number(pendingRows[0]?.count ?? 0);
@@ -5303,7 +5440,7 @@ var makeStore = (backend, modelConfig, topicsOptions, graphConfig, auditOptions,
5303
5440
  const relRows = await tx.select({ parentKey: topicRelations.parentKey }).from(topicRelations).where(and8(eq8(topicRelations.childKey, membership.topicKey), eq8(topicRelations.modelKey, membership.modelKey), eq8(topicRelations.relationKind, "parent_child")));
5304
5441
  let ancestorKey = relRows[0]?.parentKey;
5305
5442
  while (ancestorKey) {
5306
- await tx.update(topics).set({ memberCount: sql7`max(0, ${topics.memberCount} - 1)`, updatedAt: Date.now() }).where(and8(eq8(topics.topicKey, ancestorKey), eq8(topics.modelKey, membership.modelKey)));
5443
+ await tx.update(topics).set({ memberCount: sql8`max(0, ${topics.memberCount} - 1)`, updatedAt: Date.now() }).where(and8(eq8(topics.topicKey, ancestorKey), eq8(topics.modelKey, membership.modelKey)));
5307
5444
  const up = await tx.select({ parentKey: topicRelations.parentKey }).from(topicRelations).where(and8(eq8(topicRelations.childKey, ancestorKey), eq8(topicRelations.modelKey, membership.modelKey), eq8(topicRelations.relationKind, "parent_child")));
5308
5445
  ancestorKey = up[0]?.parentKey;
5309
5446
  }
@@ -5597,7 +5734,7 @@ var makeStore = (backend, modelConfig, topicsOptions, graphConfig, auditOptions,
5597
5734
  const now = Date.now();
5598
5735
  yield* Effect25.tryPromise({
5599
5736
  try: () => db.transaction(async (tx) => {
5600
- await tx.run(sql7`UPDATE ${memories2} SET embedding = CASE id ${sql7.join(entries.map((entry) => sql7`WHEN ${entry.id} THEN ${new Float32Array(entry.embedding).buffer}`), sql7` `)} END, embedding_model = ${modelName} WHERE id IN (${sql7.join(entries.map((entry) => entry.id), sql7`, `)})`);
5737
+ await tx.run(sql8`UPDATE ${memories2} SET embedding = CASE id ${sql8.join(entries.map((entry) => sql8`WHEN ${entry.id} THEN ${new Float32Array(entry.embedding).buffer}`), sql8` `)} END, embedding_model = ${modelName} WHERE id IN (${sql8.join(entries.map((entry) => entry.id), sql8`, `)})`);
5601
5738
  await tx.insert(memoryEvents2).values(entries.map((entry) => ({
5602
5739
  memoryId: entry.id,
5603
5740
  eventType: "embedding_updated",
@@ -5646,7 +5783,7 @@ var makeStore = (backend, modelConfig, topicsOptions, graphConfig, auditOptions,
5646
5783
  });
5647
5784
  const findStale = Effect25.fn("Store.findStale")(function* (model) {
5648
5785
  const rows = yield* Effect25.tryPromise({
5649
- try: () => db.select({ id: memories2.id }).from(memories2).where(and8(eq8(memories2.state, "active"), sql7`(embedding IS NULL OR embedding_model IS NULL OR embedding_model != ${model})`)),
5786
+ try: () => db.select({ id: memories2.id }).from(memories2).where(and8(eq8(memories2.state, "active"), sql8`(embedding IS NULL OR embedding_model IS NULL OR embedding_model != ${model})`)),
5650
5787
  catch: storeError("Store.findStale")
5651
5788
  });
5652
5789
  return rows.map((row) => row.id);
@@ -6284,6 +6421,7 @@ __export(exports_topics, {
6284
6421
  radiusOf: () => radiusOf,
6285
6422
  mergeOrBorrow: () => mergeOrBorrow,
6286
6423
  memoryEmbedHandler: () => memoryEmbedHandler,
6424
+ memoryEmbedBatchHandler: () => memoryEmbedBatchHandler,
6287
6425
  lloydRefine: () => lloydRefine,
6288
6426
  isActive: () => isActive,
6289
6427
  farthestPair: () => farthestPair,
@@ -6358,7 +6496,7 @@ __export(exports_graph, {
6358
6496
 
6359
6497
  // ../store/src/graph/concepts.ts
6360
6498
  import { Clock as Clock6, Effect as Effect27 } from "effect";
6361
- import { and as and9, asc as asc6, eq as eq9, isNotNull, sql as sql8 } from "drizzle-orm";
6499
+ import { and as and9, asc as asc6, eq as eq9, isNotNull, sql as sql9 } from "drizzle-orm";
6362
6500
  var DEFAULT_CLUSTER_K = 8;
6363
6501
  var DEFAULT_CLUSTER_MIN_SIZE = 3;
6364
6502
  var DEFAULT_CLUSTER_PASSES = 2;
@@ -6551,7 +6689,7 @@ var persistClusters = (db, members, config, now) => Effect27.tryPromise({
6551
6689
  }).onConflictDoUpdate({
6552
6690
  target: concepts.id,
6553
6691
  set: {
6554
- occurrenceCount: sql8`${concepts.occurrenceCount} + 1`,
6692
+ occurrenceCount: sql9`${concepts.occurrenceCount} + 1`,
6555
6693
  lastSeen: now
6556
6694
  }
6557
6695
  });
@@ -6565,7 +6703,7 @@ var persistClusters = (db, members, config, now) => Effect27.tryPromise({
6565
6703
  };
6566
6704
  })).onConflictDoUpdate({
6567
6705
  target: [memoryConcepts.conceptId, memoryConcepts.memoryId],
6568
- set: { confidence: sql8`excluded.confidence`, assignedAt: now }
6706
+ set: { confidence: sql9`excluded.confidence`, assignedAt: now }
6569
6707
  });
6570
6708
  conceptCount++;
6571
6709
  annotated += memberIndices.length;
@@ -6601,7 +6739,7 @@ function conceptClusterHandler(deps) {
6601
6739
  }
6602
6740
  // ../store/src/graph/dream.ts
6603
6741
  import { Clock as Clock7, Effect as Effect28 } from "effect";
6604
- import { and as and10, asc as asc7, eq as eq10, gt, inArray as inArray5, isNull as isNull3, sql as sql9 } from "drizzle-orm";
6742
+ import { and as and10, asc as asc7, eq as eq10, gt, inArray as inArray5, isNull as isNull3, sql as sql10 } from "drizzle-orm";
6605
6743
  var dreamStateDefaults = {
6606
6744
  minHours: 24,
6607
6745
  minNewMemories: 20,
@@ -6732,7 +6870,7 @@ var runDream = Effect28.fn("MemoryDream.run")(function* (model, classify) {
6732
6870
  };
6733
6871
  }
6734
6872
  const sinceCountRows = yield* Effect28.tryPromise({
6735
- try: () => db.select({ count: sql9`count(*)` }).from(memories).where(gt(memories.createdAt, lastDreamAt)),
6873
+ try: () => db.select({ count: sql10`count(*)` }).from(memories).where(gt(memories.createdAt, lastDreamAt)),
6736
6874
  catch: (cause) => new StoreError({ message: `memory.dream: count new: ${cause}` })
6737
6875
  });
6738
6876
  const memoriesSinceWatermark = Number(sinceCountRows[0]?.count ?? 0);
@@ -6911,7 +7049,7 @@ var runDream = Effect28.fn("MemoryDream.run")(function* (model, classify) {
6911
7049
  if (outcome.inserted)
6912
7050
  created++;
6913
7051
  }
6914
- const countRows = await tx.select({ count: sql9`count(*)` }).from(memories);
7052
+ const countRows = await tx.select({ count: sql10`count(*)` }).from(memories);
6915
7053
  const totalMemories = Number(countRows[0]?.count ?? 0);
6916
7054
  await tx.insert(dreamState).values({
6917
7055
  modelKey: model.id,
@@ -6950,7 +7088,7 @@ function graphDreamHandler(deps) {
6950
7088
  }
6951
7089
  // ../store/src/graph/repair.ts
6952
7090
  import { Clock as Clock8, Effect as Effect29 } from "effect";
6953
- import { and as and11, eq as eq11, isNotNull as isNotNull2, lt, or as or2, sql as sql10 } from "drizzle-orm";
7091
+ import { and as and11, eq as eq11, isNotNull as isNotNull2, lt, or as or2, sql as sql11 } from "drizzle-orm";
6954
7092
  var DEFAULT_EXPIRED_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
6955
7093
  var runRepair = (db, includePurgeExpired, expiredGraceMs, now) => Effect29.tryPromise({
6956
7094
  try: () => db.transaction(async (tx) => {
@@ -6962,15 +7100,15 @@ var runRepair = (db, includePurgeExpired, expiredGraceMs, now) => Effect29.tryPr
6962
7100
  edgeKind: artifactEdges.edgeKind,
6963
7101
  fromMemoryId: artifactEdges.fromMemoryId,
6964
7102
  toMemoryId: artifactEdges.toMemoryId,
6965
- fromMissing: sql10`NOT EXISTS (
7103
+ fromMissing: sql11`NOT EXISTS (
6966
7104
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.fromMemoryId}
6967
7105
  )`,
6968
- toMissing: sql10`NOT EXISTS (
7106
+ toMissing: sql11`NOT EXISTS (
6969
7107
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.toMemoryId}
6970
7108
  )`
6971
- }).from(artifactEdges).where(or2(sql10`NOT EXISTS (
7109
+ }).from(artifactEdges).where(or2(sql11`NOT EXISTS (
6972
7110
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.fromMemoryId}
6973
- )`, sql10`NOT EXISTS (
7111
+ )`, sql11`NOT EXISTS (
6974
7112
  SELECT 1 FROM memories WHERE memories.id = ${artifactEdges.toMemoryId}
6975
7113
  )`));
6976
7114
  for (const row of orphanRows) {
@@ -6993,12 +7131,12 @@ var runRepair = (db, includePurgeExpired, expiredGraceMs, now) => Effect29.tryPr
6993
7131
  const staleRows = await tx.select({
6994
7132
  conceptId: memoryConcepts.conceptId,
6995
7133
  memoryId: memoryConcepts.memoryId,
6996
- memoryMissing: sql10`NOT EXISTS (
7134
+ memoryMissing: sql11`NOT EXISTS (
6997
7135
  SELECT 1 FROM memories WHERE memories.id = ${memoryConcepts.memoryId}
6998
7136
  )`
6999
- }).from(memoryConcepts).where(or2(sql10`NOT EXISTS (
7137
+ }).from(memoryConcepts).where(or2(sql11`NOT EXISTS (
7000
7138
  SELECT 1 FROM concepts WHERE concepts.id = ${memoryConcepts.conceptId}
7001
- )`, sql10`NOT EXISTS (
7139
+ )`, sql11`NOT EXISTS (
7002
7140
  SELECT 1 FROM memories WHERE memories.id = ${memoryConcepts.memoryId}
7003
7141
  )`));
7004
7142
  for (const row of staleRows) {
@@ -7321,16 +7459,32 @@ var importFromDisk = () => new Promise((resolve, reject) => {
7321
7459
  reject(cause);
7322
7460
  }
7323
7461
  });
7324
- var buildSessionOptions = (opts) => ({
7325
- executionProviders: [
7326
- {
7327
- name: "cuda",
7328
- device_id: 0,
7329
- ...opts.gpuMemLimit !== undefined ? { gpu_mem_limit: opts.gpuMemLimit } : undefined,
7330
- ...opts.arenaExtendStrategy !== undefined ? { arena_extend_strategy: opts.arenaExtendStrategy } : undefined
7331
- }
7332
- ]
7333
- });
7462
+ var buildSessionOptions = (opts) => {
7463
+ const cuda = opts.gpuMemLimit !== undefined || opts.arenaExtendStrategy !== undefined;
7464
+ return {
7465
+ enableCpuMemArena: opts.enableCpuMemArena ?? false,
7466
+ ...opts.intraOpNumThreads !== undefined ? { intraOpNumThreads: opts.intraOpNumThreads } : undefined,
7467
+ ...opts.interOpNumThreads !== undefined ? { interOpNumThreads: opts.interOpNumThreads } : undefined,
7468
+ ...opts.executionMode !== undefined ? { executionMode: opts.executionMode } : undefined,
7469
+ ...opts.graphOptimizationLevel !== undefined ? { graphOptimizationLevel: opts.graphOptimizationLevel } : undefined,
7470
+ ...opts.enableMemPattern !== undefined ? { enableMemPattern: opts.enableMemPattern } : undefined,
7471
+ ...cuda ? {
7472
+ executionProviders: [
7473
+ {
7474
+ name: "cuda",
7475
+ device_id: 0,
7476
+ ...opts.gpuMemLimit !== undefined ? { gpu_mem_limit: opts.gpuMemLimit } : undefined,
7477
+ ...opts.arenaExtendStrategy !== undefined ? { arena_extend_strategy: opts.arenaExtendStrategy } : undefined
7478
+ }
7479
+ ]
7480
+ } : undefined
7481
+ };
7482
+ };
7483
+ var applyWasmOptions = (env, wasm) => {
7484
+ if (wasm?.numThreads === undefined)
7485
+ return;
7486
+ env.backends.onnx.wasm.numThreads = wasm.numThreads;
7487
+ };
7334
7488
  var defaults2 = {
7335
7489
  modelId: "Xenova/all-MiniLM-L6-v2",
7336
7490
  device: "cpu",
@@ -7372,10 +7526,11 @@ var make6 = (options) => {
7372
7526
  }));
7373
7527
  if (options.cacheDir)
7374
7528
  transformers.env.cacheDir = options.cacheDir;
7529
+ applyWasmOptions(transformers.env, options.wasm);
7375
7530
  pipe = yield* Effect32.tryPromise(() => transformers.pipeline("feature-extraction", options.modelId ?? defaults2.modelId, {
7376
7531
  device: options.device ?? defaults2.device,
7377
7532
  dtype: options.dtype ?? defaults2.dtype,
7378
- ...options.sessionOptions !== undefined ? { sessionOptions: buildSessionOptions(options.sessionOptions) } : undefined
7533
+ session_options: buildSessionOptions(options.sessionOptions ?? {})
7379
7534
  })).pipe(Effect32.mapError((cause) => new EmbeddingError({ message: `load model: ${cause}` })));
7380
7535
  return pipe;
7381
7536
  });
@@ -7676,7 +7831,7 @@ var configured8 = (options, deps) => make({
7676
7831
  deps
7677
7832
  });
7678
7833
  // ../llm/src/llm.ts
7679
- import { Cause as Cause3, Effect as Effect36, Option as Option12, Schema as Schema28 } from "effect";
7834
+ import { Cause as Cause4, Effect as Effect36, Option as Option12, Schema as Schema28 } from "effect";
7680
7835
  import {
7681
7836
  generateText as generateTextCall,
7682
7837
  NoSuchToolError,
@@ -7733,7 +7888,7 @@ var makeLLM = (model, defaults3 = {}) => {
7733
7888
  ...input.system !== undefined ? { system: input.system } : undefined,
7734
7889
  ...callOptions(input)
7735
7890
  }).then((result) => result.text),
7736
- catch: (cause) => new LLMError({ message: "LLM text generation failed", cause: Cause3.die(cause) })
7891
+ catch: (cause) => new LLMError({ message: "LLM text generation failed", cause: Cause4.die(cause) })
7737
7892
  });
7738
7893
  yield* Effect36.logDebug("llm.generate-text").pipe(Effect36.annotateLogs({
7739
7894
  scope: "@dimple/llm",
@@ -7785,7 +7940,7 @@ ${schemaJson}`,
7785
7940
  return Promise.reject(extracted.error);
7786
7941
  return input.schema.parse(extracted.value);
7787
7942
  },
7788
- catch: (cause) => new LLMError({ message: "LLM structured generation failed", cause: Cause3.die(cause) })
7943
+ catch: (cause) => new LLMError({ message: "LLM structured generation failed", cause: Cause4.die(cause) })
7789
7944
  });
7790
7945
  yield* Effect36.logDebug("llm.generate-object").pipe(Effect36.annotateLogs({
7791
7946
  scope: "@dimple/llm",
@@ -7897,7 +8052,8 @@ var embedderFromConfig = (model) => model.provider === "transformers" ? Effect39
7897
8052
  ...model.normalize !== undefined ? { normalize: model.normalize } : undefined,
7898
8053
  ...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : undefined,
7899
8054
  ...model.autoInstall !== undefined ? { autoInstall: model.autoInstall } : undefined,
7900
- ...model.sessionOptions !== undefined ? { sessionOptions: model.sessionOptions } : undefined
8055
+ ...model.sessionOptions !== undefined ? { sessionOptions: model.sessionOptions } : undefined,
8056
+ ...model.wasm !== undefined ? { wasm: model.wasm } : undefined
7901
8057
  })) : model.provider === "openai" ? model.apiKey === undefined ? Effect39.fail(new EmbeddingError({ message: `OpenAI model ${model.id} requires an apiKey` })) : Effect39.succeed(makeHostedEmbeddingModel({
7902
8058
  modelId: model.id,
7903
8059
  model: model.model,
@@ -7987,6 +8143,13 @@ var layer13 = (options) => {
7987
8143
  config: topicConfig,
7988
8144
  backend
7989
8145
  }));
8146
+ yield* register("memory.embed.batch", exports_topics.memoryEmbedBatchHandler({
8147
+ getModelConfig,
8148
+ embedOne: (text4, model) => options.embedderFor(model).pipe(Effect39.flatMap((m) => m.embedSingle(text4))),
8149
+ getMemoryContent: (memoryId) => store2.get(memoryId).pipe(Effect39.map((m) => m.content)),
8150
+ config: topicConfig,
8151
+ backend
8152
+ }));
7990
8153
  yield* register("concept.cluster", exports_graph.conceptClusterHandler({ getModelConfig, config: graphConfig, backend }));
7991
8154
  if (options.llm !== undefined) {
7992
8155
  yield* register("memory.dream", exports_graph.graphDreamHandler({
@@ -8062,6 +8225,13 @@ var layerConfig7 = (options) => Layer15.unwrap(Effect39.gen(function* () {
8062
8225
  });
8063
8226
  }
8064
8227
  const llm = cfg.config.llm;
8228
+ const models2 = embedding.models.map((model) => {
8229
+ const sessionOptions = mergeSessionOptions(embedding.sessionOptions, model.sessionOptions);
8230
+ return {
8231
+ ...model,
8232
+ ...sessionOptions !== undefined ? { sessionOptions } : undefined
8233
+ };
8234
+ });
8065
8235
  return layer13({
8066
8236
  storeUrl: cfg.config.storeUrl,
8067
8237
  ...cfg.config.defaultK !== undefined ? { defaultK: cfg.config.defaultK } : undefined,
@@ -8069,7 +8239,7 @@ var layerConfig7 = (options) => Layer15.unwrap(Effect39.gen(function* () {
8069
8239
  ...cfg.config.defaultTtlSecs !== undefined ? { defaultTtlSecs: cfg.config.defaultTtlSecs } : undefined,
8070
8240
  jobsUrl,
8071
8241
  ...options?.migrationFolder !== undefined ? { migrationFolder: options.migrationFolder } : undefined,
8072
- models: embedding.models,
8242
+ models: models2,
8073
8243
  defaultModelId: embedding.defaultModel,
8074
8244
  embedderFor: embedderFromConfig,
8075
8245
  ...cfg.config.topics !== undefined ? { topics: { config: cfg.config.topics } } : undefined,
@@ -8204,7 +8374,7 @@ var remapError = (cause) => {
8204
8374
  };
8205
8375
 
8206
8376
  // src/make.ts
8207
- import { Effect as Effect50, Exit as Exit7, Layer as Layer26, ManagedRuntime, Scope as Scope5 } from "effect";
8377
+ import { Effect as Effect50, Exit as Exit8, Layer as Layer26, ManagedRuntime, Scope as Scope5 } from "effect";
8208
8378
 
8209
8379
  // ../extractor/src/code-extractor.ts
8210
8380
  import { Context as Context13, Effect as Effect40, Layer as Layer16 } from "effect";
@@ -9098,7 +9268,7 @@ var resolveConfigInput = Effect48.fn("Sdk.resolveConfigInput")(function* (input)
9098
9268
  var configLayerFromInput = (input) => Layer25.effect(Service4, resolveConfigInput(input).pipe(Effect48.map((config) => Service4.of({ config }))));
9099
9269
 
9100
9270
  // src/dimple.ts
9101
- import { Cause as Cause4, Effect as Effect49, Exit as Exit6, Option as Option15, Schema as Schema40 } from "effect";
9271
+ import { Cause as Cause5, Effect as Effect49, Exit as Exit7, Option as Option15, Schema as Schema40 } from "effect";
9102
9272
 
9103
9273
  // src/indexer.ts
9104
9274
  import { execFileSync } from "node:child_process";
@@ -9585,6 +9755,8 @@ var makeFacade = (state, reload, dispose) => {
9585
9755
  const keyPrefix = options.keyPrefix ?? "code-";
9586
9756
  const namespace = options.namespace;
9587
9757
  const embed = options.embed !== false;
9758
+ const deferEmbed = embed && options.deferEmbed === true;
9759
+ const embedUnits = embed && !deferEmbed;
9588
9760
  const maxFileBytes = options.maxFileBytes ?? 64 * 1024;
9589
9761
  const maxChunkBytes = options.maxChunkBytes ?? 8 * 1024;
9590
9762
  const extensions = [...options.extensions ?? INDEXABLE_EXTENSIONS];
@@ -9602,6 +9774,7 @@ var makeFacade = (state, reload, dispose) => {
9602
9774
  let skipped = 0;
9603
9775
  const unitIds = new Map;
9604
9776
  const freshIds = [];
9777
+ const pendingEmbedIds = [];
9605
9778
  const pendingEdges = [];
9606
9779
  const pendingCallEdges = [];
9607
9780
  const stableChunkId = (chunkId) => chunkId.replace(/:\d+$/, "");
@@ -9612,6 +9785,8 @@ var makeFacade = (state, reload, dispose) => {
9612
9785
  return;
9613
9786
  return parentAndMethod.split(".")[0];
9614
9787
  };
9788
+ if (embedUnits)
9789
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
9615
9790
  for (const filePath of walk.files) {
9616
9791
  const relative = keyForPath(walk.root, filePath);
9617
9792
  const language = languageForPath(filePath);
@@ -9629,77 +9804,143 @@ var makeFacade = (state, reload, dispose) => {
9629
9804
  let upd = 0;
9630
9805
  let dups = 0;
9631
9806
  const fileKey = `${keyPrefix}${relative}`;
9632
- const fileOutcome = yield* upsertUnit({
9633
- logicalKey: fileKey,
9634
- content: source,
9635
- ...namespace !== undefined ? { namespace } : undefined,
9636
- ttlSecs: null,
9637
- metadata: JSON.stringify({
9638
- filePath,
9639
- language,
9640
- kind: "file",
9641
- imports: extracted.imports ?? [],
9642
- ...commit !== null ? { codeVersion: commit } : undefined
9643
- }),
9644
- contentType: `text/x-${language}`
9645
- }, embed);
9646
- if (fileOutcome.kind === "skipped") {
9647
- dups += 1;
9648
- unitIds.set(fileKey, fileOutcome.id);
9649
- } else {
9650
- if (fileOutcome.kind === "updated")
9651
- upd += 1;
9652
- else
9653
- units += 1;
9654
- unitIds.set(fileKey, fileOutcome.id);
9655
- freshIds.push(fileOutcome.id);
9656
- }
9657
9807
  const classKeys = new Map;
9658
9808
  for (const chunk of extracted.chunks) {
9659
9809
  if (chunk.kind === "class" || chunk.kind.endsWith("class")) {
9660
9810
  classKeys.set(chunk.name, chunkKeyOf(chunk.id));
9661
9811
  }
9662
9812
  }
9813
+ const specs = [
9814
+ {
9815
+ key: fileKey,
9816
+ input: {
9817
+ logicalKey: fileKey,
9818
+ content: source,
9819
+ ...namespace !== undefined ? { namespace } : undefined,
9820
+ ttlSecs: null,
9821
+ metadata: JSON.stringify({
9822
+ filePath,
9823
+ language,
9824
+ kind: "file",
9825
+ imports: extracted.imports ?? [],
9826
+ ...commit !== null ? { codeVersion: commit } : undefined
9827
+ }),
9828
+ contentType: `text/x-${language}`
9829
+ }
9830
+ }
9831
+ ];
9663
9832
  for (const chunk of extracted.chunks) {
9664
9833
  const chunkKey = chunkKeyOf(chunk.id);
9665
9834
  const content = [chunk.docstring, chunk.sourceText].filter((c) => c !== undefined && c.length > 0).join(`
9666
9835
 
9667
9836
  `).slice(0, maxChunkBytes);
9668
- const chunkOutcome = yield* upsertUnit({
9669
- logicalKey: chunkKey,
9670
- content,
9671
- ...namespace !== undefined ? { namespace } : undefined,
9672
- ttlSecs: null,
9673
- metadata: JSON.stringify({
9674
- filePath,
9675
- language,
9676
- kind: chunk.kind,
9677
- name: chunk.name,
9678
- startLine: chunk.startLine,
9679
- endLine: chunk.endLine,
9680
- ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : undefined,
9681
- ...commit !== null ? { codeVersion: commit } : undefined
9682
- }),
9683
- contentType: `text/x-${language}`
9684
- }, embed);
9685
- if (chunkOutcome.kind === "skipped") {
9837
+ const parent = chunk.kind === "method" ? parentClassOf(chunk.id) : undefined;
9838
+ const classKey = parent === undefined ? undefined : classKeys.get(parent);
9839
+ specs.push({
9840
+ key: chunkKey,
9841
+ input: {
9842
+ logicalKey: chunkKey,
9843
+ content,
9844
+ ...namespace !== undefined ? { namespace } : undefined,
9845
+ ttlSecs: null,
9846
+ metadata: JSON.stringify({
9847
+ filePath,
9848
+ language,
9849
+ kind: chunk.kind,
9850
+ name: chunk.name,
9851
+ startLine: chunk.startLine,
9852
+ endLine: chunk.endLine,
9853
+ ...chunk.docstring !== undefined ? { docstring: chunk.docstring } : undefined,
9854
+ ...commit !== null ? { codeVersion: commit } : undefined
9855
+ }),
9856
+ contentType: `text/x-${language}`
9857
+ },
9858
+ ...classKey !== undefined ? { containedBy: classKey } : undefined
9859
+ });
9860
+ }
9861
+ const outcomes = new Map;
9862
+ const record = (key, kind, id) => {
9863
+ outcomes.set(key, kind);
9864
+ if (kind === "skipped") {
9686
9865
  dups += 1;
9687
- unitIds.set(chunkKey, chunkOutcome.id);
9688
- } else {
9689
- if (chunkOutcome.kind === "updated")
9690
- upd += 1;
9691
- else
9692
- units += 1;
9693
- unitIds.set(chunkKey, chunkOutcome.id);
9694
- freshIds.push(chunkOutcome.id);
9866
+ unitIds.set(key, id);
9867
+ return;
9695
9868
  }
9696
- if (chunkOutcome.kind !== "skipped" && chunk.kind === "method") {
9697
- const parent = parentClassOf(chunk.id);
9698
- const classKey = parent === undefined ? undefined : classKeys.get(parent);
9699
- if (classKey !== undefined) {
9700
- pendingEdges.push({ from: classKey, to: chunkKey });
9869
+ if (kind === "updated")
9870
+ upd += 1;
9871
+ else
9872
+ units += 1;
9873
+ unitIds.set(key, id);
9874
+ freshIds.push(id);
9875
+ };
9876
+ const upsertAll = (candidates2) => Effect49.gen(function* () {
9877
+ const ids = [];
9878
+ for (const spec of candidates2) {
9879
+ const outcome = yield* upsertUnit(spec.input, embedUnits);
9880
+ record(spec.key, outcome.kind, outcome.id);
9881
+ ids.push(outcome.id);
9882
+ }
9883
+ return ids;
9884
+ });
9885
+ if (deferEmbed) {
9886
+ const fresh = [];
9887
+ for (const spec of specs) {
9888
+ const existing = yield* core().store.getByKey(spec.key, namespace ?? "default").pipe(Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
9889
+ if (existing === undefined) {
9890
+ fresh.push(spec);
9891
+ continue;
9892
+ }
9893
+ const outcome = yield* upsertUnit(spec.input, false);
9894
+ record(spec.key, outcome.kind, outcome.id);
9895
+ if (outcome.kind !== "skipped" || existing.embedding == null || existing.embeddingModel !== core().defaultModel.id) {
9896
+ pendingEmbedIds.push(outcome.id);
9701
9897
  }
9702
9898
  }
9899
+ if (fresh.length > 0) {
9900
+ const freshKeys = fresh.map((spec) => spec.key);
9901
+ if (new Set(freshKeys).size !== freshKeys.length) {
9902
+ pendingEmbedIds.push(...yield* upsertAll(fresh));
9903
+ } else {
9904
+ const mems = yield* core().store.writeMany(fresh.map((spec) => spec.input));
9905
+ for (const mem of mems) {
9906
+ record(mem.logicalKey, "indexed", mem.id);
9907
+ pendingEmbedIds.push(mem.id);
9908
+ }
9909
+ }
9910
+ }
9911
+ } else if (!embedUnits) {
9912
+ yield* upsertAll(specs);
9913
+ } else {
9914
+ const fresh = [];
9915
+ for (const spec of specs) {
9916
+ const existing = yield* core().store.getByKey(spec.key, namespace ?? "default").pipe(Effect49.catchTag("Store.NotFound", () => Effect49.succeed(undefined)));
9917
+ if (existing === undefined) {
9918
+ fresh.push(spec);
9919
+ continue;
9920
+ }
9921
+ const outcome = yield* upsertUnit(spec.input, embed);
9922
+ record(spec.key, outcome.kind, outcome.id);
9923
+ }
9924
+ if (fresh.length > 0) {
9925
+ const freshKeys = fresh.map((spec) => spec.key);
9926
+ if (new Set(freshKeys).size !== freshKeys.length) {
9927
+ yield* upsertAll(fresh);
9928
+ } else {
9929
+ const mems = yield* core().store.writeMany(fresh.map((spec) => spec.input));
9930
+ const vectors = [];
9931
+ for (const spec of fresh) {
9932
+ vectors.push(yield* core().defaultEmbedder.embedSingle(spec.input.content));
9933
+ }
9934
+ yield* core().store.updateEmbeddingMany(mems.map((mem, i) => ({ id: mem.id, embedding: [...vectors[i]] })), core().defaultModel.id);
9935
+ for (const mem of mems)
9936
+ record(mem.logicalKey, "indexed", mem.id);
9937
+ }
9938
+ }
9939
+ }
9940
+ for (const spec of specs) {
9941
+ if (spec.containedBy !== undefined && outcomes.get(spec.key) !== "skipped") {
9942
+ pendingEdges.push({ from: spec.containedBy, to: spec.key });
9943
+ }
9703
9944
  }
9704
9945
  const importTargets = resolveImports(filePath, (extracted.imports ?? []).map((imp) => imp.source), extensions);
9705
9946
  for (const target of importTargets) {
@@ -9726,8 +9967,8 @@ var makeFacade = (state, reload, dispose) => {
9726
9967
  }
9727
9968
  return { units, upd, dups };
9728
9969
  }));
9729
- if (Exit6.isFailure(exit)) {
9730
- failed.push({ filePath, message: Cause4.pretty(exit.cause) });
9970
+ if (Exit7.isFailure(exit)) {
9971
+ failed.push({ filePath, message: Cause5.pretty(exit.cause) });
9731
9972
  } else {
9732
9973
  indexed += exit.value.units;
9733
9974
  updated += exit.value.upd;
@@ -9762,6 +10003,16 @@ var makeFacade = (state, reload, dispose) => {
9762
10003
  for (const edge of pendingCallEdges) {
9763
10004
  yield* Effect49.exit(edgeEffect({ ...edge, edgeKind: "code_calls" }));
9764
10005
  }
10006
+ const EMBED_BATCH = 512;
10007
+ if (deferEmbed) {
10008
+ for (let i = 0;i < pendingEmbedIds.length; i += EMBED_BATCH) {
10009
+ yield* core().runtime.enqueue(MemoryEmbedBatch, {
10010
+ memoryIds: pendingEmbedIds.slice(i, i + EMBED_BATCH),
10011
+ modelKey: core().defaultModel.id,
10012
+ nonce: crypto.randomUUID()
10013
+ });
10014
+ }
10015
+ }
9765
10016
  return {
9766
10017
  files: walk.files.length,
9767
10018
  indexed,
@@ -9770,7 +10021,8 @@ var makeFacade = (state, reload, dispose) => {
9770
10021
  skipped,
9771
10022
  unsupported: walk.unsupported,
9772
10023
  commit,
9773
- failed
10024
+ failed,
10025
+ ...deferEmbed ? { pendingEmbeddings: pendingEmbedIds.length } : undefined
9774
10026
  };
9775
10027
  })),
9776
10028
  updateEmbedding: (id, embedding, modelKey) => run(Effect49.gen(function* () {
@@ -9857,10 +10109,10 @@ var bootStack = async (options) => {
9857
10109
  core,
9858
10110
  parser,
9859
10111
  extractor,
9860
- dispose: () => Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(rt.disposeEffect)))
10112
+ dispose: () => Effect50.runPromise(Scope5.close(scope, Exit8.void).pipe(Effect50.andThen(rt.disposeEffect)))
9861
10113
  };
9862
10114
  } catch (cause) {
9863
- await Effect50.runPromise(Scope5.close(scope, Exit7.void).pipe(Effect50.andThen(runtime?.disposeEffect ?? Effect50.void))).catch(() => {
10115
+ await Effect50.runPromise(Scope5.close(scope, Exit8.void).pipe(Effect50.andThen(runtime?.disposeEffect ?? Effect50.void))).catch(() => {
9864
10116
  return;
9865
10117
  });
9866
10118
  return Promise.reject(cause);