@mindot/will 0.6.0 → 0.8.0

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 (40) hide show
  1. package/README.md +87 -22
  2. package/dist/channels/discord.d.ts +1 -1
  3. package/dist/channels/whatsapp.d.ts +1 -1
  4. package/dist/cli.js +11104 -10312
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +972 -213
  8. package/dist/index.js.map +1 -1
  9. package/dist/mcp/effectors.d.ts +1 -1
  10. package/dist/{will-Bikuk4s2.d.ts → will-cS6k4uiJ.d.ts} +510 -86
  11. package/package.json +1 -1
  12. package/src/cognition/agency/engines/action.selector.ts +42 -9
  13. package/src/cognition/agency/engines/affordance.synthesizer.ts +9 -0
  14. package/src/cognition/agency/engines/motor.schema.executor.ts +4 -0
  15. package/src/cognition/agency/engines/reafference.engine.ts +40 -5
  16. package/src/cognition/agency/reconcile.learning.ts +23 -0
  17. package/src/cognition/agency/schemas/repertoire.ts +114 -7
  18. package/src/cognition/agency/selection.scoring.ts +7 -1
  19. package/src/cognition/agency/types.ts +9 -0
  20. package/src/cognition/config.mirror.entities.ts +1 -1
  21. package/src/cognition/faculties/executive.engine/engine.ts +136 -58
  22. package/src/cognition/faculties/executive.engine/facet.ts +10 -2
  23. package/src/cognition/faculties/executive.engine/prompt.factory.ts +2 -1
  24. package/src/cognition/index.ts +4 -0
  25. package/src/cognition/memory/vector.embedder.ts +9 -5
  26. package/src/cognition/utilities/token.tracker.ts +191 -96
  27. package/src/host/boot.ts +78 -22
  28. package/src/index.ts +35 -0
  29. package/src/llm/index.ts +397 -96
  30. package/src/llm/routing.ts +198 -0
  31. package/src/llm/summarizer.ts +5 -1
  32. package/src/runners/thin-shim.runner.ts +18 -6
  33. package/src/sdk/will.ts +82 -16
  34. package/src/stem/guards/identity.coherence.ts +17 -6
  35. package/src/stem/index.ts +18 -3
  36. package/src/stem/mind.ts +155 -24
  37. package/src/stem/policy/arbiter.ts +171 -0
  38. package/src/stem/policy/rule.table.ts +172 -0
  39. package/src/stem/policy/verdict.recorder.ts +0 -0
  40. package/src/stem/tracts/effector.controller.ts +426 -0
@@ -60,7 +60,10 @@ import {
60
60
  type GatingState
61
61
  } from '#faculties/executive.engine/gating'
62
62
  import { LLMDirector } from '#llm/index'
63
- import { defaultModelFor, type LLMProvider } from '#llm/index'
63
+ import { MOCK_PROVIDER, MOCK_MODEL } from '#llm/index'
64
+ import { getCompletionSource } from '#core/completion.recorder'
65
+ import { providerKeyFromEnv, type LLMProvider, type LLMCallMeta, type ProviderCredential, type LLMWire } from '#llm/index'
66
+ import type { ModelRouter } from '#llm/routing'
64
67
  import { buildFallbackOutput, parseResponse } from '#faculties/executive.engine/parser'
65
68
  import { selectProcess, ideationTemperature, DELIBERATE_THRESHOLD } from '#faculties/executive.engine/effort.gate'
66
69
  import { proposeCandidates } from '#faculties/executive.engine/deliberate.reasoning'
@@ -145,14 +148,14 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
145
148
 
146
149
  // ── Injected dependencies ──────────────────────────────────
147
150
  private _willId: string | null = null
148
- /** Per-Will, per-role model ids (config.model, resolved in mind.ts). */
149
- private _models: { executive: string | null; summarizer: string | null; deliberation: string | null; conversation: string | null } =
150
- { executive: null, summarizer: null, deliberation: null, conversation: null }
151
+ /**
152
+ * The Will's default model (config.model's `executive` role, resolved in
153
+ * mind.ts). Every other role reaches its model through the router — see
154
+ * `compileRoleRouter`.
155
+ */
156
+ private _modelId: string | null = null
151
157
  /** Per-Will LLM transport overrides (config.llm) — env fallbacks apply per field. */
152
- private _llm: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number } | null = null
153
- /** One director per distinct model — same config, different model. Shared
154
- * tracker/recorder/willId, so ledger attribution and replay hold per role. */
155
- private _directorCache = new Map<string, LLMDirector>()
158
+ private _llm: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number; credentials?: Partial<Record<string, ProviderCredential>>; router?: ModelRouter | null; wire?: LLMWire } | null = null
156
159
  private _workingMemory: WorkingMemory | null = null
157
160
  private _goalManager: GoalManager | null = null
158
161
  private _episodicConsolidator: EpisodicConsolidator | null = null
@@ -267,13 +270,18 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
267
270
 
268
271
  set willId( willId: string ){ this._willId = willId }
269
272
 
270
- /** Per-Will role models (config.model, resolved). Set before the first tick. */
271
- set models( m: { executive: string | null; summarizer: string | null; deliberation: string | null; conversation: string | null } ){ this._models = m }
272
- get models(): { executive: string | null; summarizer: string | null; deliberation: string | null; conversation: string | null } { return this._models }
273
+ /**
274
+ * The Will's default model. Set before the first tick.
275
+ *
276
+ * This replaced a four-role map (W7): the other roles are routing rules now,
277
+ * compiled in mind.ts, so the engine holds one model and one router rather
278
+ * than a model per role plus a router.
279
+ */
280
+ set modelId( id: string | null ){ this._modelId = id }
281
+ get modelId(): string | null { return this._modelId }
282
+
273
283
  /** Per-Will LLM transport overrides (config.llm). Set before the first tick. */
274
- set llm( c: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number } | null ){ this._llm = c }
275
- /** The executive-role model id (back-compat read). */
276
- get modelId(): string | null { return this._models.executive }
284
+ set llm( c: { provider?: string; apiKey?: string; baseUrl?: string; maxOutputTokens?: number; timeoutMs?: number; credentials?: Partial<Record<string, ProviderCredential>>; router?: ModelRouter | null; wire?: LLMWire } | null ){ this._llm = c }
277
285
 
278
286
  // ── Public surface ─────────────────────────────────────────
279
287
 
@@ -301,48 +309,98 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
301
309
  * and subscribe() to receive facet decisions.
302
310
  */
303
311
  /** Get-or-create the director for a model id (shared config, per-Will). */
304
- private _directorFor( model: string ): LLMDirector {
305
- let d = this._directorCache.get( model )
306
- if( !d ){
307
- // Per-Will transport overrides first (BYO keys), env per field otherwise.
308
- d = new LLMDirector( {
309
- willId: this._willId!,
310
- model,
311
- maxOutputTokens: this._llm?.maxOutputTokens ?? parseInt( process.env.WILL_MAX_OUTPUT_TOKENS ?? '8096'),
312
- // Provider-agnostic key; falls back to ANTHROPIC_API_KEY for back-compat.
313
- apiKey: this._llm?.apiKey ?? process.env.WILL_LLM_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? '',
314
- provider: ( this._llm?.provider ?? process.env.WILL_LLM_PROVIDER ?? 'anthropic') as LLMProvider,
315
- // Optional base-URL override (e.g. Ollama / Azure / self-hosted). Unset →
316
- // the director uses the provider's official endpoint.
317
- baseUrl: this._llm?.baseUrl ?? process.env.WILL_LLM_BASE_URL ?? process.env.OPENAI_BASE_URL,
318
- timeoutMs: this._llm?.timeoutMs ?? ( process.env.WILL_LLM_TIMEOUT_MS ? parseInt( process.env.WILL_LLM_TIMEOUT_MS ) : undefined ),
319
- sessionLogger: this._sessionLogger,
320
- mock: this._testMode,
321
- // Inject the per-Will tracker (R4) so live calls record usage here, not
322
- // through a process global. null is fine — the director skips recording.
323
- tokenTracker: this._tokenTracker,
324
- } )
325
- this._directorCache.set( model, d )
326
- }
327
- return d
312
+ /**
313
+ * The provider, from config or environment — never guessed.
314
+ *
315
+ * This used to default to 'anthropic', which is how a Will configured for one
316
+ * vendor could quietly talk to another. An unset provider is a configuration
317
+ * error, and saying so at construction is far cheaper than a 401 mid-tick.
318
+ */
319
+ private _requireProvider(): LLMProvider {
320
+ const provider = this._llm?.provider ?? process.env.WILL_LLM_PROVIDER
321
+ ?? ( this._noLiveCalls() ? MOCK_PROVIDER : undefined )
322
+ if( !provider )
323
+ throw new Error(
324
+ 'No LLM provider configured. Set one on the Will (llm.provider) or in ' +
325
+ 'the environment (WILL_LLM_PROVIDER) the engine carries no default.'
326
+ )
327
+ return provider as LLMProvider
328
+ }
329
+
330
+ /**
331
+ * True when this Will cannot make a live call, so provider/model are not
332
+ * required: mock mode, or a replay re-feeding recorded completions.
333
+ */
334
+ private _noLiveCalls(): boolean {
335
+ return this._testMode || ( !!this._willId && !!getCompletionSource( this._willId ) )
328
336
  }
329
337
 
338
+ /**
339
+ * Build this Will's one and only director.
340
+ *
341
+ * There used to be a cache of them, keyed by model, because the per-role
342
+ * model map had no other way to make a role use a different model. Routing
343
+ * gave it one — the role map now compiles to rules (see `compileRoleRouter`)
344
+ * and a single director resolves every call's endpoint per call. That is also
345
+ * strictly more faithful: a facet follows the work it is doing rather than
346
+ * whatever role it happened to be spawned under.
347
+ */
348
+ private _buildDirector( model: string ): LLMDirector {
349
+ // Resolved first: the key fallback below is keyed by it.
350
+ const provider = this._requireProvider()
351
+
352
+ // Per-Will transport overrides first (BYO keys), env per field otherwise.
353
+ return new LLMDirector( {
354
+ willId: this._willId!,
355
+ model,
356
+ maxOutputTokens: this._llm?.maxOutputTokens ?? parseInt( process.env.WILL_MAX_OUTPUT_TOKENS ?? '8096'),
357
+ // Config, then the provider-agnostic env, then THIS provider's own env.
358
+ // The last step is not the fallback W9 removed: that one ended at
359
+ // ANTHROPIC_API_KEY for every provider, so a Will pointed elsewhere sent
360
+ // Anthropic's key to a stranger. This one can only ever read the key
361
+ // belonging to the provider actually configured.
362
+ apiKey: this._llm?.apiKey ?? process.env.WILL_LLM_API_KEY ?? providerKeyFromEnv( provider ) ?? '',
363
+ provider,
364
+ // Optional base-URL override (e.g. Ollama / Azure / self-hosted). Unset →
365
+ // the director uses the provider's official endpoint.
366
+ baseUrl: this._llm?.baseUrl ?? process.env.WILL_LLM_BASE_URL ?? process.env.OPENAI_BASE_URL,
367
+ timeoutMs: this._llm?.timeoutMs ?? ( process.env.WILL_LLM_TIMEOUT_MS ? parseInt( process.env.WILL_LLM_TIMEOUT_MS ) : undefined ),
368
+ sessionLogger: this._sessionLogger,
369
+ mock: this._testMode,
370
+ // Inject the per-Will tracker (R4) so live calls record usage here, not
371
+ // through a process global. null is fine — the director skips recording.
372
+ tokenTracker: this._tokenTracker,
373
+ // Credentials for routed calls, narrowed by the stem from the host's
374
+ // per-provider map. Prices from that same map ride to the TokenTracker
375
+ // instead, so nothing carries pricing into the call path.
376
+ ...( this._llm?.credentials ? { credentials: this._llm.credentials } : {} ),
377
+ // Per-call model selection — the host's router chained with the rules
378
+ // compiled from the per-role model map.
379
+ ...( this._llm?.router ? { router: this._llm.router } : {} ),
380
+ // Dialect for the default provider — required for anything outside the
381
+ // known set, so the engine never guesses how to talk to an endpoint.
382
+ ...( this._llm?.wire ? { wire: this._llm.wire } : {} ),
383
+ } )
384
+ }
385
+
386
+ /**
387
+ * Spawn a facet.
388
+ *
389
+ * `role` declares the facet's intent at the call site. It no longer selects a
390
+ * model: that used to happen here, pinning a facet for life to whatever role
391
+ * it was spawned under, and it now happens per call from the focus function
392
+ * the caller sets immediately afterwards (W7). The two always agreed — every
393
+ * spawn site sets a focus whose `function` matches its role — so the routed
394
+ * answer is the same one, decided later and from the work itself.
395
+ */
330
396
  spawnFacet( role?: 'deliberation' | 'conversation' | 'outreach' | 'supervision'): { attention: 'available' | 'full', handle?: ExecutiveFacetHandle } {
397
+ void role
331
398
  // Delegate to FacetSupervisor (R5-g-3), passing the current engine
332
399
  // attachments. The supervisor owns the registry + attention budget and
333
400
  // performs the throw-checks on bus / director / state ref.
334
- // A role with its own configured model gets that role's director; every
335
- // other facet shares the executive's (one self, role-appropriate depth).
336
- // Outreach speaks with the conversation voice; supervision thinks with the
337
- // executive's depth.
338
- const roleModel =
339
- role === 'deliberation' ? this._models.deliberation :
340
- role === 'conversation' || role === 'outreach' ? this._models.conversation :
341
- null
342
- const director = roleModel && this._llmDirector ? this._directorFor( roleModel ) : this._llmDirector
343
401
  return this._facetSupervisor.spawn( {
344
402
  bus: this._bus,
345
- llmDirector: director,
403
+ llmDirector: this._llmDirector,
346
404
  stateRef: this._lastStateRef,
347
405
  willId: this._willId,
348
406
  inbox: this._inbox,
@@ -433,15 +491,32 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
433
491
  this._gatingState.executiveInterval = rtConfig.executiveInterval
434
492
  this._gatingState.cooldownTicks = rtConfig.cooldownTicks
435
493
 
436
- // Initialize LLM directors if not yet done (requires willId). One director
437
- // per distinct role model; roles that share a model share the instance.
494
+ // Initialize the LLM director if not yet done (requires willId). One per
495
+ // Will every role reaches its model through the router.
438
496
  if( !this._llmDirector && this._willId ){
439
- const execModel = this._models.executive ?? process.env.WILL_LLM_MODEL
440
- ?? defaultModelFor( ( this._llm?.provider ?? process.env.WILL_LLM_PROVIDER ?? 'anthropic') as LLMProvider )
441
- this._llmDirector = this._directorFor( execModel )
442
- // The summarizer runs its role's model (falls back to executive) with the
443
- // same provider, session logging and token tracking.
444
- this._summarizer?.attachLLMDirector( this._directorFor( this._models.summarizer ?? execModel ) )
497
+ // No default model. The engine used to fall back to a Claude id for every
498
+ // provider but GLM, which meant a misconfigured Will asked the wrong
499
+ // vendor for the wrong model and failed at the first tick with a 404
500
+ // instead of at construction with a sentence.
501
+ //
502
+ // Two cases legitimately have no credentials and are exempt: a test-mode
503
+ // Will (never reaches a network — demanding a key would break the whole
504
+ // point of the no-key quickstart) and a replay (completions are re-fed
505
+ // from the tape, which carries the model that actually served them). The
506
+ // sentinel is what a mock run records, so the tape still says plainly
507
+ // that nothing real answered.
508
+ const defaultModel = this._modelId ?? process.env.WILL_LLM_MODEL
509
+ ?? ( this._noLiveCalls() ? MOCK_MODEL : undefined )
510
+ if( !defaultModel )
511
+ throw new Error(
512
+ 'No LLM model configured. Set one on the Will (llm.model) or in the ' +
513
+ 'environment (WILL_LLM_MODEL) — the engine carries no default.'
514
+ )
515
+ this._llmDirector = this._buildDirector( defaultModel )
516
+ // The summarizer shares that director. Its calls tag themselves
517
+ // `category: 'summarizer'`, which is what a configured summarizer role
518
+ // compiles to — so it still gets its own model, chosen per call.
519
+ this._summarizer?.attachLLMDirector( this._llmDirector )
445
520
  }
446
521
 
447
522
  // Evaluate gating
@@ -593,7 +668,7 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
593
668
  ideationUserMessage,
594
669
  tick: state.tick,
595
670
  proposeTemperature,
596
- meta: { category: 'executive', attribute: 'master', function: 'ideation' },
671
+ meta: { category: 'executive', attribute: 'master', function: 'ideation', demand: processSelection.effortScore },
597
672
  } )
598
673
  logger.info(
599
674
  `[executive] ◆ deliberate propose tick=${state.tick} ` +
@@ -646,7 +721,10 @@ export class ExecutiveEngine extends AsyncEngine implements CognitiveEngine {
646
721
 
647
722
  try {
648
723
  // Use streaming call when clients are connected (F3); fall back to regular call.
649
- const masterMeta = { category: 'executive', attribute: 'master', function: 'decision' }
724
+ // MODEL_ROUTING W0 the effort gate already weighed this tick's demand
725
+ // (uncertainty, prior confidence, novelty, a pending reply, stress load);
726
+ // forward it rather than inventing a second measure of the same thing.
727
+ const masterMeta: LLMCallMeta = { category: 'executive', attribute: 'master', function: 'decision', demand: processSelection.effortScore }
650
728
  const result = this._chunkBroadcaster
651
729
  ? await this._llmDirector.callStream( systemPrompt, userMessage, state.tick, this._chunkBroadcaster, undefined, masterMeta )
652
730
  : await this._llmDirector.call( systemPrompt, userMessage, state.tick, undefined, masterMeta )
@@ -424,7 +424,7 @@ export class ExecutiveFacet {
424
424
  ideationUserMessage,
425
425
  tick: currentState.tick,
426
426
  proposeTemperature,
427
- meta: { category: 'executive', attribute: 'facet', function: this._currentFocus?.function ?? 'ideation', scope: this.facetId },
427
+ meta: { category: 'executive', attribute: 'facet', function: this._currentFocus?.function ?? 'ideation', scope: this.facetId, demand: processSelection.effortScore },
428
428
  } )
429
429
  logger.info(
430
430
  `[executive.facet] ${this.facetId} ◆ deliberate propose tick=${currentState.tick} ` +
@@ -465,11 +465,19 @@ export class ExecutiveFacet {
465
465
  // Use streaming call when a per-facet chunk handler is registered —
466
466
  // this enables entity-scoped token delivery (e.g. AuditionEngine SSE).
467
467
  // Falls back to regular call when no handler is present.
468
+ // MODEL_ROUTING W0 — same effort gate as master, so a facet's demand is
469
+ // measured on the identical scale (a live message awaiting reply is this
470
+ // focus's stakes-bearing moment, and already weighs into the score).
468
471
  const facetMeta: LLMCallMeta = {
469
472
  category: 'executive',
470
473
  attribute: 'facet',
471
- function: this._currentFocus?.function ?? 'facet',
474
+ // A focus that declares no function is making its decision call, the
475
+ // facet's analogue of master's 'decision'. This previously fell back to
476
+ // 'facet' — an *attribute* value, which quietly created a bogus bucket
477
+ // in the by-function cost breakdown. The typed axes caught it.
478
+ function: this._currentFocus?.function ?? 'decision',
472
479
  scope: this.facetId,
480
+ demand: processSelection.effortScore,
473
481
  }
474
482
  const result = this._chunkHandler
475
483
  ? await this._llmDirector.callStream( systemPrompt, userMessage, currentState.tick, this._chunkHandler, undefined, facetMeta )
@@ -45,6 +45,7 @@
45
45
  */
46
46
 
47
47
  import type { ReadonlySimulationState } from '#core/types'
48
+ import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
48
49
  import type { ExecutiveSummarizer } from '#llm/summarizer'
49
50
  import type { ExecutiveContext, PendingMessage, IdeationCandidate } from '#faculties/executive.engine/types'
50
51
  import { buildExecutiveContext, type ContextDependencies } from '#faculties/executive.engine/context'
@@ -158,7 +159,7 @@ export interface FocusSection {
158
159
  * into the facet's LLM calls as `LLMCallMeta.function` so the TokenTracker can
159
160
  * break spend down per facet type. Defaults to 'facet' when unset.
160
161
  */
161
- function?: string
162
+ function?: LLMCallFunction
162
163
  /**
163
164
  * Optional: Custom output format to append instead of the standard executive format.
164
165
  * Pass via PromptBuildOptions.outputFormat when building the user message.
@@ -9,6 +9,8 @@ import {
9
9
  type TokenUsage,
10
10
  type TokenLedgerRecord,
11
11
  type RecordUsageInput,
12
+ type PriceTable,
13
+ type ModelPrice,
12
14
  } from '#cognition/utilities/token.tracker'
13
15
 
14
16
  import { EnergyRegulator, type EnergyRegulatorConfig } from '#faculties/energy.regulator'
@@ -84,6 +86,8 @@ export {
84
86
  type TokenUsage,
85
87
  type TokenLedgerRecord,
86
88
  type RecordUsageInput,
89
+ type PriceTable,
90
+ type ModelPrice,
87
91
 
88
92
  // ── Regulatory Engines ─────────────────────────────────────
89
93
 
@@ -12,6 +12,10 @@
12
12
  */
13
13
 
14
14
  import type { TokenTracker } from '#cognition/utilities/token.tracker'
15
+ import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
16
+
17
+ /** Embedding is only ever a read or a write. */
18
+ export type EmbedFunction = Extract<LLMCallFunction, 'recall' | 'index'>
15
19
 
16
20
  export interface EmbeddingProvider {
17
21
  readonly modelName: string
@@ -55,7 +59,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
55
59
  /**
56
60
  * Per-Will token tracker. When provided, each embedding call records its
57
61
  * input-token usage under the 'embedding' category so memory-vector spend is
58
- * visible alongside LLM spend instead of being a silent COGS leak.
62
+ * visible alongside LLM spend instead of being a silent cost leak.
59
63
  */
60
64
  tokenTracker?: TokenTracker | null
61
65
  } ){
@@ -68,7 +72,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
68
72
  this._tokenTracker = config.tokenTracker ?? null
69
73
  }
70
74
 
71
- async embed( content: unknown, fn: string = 'recall'): Promise<number[]> {
75
+ async embed( content: unknown, fn: EmbedFunction = 'recall'): Promise<number[]> {
72
76
  let response: Response
73
77
  try {
74
78
  response = await fetch(`${this._apiUrl}/embeddings`, {
@@ -128,7 +132,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
128
132
  return embedding
129
133
  }
130
134
 
131
- async embedBatch( contents: unknown[], fn: string = 'index'): Promise<number[][]> {
135
+ async embedBatch( contents: unknown[], fn: EmbedFunction = 'index'): Promise<number[][]> {
132
136
  // Bounded fan-out: cap concurrent requests at _maxConcurrency instead of
133
137
  // firing all of them at once (FN16), while preserving input order.
134
138
  const results: number[][] = new Array( contents.length )
@@ -170,7 +174,7 @@ export class MockEmbedder implements EmbeddingProvider {
170
174
  this._seed = seed
171
175
  }
172
176
 
173
- async embed( content: unknown, _fn: string = 'recall'): Promise<number[]> {
177
+ async embed( content: unknown, _fn: EmbedFunction = 'recall'): Promise<number[]> {
174
178
  const str = typeof content === 'string' ? content : JSON.stringify( content )
175
179
  const hash = this._hashString( str )
176
180
  const embedding: number[] = []
@@ -184,7 +188,7 @@ export class MockEmbedder implements EmbeddingProvider {
184
188
  return embedding
185
189
  }
186
190
 
187
- async embedBatch( contents: unknown[], fn: string = 'index'): Promise<number[][]> {
191
+ async embedBatch( contents: unknown[], fn: EmbedFunction = 'index'): Promise<number[][]> {
188
192
  return Promise.all( contents.map( c => this.embed( c, fn ) ) )
189
193
  }
190
194