@alvin0/ai-agent-sdk-core 0.1.0 → 0.1.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.
- package/dist/agent/define/session/model-config.js +1 -1
- package/dist/agent/define/session/model-config.js.map +1 -1
- package/dist/agent/define/session/types.d.ts +21 -0
- package/dist/agent/define/session/types.d.ts.map +1 -1
- package/dist/agent/define/session.d.ts +2 -0
- package/dist/agent/define/session.d.ts.map +1 -1
- package/dist/agent/define/session.js +1 -1
- package/dist/agent/define/session.js.map +1 -1
- package/dist/agent/history/validation.js +1 -1
- package/dist/agent/history/validation.js.map +1 -1
- package/dist/agent/loop/events.d.ts +5 -0
- package/dist/agent/loop/events.d.ts.map +1 -1
- package/dist/agent/loop/schedule.js +1 -1
- package/dist/agent/loop/schedule.js.map +1 -1
- package/dist/agent/loop/turn/model-round.js +1 -1
- package/dist/agent/loop/turn/model-round.js.map +1 -1
- package/dist/agent/loop/turn/types.d.ts +2 -0
- package/dist/agent/loop/turn/types.d.ts.map +1 -1
- package/dist/agent/loop/turn/validation.js +1 -1
- package/dist/agent/loop/turn/validation.js.map +1 -1
- package/dist/agent/memory/compaction.d.ts +10 -1
- package/dist/agent/memory/compaction.d.ts.map +1 -1
- package/dist/agent/memory/compaction.js +2 -2
- package/dist/agent/memory/compaction.js.map +1 -1
- package/dist/agent/memory/token-estimator.js +1 -1
- package/dist/agent/memory/token-estimator.js.map +1 -1
- package/dist/agent/mode/run-agent.js +1 -1
- package/dist/agent/mode/run-agent.js.map +1 -1
- package/dist/composition/agent/options.js +1 -1
- package/dist/composition/agent/options.js.map +1 -1
- package/dist/composition/agent/session.js +1 -1
- package/dist/composition/agent/session.js.map +1 -1
- package/dist/composition/agent/types.d.ts +43 -1
- package/dist/composition/agent/types.d.ts.map +1 -1
- package/dist/composition/common/errors.d.ts.map +1 -1
- package/dist/composition/common/errors.js +1 -1
- package/dist/composition/common/errors.js.map +1 -1
- package/dist/composition/embedding/activation.js +2 -0
- package/dist/composition/embedding/activation.js.map +1 -0
- package/dist/composition/embedding/cache.js +2 -0
- package/dist/composition/embedding/cache.js.map +1 -0
- package/dist/composition/embedding/definition.d.ts +7 -0
- package/dist/composition/embedding/definition.d.ts.map +1 -0
- package/dist/composition/embedding/definition.js +2 -0
- package/dist/composition/embedding/definition.js.map +1 -0
- package/dist/composition/embedding/handle.js +2 -0
- package/dist/composition/embedding/handle.js.map +1 -0
- package/dist/composition/embedding/limiter.js +2 -0
- package/dist/composition/embedding/limiter.js.map +1 -0
- package/dist/composition/embedding/manager.js +2 -0
- package/dist/composition/embedding/manager.js.map +1 -0
- package/dist/composition/embedding/observation.js +2 -0
- package/dist/composition/embedding/observation.js.map +1 -0
- package/dist/composition/embedding/planner.js +2 -0
- package/dist/composition/embedding/planner.js.map +1 -0
- package/dist/composition/embedding/plugin-types.d.ts +48 -0
- package/dist/composition/embedding/plugin-types.d.ts.map +1 -0
- package/dist/composition/embedding/plugin-types.js +2 -0
- package/dist/composition/embedding/plugin-types.js.map +1 -0
- package/dist/composition/embedding/preflight.js +2 -0
- package/dist/composition/embedding/preflight.js.map +1 -0
- package/dist/composition/embedding/registry.js +2 -0
- package/dist/composition/embedding/registry.js.map +1 -0
- package/dist/composition/embedding/retry.js +2 -0
- package/dist/composition/embedding/retry.js.map +1 -0
- package/dist/composition/embedding/usage.js +2 -0
- package/dist/composition/embedding/usage.js.map +1 -0
- package/dist/composition/lifecycle/types.d.ts +5 -1
- package/dist/composition/lifecycle/types.d.ts.map +1 -1
- package/dist/composition/lifecycle/types.js +1 -1
- package/dist/composition/lifecycle/types.js.map +1 -1
- package/dist/composition/preflight.js +1 -1
- package/dist/composition/preflight.js.map +1 -1
- package/dist/composition/provider/activation.js +1 -1
- package/dist/composition/provider/activation.js.map +1 -1
- package/dist/composition/provider/model-selection.js +1 -1
- package/dist/composition/provider/model-selection.js.map +1 -1
- package/dist/composition/provider/preflight.js +1 -1
- package/dist/composition/provider/preflight.js.map +1 -1
- package/dist/composition/runtime/owner.js +1 -1
- package/dist/composition/runtime/owner.js.map +1 -1
- package/dist/composition/runtime/public.js +1 -1
- package/dist/composition/runtime/public.js.map +1 -1
- package/dist/composition/runtime/types.d.ts +25 -2
- package/dist/composition/runtime/types.d.ts.map +1 -1
- package/dist/composition/startup.js +1 -1
- package/dist/composition/startup.js.map +1 -1
- package/dist/composition/team/options.js +1 -1
- package/dist/composition/team/options.js.map +1 -1
- package/dist/composition/team/runtime.js +1 -1
- package/dist/composition/team/runtime.js.map +1 -1
- package/dist/composition/team/types.d.ts +3 -0
- package/dist/composition/team/types.d.ts.map +1 -1
- package/dist/contract/generate-options.d.ts +2 -0
- package/dist/contract/generate-options.d.ts.map +1 -1
- package/dist/contract/model-info.d.ts +12 -2
- package/dist/contract/model-info.d.ts.map +1 -1
- package/dist/embedding/adapter.d.ts +104 -0
- package/dist/embedding/adapter.d.ts.map +1 -0
- package/dist/embedding/adapter.js +2 -0
- package/dist/embedding/adapter.js.map +1 -0
- package/dist/embedding/catalog.d.ts +82 -0
- package/dist/embedding/catalog.d.ts.map +1 -0
- package/dist/embedding/catalog.js +2 -0
- package/dist/embedding/catalog.js.map +1 -0
- package/dist/embedding/errors.d.ts +98 -0
- package/dist/embedding/errors.d.ts.map +1 -0
- package/dist/embedding/errors.js +2 -0
- package/dist/embedding/errors.js.map +1 -0
- package/dist/embedding/handle.d.ts +76 -0
- package/dist/embedding/handle.d.ts.map +1 -0
- package/dist/embedding/limits.d.ts +47 -0
- package/dist/embedding/limits.d.ts.map +1 -0
- package/dist/embedding/limits.js +2 -0
- package/dist/embedding/limits.js.map +1 -0
- package/dist/embedding/profile.d.ts +89 -0
- package/dist/embedding/profile.d.ts.map +1 -0
- package/dist/embedding/profile.js +2 -0
- package/dist/embedding/profile.js.map +1 -0
- package/dist/embedding/purpose.d.ts +37 -0
- package/dist/embedding/purpose.d.ts.map +1 -0
- package/dist/embedding/request.d.ts +54 -0
- package/dist/embedding/request.d.ts.map +1 -0
- package/dist/embedding/request.js +2 -0
- package/dist/embedding/request.js.map +1 -0
- package/dist/embedding/result.d.ts +50 -0
- package/dist/embedding/result.d.ts.map +1 -0
- package/dist/embedding/usage.d.ts +64 -0
- package/dist/embedding/usage.d.ts.map +1 -0
- package/dist/embedding/usage.js +2 -0
- package/dist/embedding/usage.js.map +1 -0
- package/dist/embedding/validation.d.ts +68 -0
- package/dist/embedding/validation.d.ts.map +1 -0
- package/dist/embedding/validation.js +2 -0
- package/dist/embedding/validation.js.map +1 -0
- package/dist/embedding.d.ts +12 -0
- package/dist/embedding.js +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +1 -1
- package/dist/message/content.d.ts +71 -1
- package/dist/message/content.d.ts.map +1 -1
- package/dist/message/projection.d.ts +28 -2
- package/dist/message/projection.d.ts.map +1 -1
- package/dist/message/projection.js +1 -1
- package/dist/message/projection.js.map +1 -1
- package/dist/observability/bus.js +1 -1
- package/dist/observation/event.d.ts +1 -1
- package/dist/observation/event.d.ts.map +1 -1
- package/dist/observation/event.js.map +1 -1
- package/dist/observation/port.d.ts +1 -1
- package/dist/observation/port.d.ts.map +1 -1
- package/dist/observation/port.js.map +1 -1
- package/dist/observation/privacy.js +1 -1
- package/dist/observation/privacy.js.map +1 -1
- package/dist/observation/report.d.ts +2 -0
- package/dist/observation/report.d.ts.map +1 -1
- package/dist/observation/report.js.map +1 -1
- package/dist/primitives/version.d.ts +1 -1
- package/dist/primitives/version.js +1 -1
- package/dist/primitives/version.js.map +1 -1
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/model-call-handle.js +1 -1
- package/dist/runtime/model-call-handle.js.map +1 -1
- package/dist/runtime/model-metadata.js +1 -1
- package/dist/runtime/model-metadata.js.map +1 -1
- package/dist/runtime/model-stream.js +1 -1
- package/dist/runtime/model-stream.js.map +1 -1
- package/dist/runtime/registry.js +1 -1
- package/dist/runtime/with-retry.d.ts.map +1 -1
- package/dist/runtime/with-retry.js +1 -1
- package/dist/runtime/with-retry.js.map +1 -1
- package/dist/stream/assembler.d.ts.map +1 -1
- package/dist/stream/assembler.js +1 -1
- package/dist/stream/assembler.js.map +1 -1
- package/dist/stream/chunk.d.ts +6 -0
- package/dist/stream/chunk.d.ts.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limiter.js","names":[],"sources":["../../../src/composition/embedding/limiter.ts"],"sourcesContent":["/**\n * Bounded-concurrency driver for `Physical_Batch`es of one `Logical_Call`.\n *\n * The whole point of this file is a memory bound. `planEmbeddingBatches()` is a\n * lazy generator, so a batch payload only exists once it has been pulled out of\n * it. If a caller drained that generator into an array first, peak payload\n * memory would scale with the corpus and Requirement 17.4 would be lost. So the\n * runner here NEVER drains its source: it holds one shared iterator and pulls\n * exactly one more batch each time a worker slot frees up. At most\n * `concurrency` batches are materialised at any instant, which is what makes\n * peak payload memory `concurrency × maxBytes` (Requirements 4.5, 17.4).\n *\n * The runner is generic over the batch type on purpose. It only needs \"an\n * ordered source of work\" and knows nothing about batching, retry, or vectors —\n * retry and order restoration live in `retry.ts` and write results by\n * `item.index`, so this file has no opinion about either.\n *\n * @module ai-agent-sdk/core/composition/embedding/limiter\n */\n\nimport { EMBEDDING_ERROR_CODES, EmbeddingError } from '../../embedding/errors.ts'\n\n/**\n * In-flight `Physical_Batch`es allowed when a handle declares no `concurrency`.\n *\n * Small on purpose: the default has to be safe for the smallest deployment that\n * will ever run it, and a caller who knows their rate limit can raise it.\n */\nexport const DEFAULT_EMBEDDING_CONCURRENCY = 4\n\n/**\n * Decides the in-flight bound for one `Logical_Call`.\n *\n * `undefined` means \"not configured\" and takes {@link DEFAULT_EMBEDDING_CONCURRENCY}.\n * Anything else that is not a positive integer is a configuration mistake, not\n * a value to quietly repair: silently turning `concurrency: 0` into `4` would\n * hide a caller bug behind traffic the caller did not expect to send.\n */\nexport function resolveEmbeddingConcurrency(configured?: number): number {\n if (configured === undefined) return DEFAULT_EMBEDDING_CONCURRENCY\n if (!Number.isInteger(configured) || configured <= 0) {\n throw new EmbeddingError(\n 'embedding concurrency must be a positive integer',\n EMBEDDING_ERROR_CODES.CONFIGURATION_INVALID,\n )\n }\n return configured\n}\n\n/** How a batch is executed once the runner has granted it a slot. */\nexport type BatchRunner<Batch> = (batch: Batch, ordinal: number) => Promise<void> | void\n\n/** Knobs for {@link runBatchesWithConcurrency}. */\nexport interface ConcurrencyLimitOptions {\n /** Upper bound on simultaneously in-flight batches; resolved per {@link resolveEmbeddingConcurrency}. */\n readonly concurrency?: number\n /**\n * Caller signal or runtime `close()`.\n *\n * Once aborted, no further batch is pulled or started. Batches already in\n * flight are left to settle — cancelling them is the transport's job, not the\n * scheduler's (Requirement 12.3).\n */\n readonly signal?: AbortSignal\n}\n\n/** What the runner observed, so the caller can decide what an abort means. */\nexport interface ConcurrencyLimitOutcome {\n /** Batches that were handed to the runner function. */\n readonly started: number\n /** `true` when the source still had work but the signal stopped the runner. */\n readonly aborted: boolean\n}\n\n/**\n * Drives `source` with at most `concurrency` batches in flight.\n *\n * Accepts a sync or async iterable so a lazy `Generator` composes directly.\n * Pulls are serialised — an async iterator may not have two `next()` calls\n * outstanding — while the work itself overlaps.\n *\n * Failure is fail-fast on the scheduling side only: the first rejection stops\n * new batches from starting, the runner still awaits the batches already in\n * flight, and then that first error is rethrown. Recording a batch as failed\n * versus retryable is `retry.ts`'s decision, so a `run` that resolves is simply\n * \"this slot is free again\".\n *\n * An abort is NOT thrown here. The runner reports it in\n * {@link ConcurrencyLimitOutcome.aborted} and lets the caller choose between\n * partial results and an `EMBEDDING_ABORTED` failure, because only the caller\n * knows whether any batch had already succeeded.\n */\nexport async function runBatchesWithConcurrency<Batch>(\n source: Iterable<Batch> | AsyncIterable<Batch>,\n run: BatchRunner<Batch>,\n options?: ConcurrencyLimitOptions,\n): Promise<ConcurrencyLimitOutcome> {\n const limit = resolveEmbeddingConcurrency(options?.concurrency)\n const signal = options?.signal\n const isAborted = (): boolean => signal?.aborted === true\n const iterator = openIterator(source)\n\n let started = 0\n let aborted = false\n let exhausted = false\n let failure: { readonly error: unknown } | undefined\n /** Serialises `next()` so no two pulls overlap on an async iterator. */\n let pull: Promise<void> = Promise.resolve()\n\n const worker = async (): Promise<void> => {\n for (;;) {\n if (failure !== undefined || exhausted) return\n if (isAborted()) {\n aborted = true\n return\n }\n\n // Claim the next batch under the pull lock: one item, one owner, and the\n // source is advanced exactly as far as there is capacity to run it.\n const claimed = pull.then(async (): Promise<Claim<Batch> | undefined> => {\n if (failure !== undefined || exhausted || isAborted()) return undefined\n const next = await iterator.next()\n // Cancellation or another worker's failure can happen during an async pull.\n if (isAborted()) {\n aborted = true\n return undefined\n }\n if (failure !== undefined) return undefined\n if (next.done === true) {\n exhausted = true\n return undefined\n }\n const claim: Claim<Batch> = { batch: next.value }\n return claim\n })\n pull = claimed.then(() => undefined, () => undefined)\n\n let work: Claim<Batch> | undefined\n try {\n work = await claimed\n } catch (error) {\n failure ??= { error }\n return\n }\n if (work === undefined) continue\n if (failure !== undefined) return\n if (isAborted()) {\n aborted = true\n return\n }\n\n try {\n await run(work.batch, started++)\n } catch (error) {\n failure ??= { error }\n return\n }\n }\n }\n\n const workers: Promise<void>[] = []\n for (let slot = 0; slot < limit; slot += 1) workers.push(worker())\n await Promise.all(workers)\n\n if (failure !== undefined) {\n await closeIterator(iterator)\n throw failure.error\n }\n if (aborted || !exhausted) {\n await closeIterator(iterator)\n return { started, aborted: aborted || !exhausted }\n }\n return { started, aborted: false }\n}\n\n/** One batch a worker owns for the duration of its slot. */\ninterface Claim<Batch> {\n readonly batch: Batch\n}\n\n/** Normalises a sync or async iterable to one async-shaped iterator. */\nfunction openIterator<Batch>(\n source: Iterable<Batch> | AsyncIterable<Batch>,\n): AsyncIterator<Batch> | Iterator<Batch> {\n const asAsync = (source as AsyncIterable<Batch>)[Symbol.asyncIterator]\n if (typeof asAsync === 'function') return asAsync.call(source)\n const asSync = (source as Iterable<Batch>)[Symbol.iterator]\n if (typeof asSync === 'function') return asSync.call(source)\n throw new EmbeddingError(\n 'embedding batch source must be iterable',\n EMBEDDING_ERROR_CODES.CONFIGURATION_INVALID,\n )\n}\n\n/**\n * Lets an abandoned generator run its `finally` blocks.\n *\n * A lazy planner may hold a buffer for the batch it was building; leaving the\n * generator suspended forever keeps that alive, which is the same leak this\n * file exists to prevent.\n */\nasync function closeIterator<Batch>(\n iterator: AsyncIterator<Batch> | Iterator<Batch>,\n): Promise<void> {\n try {\n await iterator.return?.(undefined)\n } catch {\n // A source that fails while closing has nothing left to tell us; the\n // scheduling outcome already reported is the honest answer.\n }\n}\n"],"mappings":"sFAsCA,SAAgB,EAA4B,EAA6B,CACvE,GAAI,IAAe,IAAA,GAAW,MAAA,GAC9B,GAAI,CAAC,OAAO,UAAU,CAAU,GAAK,GAAc,EACjD,MAAM,IAAI,EACR,mDACA,EAAsB,qBACxB,EAEF,OAAO,CACT,CA6CA,eAAsB,EACpB,EACA,EACA,EACkC,CAClC,IAAM,EAAQ,EAA4B,GAAS,WAAW,EACxD,EAAS,GAAS,OAClB,MAA2B,GAAQ,UAAY,GAC/C,EAAW,EAAa,CAAM,EAEhC,EAAU,EACV,EAAU,GACV,EAAY,GACZ,EAEA,EAAsB,QAAQ,QAAQ,EAEpC,EAAS,SAA2B,CACxC,OAAS,CACP,GAAI,IAAY,IAAA,IAAa,EAAW,OACxC,GAAI,EAAU,EAAG,CACf,EAAU,GACV,MACF,CAIA,IAAM,EAAU,EAAK,KAAK,SAA+C,CACvE,GAAI,IAAY,IAAA,IAAa,GAAa,EAAU,EAAG,OACvD,IAAM,EAAO,MAAM,EAAS,KAAK,EAEjC,GAAI,EAAU,EAAG,CACf,EAAU,GACV,MACF,CACI,OAAY,IAAA,GAChB,IAAI,EAAK,OAAS,GAAM,CACtB,EAAY,GACZ,MACF,CAEA,MAAO,CADuB,MAAO,EAAK,KAC/B,CAFX,CAGF,CAAC,EACD,EAAO,EAAQ,SAAW,IAAA,OAAiB,IAAA,EAAS,EAEpD,IAAI,EACJ,GAAI,CACF,EAAO,MAAM,CACf,OAAS,EAAO,CACd,IAAY,CAAE,OAAM,EACpB,MACF,CACI,OAAS,IAAA,GACb,IAAI,IAAY,IAAA,GAAW,OAC3B,GAAI,EAAU,EAAG,CACf,EAAU,GACV,MACF,CAEA,GAAI,CACF,MAAM,EAAI,EAAK,MAAO,GAAS,CACjC,OAAS,EAAO,CACd,IAAY,CAAE,OAAM,EACpB,MACF,CAX2B,CAY7B,CACF,EAEM,EAA2B,CAAC,EAClC,IAAK,IAAI,EAAO,EAAG,EAAO,EAAO,GAAQ,EAAG,EAAQ,KAAK,EAAO,CAAC,EAGjE,GAFA,MAAM,QAAQ,IAAI,CAAO,EAErB,IAAY,IAAA,GAEd,MADA,MAAM,EAAc,CAAQ,EACtB,EAAQ,MAMhB,OAJI,GAAW,CAAC,GACd,MAAM,EAAc,CAAQ,EACrB,CAAE,UAAS,QAAS,GAAW,CAAC,CAAU,GAE5C,CAAE,UAAS,QAAS,EAAM,CACnC,CAQA,SAAS,EACP,EACwC,CACxC,IAAM,EAAW,EAAgC,OAAO,eACxD,GAAI,OAAO,GAAY,WAAY,OAAO,EAAQ,KAAK,CAAM,EAC7D,IAAM,EAAU,EAA2B,OAAO,UAClD,GAAI,OAAO,GAAW,WAAY,OAAO,EAAO,KAAK,CAAM,EAC3D,MAAM,IAAI,EACR,0CACA,EAAsB,qBACxB,CACF,CASA,eAAe,EACb,EACe,CACf,GAAI,CACF,MAAM,EAAS,SAAS,IAAA,EAAS,CACnC,MAAQ,CAGR,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{EMBEDDING_ERROR_CODES as e,EmbeddingError as t}from"../../embedding/errors.js";import{createEmbeddingModelHandle as n}from"./handle.js";function r(n){return new t(n,e.CONFIGURATION_INVALID)}function i(e,t){if(typeof e!=`string`||e.length===0)throw r(`\`embeddingModel()\` requires a non-empty \`${t}\``);return e}function a(e){if(typeof e!=`object`||!e)throw r("`embeddingModel()` requires an options object");let t=e,n=t.fallback;return Object.freeze({provider:i(t.provider,`provider`),model:i(t.model,`model`),...t.dimensions===void 0?{}:{dimensions:t.dimensions},...t.truncation===void 0?{}:{truncation:t.truncation},...t.expectedSpace===void 0?{}:{expectedSpace:t.expectedSpace},...t.concurrency===void 0?{}:{concurrency:t.concurrency},...t.batchLimits===void 0?{}:{batchLimits:o(t.batchLimits)},...t.cache===void 0?{}:{cache:s(t.cache)},...t.compatibilityIdentity===void 0?{}:{compatibilityIdentity:i(t.compatibilityIdentity,`compatibilityIdentity`)},...n===void 0?{}:{fallback:c(n)}})}function o(e){if(typeof e!=`object`||!e)throw r("`batchLimits` must be an object when it is provided");return Object.freeze({...e})}function s(e){if(typeof e!=`object`||!e)throw r("`cache` must be an object when it is provided");return Object.freeze({...e})}function c(e){if(!Array.isArray(e))throw r("`fallback` must be an array when it is provided");return Object.freeze(e.map(e=>{if(typeof e!=`object`||!e)throw r("each `fallback` entry must be an object");return Object.freeze({...e})}))}var l=class{registry;operations;context;observation;resource;maxCachedHandles;handles=new Map;storeIds=new WeakMap;storeSequence=0;unsubscribe;disposed=!1;constructor(e){this.registry=e.registry,this.operations=e.operations,this.context=e.context,this.observation=e.observation,this.resource=e.resource,this.maxCachedHandles=u(e.options?.maxCachedHandles),this.unsubscribe=this.registry.onAdaptersUpdated(()=>this.handles.clear())}model(e){this.operations.assertActive();let t=a(e),r=this.cacheKey(t);if(r!==void 0){let e=this.handles.get(r);if(e!==void 0)return this.handles.delete(r),this.handles.set(r,e),e}let i=this.registry.resolve(t.provider,t.model),o=n({operations:this.operations,adapter:i.adapter,options:t,...i.retryPolicy===void 0?{}:{retryPolicy:i.retryPolicy},...this.context===void 0?{}:{context:this.context},...this.observation===void 0?{}:{observation:this.observation},...this.resource===void 0?{}:{resource:this.resource}});return r!==void 0&&this.remember(r,o),o}get cachedHandleCount(){return this.handles.size}dispose(){this.disposed||(this.disposed=!0,this.unsubscribe(),this.handles.clear())}remember(e,t){for(this.handles.set(e,t);this.handles.size>this.maxCachedHandles;){let e=this.handles.keys().next();if(e.done===!0)break;this.handles.delete(e.value)}}cacheKey(e){if(this.maxCachedHandles===0)return;let t=this.cacheComponent(e.cache);if(t===void 0)return;let n=this.limitsComponent(e.batchLimits);if(n===void 0)return;let r=this.fallbackComponent(e.fallback);if(r!==void 0&&d(e.dimensions,`number`)&&d(e.truncation,`string`)&&d(e.expectedSpace,`string`)&&d(e.concurrency,`number`))return JSON.stringify([e.provider,e.model,e.dimensions??null,e.truncation??null,e.expectedSpace??null,e.concurrency??null,n,t,e.compatibilityIdentity??null,r])}limitsComponent(e){if(e===void 0)return null;if(!d(e.maxItems,`number`)||!d(e.maxTokens,`number`)||!d(e.maxBytes,`number`))return;let t=e.estimateTokens;if(t===void 0||typeof t==`function`)return[e.maxItems??null,e.maxTokens??null,e.maxBytes??null,t===void 0?null:this.referenceId(t)]}cacheComponent(e){if(e===void 0)return null;if(typeof e.scope!=`string`)return;let t=e.store;if(!(t===null||typeof t!=`object`&&typeof t!=`function`))return[e.scope,this.referenceId(t)]}fallbackComponent(e){if(e===void 0)return null;let t=[];for(let n of e){if(typeof n.model!=`string`||typeof n.compatibilityIdentity!=`string`)return;t.push([n.model,n.compatibilityIdentity])}return t}referenceId(e){let t=this.storeIds.get(e);if(t!==void 0)return t;let n=`ref-${++this.storeSequence}`;return this.storeIds.set(e,n),n}};function u(e){if(e===void 0)return 64;if(!Number.isInteger(e)||e<0)throw r("`maxCachedHandles` must be a non-negative integer");return e}function d(e,t){return e===void 0||typeof e===t}export{l as RuntimeEmbedding};
|
|
2
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","names":[],"sources":["../../../src/composition/embedding/manager.ts"],"sourcesContent":["/**\n * `RuntimeEmbedding`: the runtime-owned door between `runtime.embeddingModel()`\n * and one {@link EmbeddingModelHandle}.\n *\n * This manager is the embedding counterpart of `RuntimeModelCatalog`: one\n * instance per runtime, owned by `RuntimeCompositionOwner`, holding the two\n * things that are per-runtime rather than per-call — the {@link EmbeddingRegistry}\n * it resolves through, and a bounded cache of the handles it has already handed\n * out. It deliberately owns nothing else. Batching, concurrency, retry, usage and\n * order restoration all live behind `createEmbeddingModelHandle()`; resolution by\n * route + model lives in the registry.\n *\n * Three properties are the whole point of the module:\n *\n * 1. **Synchronous, and no agent anywhere.** `model()` performs an admission\n * check, captures the options, resolves an adapter and constructs a handle.\n * There is no `await`, no session, no agent, no team — a document indexing\n * service never pays for machinery it does not use (Requirements 3.1, 3.4).\n * 2. **Failures happen at the call that caused them.** A closing runtime, an\n * unusable options object, and a route with no embedding adapter are all\n * rejected by `model()` itself rather than at the first `embed()`. A missing\n * adapter surfaces as the registry's `EMBEDDING_ADAPTER_MISSING`, including\n * when the route carries only a generation adapter (Requirement 3.5).\n * 3. **The handle cache never outlives the topology it was resolved against.**\n * A handle closes over the adapter and retry policy that were live when it was\n * built, so any registration, `replace()` or disposal clears the cache. The\n * alternative — reusing a handle bound to an adapter that has been torn down —\n * would keep dispatching into a dead registration.\n *\n * The cache is keyed by the FULL captured configuration, not by route + model:\n * two handles that differ in `dimensions`, `truncation`, `expectedSpace`,\n * concurrency, batch limits, cache scope/store or fallback group are different\n * handles, and collapsing them would silently serve a caller a configuration it\n * did not ask for. Where a configuration cannot be fingerprinted safely, the\n * manager simply does not cache it and builds a fresh handle instead — a cache\n * miss is always correct, a wrong hit never is.\n *\n * @module ai-agent-sdk/core/composition/embedding/manager\n */\n\nimport type { EmbeddingCacheOptions, EmbeddingModelHandle } from '../../embedding/handle.ts'\nimport { EMBEDDING_ERROR_CODES, EmbeddingError } from '../../embedding/errors.ts'\nimport type { ResolvedEmbeddingBatchLimits } from '../../embedding/limits.ts'\nimport type { ObservationResource } from '../../observation/event.ts'\nimport type { ObservationPort } from '../../observation/port.ts'\nimport type { ModelInvocationContext } from '../../observation/report.ts'\nimport {\n createEmbeddingModelHandle,\n type EmbeddingFallbackDeclaration,\n type EmbeddingHandleOptions,\n type EmbeddingOperationScheduler,\n} from './handle.ts'\nimport type { EmbeddingRegistration, EmbeddingRegistry } from './registry.ts'\n\n/** Default upper bound on remembered handles; see {@link RuntimeEmbeddingOptions}. */\nconst DEFAULT_MAX_CACHED_HANDLES = 64\n\n/**\n * Runtime admission as this manager needs it: the scheduler the handle will use,\n * plus the close-state gate `model()` applies before doing any work.\n *\n * Structural rather than `RuntimeOperations` so a test can drive the manager with\n * a two-method stub, and so nothing here can reach into runtime lifecycle\n * internals it has no business touching.\n */\nexport interface EmbeddingOperationAdmission extends EmbeddingOperationScheduler {\n /**\n * Throws `RUNTIME_CLOSING` / `RUNTIME_CLOSED` once close has begun\n * (Requirement 12.6).\n */\n assertActive(): void\n}\n\n/**\n * The registry surface the manager consumes: resolution, and notification when\n * the embedding topology changed.\n */\nexport type EmbeddingAdapterResolver = Pick<EmbeddingRegistry, 'resolve' | 'onAdaptersUpdated'>\n\n/** Everything {@link RuntimeEmbedding} needs, all owned by the runtime. */\nexport interface RuntimeEmbeddingDependencies {\n /** Runtime-owned embedding topology, resolved per route + model id. */\n readonly registry: EmbeddingAdapterResolver\n /** Runtime admission; also supplies the lease each `Logical_Call` runs under. */\n readonly operations: EmbeddingOperationAdmission\n /**\n * Invocation context handed to every handle this manager creates, forwarded\n * untouched to attempt accounting.\n */\n readonly context?: ModelInvocationContext\n /**\n * Runtime observation port, used by a handle whose call carries no context of\n * its own. Without it an embedding call would run correctly but silently, so\n * the runtime forwards its own port here (Requirement 16.1).\n */\n readonly observation?: ObservationPort\n /** Runtime resource identity, paired with {@link observation}. */\n readonly resource?: ObservationResource\n readonly options?: RuntimeEmbeddingOptions\n}\n\n/** Tuning of the manager itself; every field has a usable default. */\nexport interface RuntimeEmbeddingOptions {\n /**\n * Upper bound on remembered handles, evicting least recently used first.\n *\n * Bounded because the key includes caller-supplied strings: an application\n * that varies `expectedSpace` or a cache scope per tenant would otherwise grow\n * this map without limit for the lifetime of the runtime. `0` disables caching.\n */\n readonly maxCachedHandles?: number\n}\n\nfunction configurationError(message: string): EmbeddingError {\n return new EmbeddingError(message, EMBEDDING_ERROR_CODES.CONFIGURATION_INVALID)\n}\n\n/** A non-empty string, or a configuration failure naming the field. */\nfunction requiredIdentifier(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.length === 0) {\n throw configurationError(`\\`embeddingModel()\\` requires a non-empty \\`${field}\\``)\n }\n return value\n}\n\n/**\n * Copies the caller's options into a frozen snapshot of its own.\n *\n * Two reasons this is a copy and not a pass-through. A remembered handle would\n * otherwise share a mutable object with the caller, so a later mutation would\n * change the configuration of a handle that was already validated. And `model()`\n * is the earliest point at which route identity can be checked at all, which is\n * where Requirement 3.1's \"fails at the call that made it\" comes from.\n *\n * Everything beyond route identity is left to the owners that already validate\n * it: `resolveEmbeddingConcurrency()`, `resolveEmbeddingCache()` and the fallback\n * group check, all reached through `createEmbeddingModelHandle()`.\n */\nfunction captureHandleOptions(value: unknown): EmbeddingHandleOptions {\n if (value === null || typeof value !== 'object') {\n throw configurationError('`embeddingModel()` requires an options object')\n }\n const source = value as Partial<EmbeddingHandleOptions>\n const fallback = source.fallback\n return Object.freeze<EmbeddingHandleOptions>({\n provider: requiredIdentifier(source.provider, 'provider'),\n model: requiredIdentifier(source.model, 'model'),\n ...(source.dimensions === undefined ? {} : { dimensions: source.dimensions }),\n ...(source.truncation === undefined ? {} : { truncation: source.truncation }),\n ...(source.expectedSpace === undefined ? {} : { expectedSpace: source.expectedSpace }),\n ...(source.concurrency === undefined ? {} : { concurrency: source.concurrency }),\n ...(source.batchLimits === undefined\n ? {}\n : { batchLimits: capturedBatchLimits(source.batchLimits) }),\n ...(source.cache === undefined ? {} : { cache: capturedCache(source.cache) }),\n ...(source.compatibilityIdentity === undefined\n ? {}\n : { compatibilityIdentity: requiredIdentifier(source.compatibilityIdentity, 'compatibilityIdentity') }),\n ...(fallback === undefined ? {} : { fallback: capturedFallback(fallback) }),\n })\n}\n\n/** Shallow frozen copy; value validation belongs to `resolveBatchLimits()`. */\nfunction capturedBatchLimits(\n value: Partial<ResolvedEmbeddingBatchLimits>,\n): Partial<ResolvedEmbeddingBatchLimits> {\n if (value === null || typeof value !== 'object') {\n throw configurationError('`batchLimits` must be an object when it is provided')\n }\n return Object.freeze({ ...value })\n}\n\n/**\n * Shallow frozen copy. The `store` reference is kept as given — it is the\n * caller's live cache, not data to clone — while `scope` is validated by\n * `resolveEmbeddingCache()`.\n */\nfunction capturedCache(value: EmbeddingCacheOptions): EmbeddingCacheOptions {\n if (value === null || typeof value !== 'object') {\n throw configurationError('`cache` must be an object when it is provided')\n }\n return Object.freeze({ ...value })\n}\n\n/**\n * Frozen copy of a declared fallback group.\n *\n * Only the shape needed to make the copy is checked here; whether the group is a\n * legitimate one — every member declaring the SAME `compatibilityIdentity` — is\n * the handle's decision, so that exactly one place answers Requirement 6.7.\n */\nfunction capturedFallback(\n value: readonly EmbeddingFallbackDeclaration[],\n): readonly EmbeddingFallbackDeclaration[] {\n if (!Array.isArray(value)) {\n throw configurationError('`fallback` must be an array when it is provided')\n }\n return Object.freeze(value.map(entry => {\n if (entry === null || typeof entry !== 'object') {\n throw configurationError('each `fallback` entry must be an object')\n }\n return Object.freeze({ ...entry })\n }))\n}\n\n/**\n * Runtime-owned factory and cache of {@link EmbeddingModelHandle}s.\n *\n * Construct one per runtime, beside the {@link EmbeddingRegistry} it resolves\n * through. Present even when a runtime carries zero embedding plugins: an empty\n * registry resolves nothing and `model()` fails with `EMBEDDING_ADAPTER_MISSING`,\n * which is a far better answer than a missing manager (Requirement 11.8).\n */\nexport class RuntimeEmbedding {\n private readonly registry: EmbeddingAdapterResolver\n private readonly operations: EmbeddingOperationAdmission\n private readonly context: ModelInvocationContext | undefined\n private readonly observation: ObservationPort | undefined\n private readonly resource: ObservationResource | undefined\n private readonly maxCachedHandles: number\n /** Insertion-ordered, re-inserted on hit, so the first key is the LRU one. */\n private readonly handles = new Map<string, EmbeddingModelHandle>()\n /** Identity of caller-supplied cache stores, so two stores never share a key. */\n private readonly storeIds = new WeakMap<object, string>()\n private storeSequence = 0\n private readonly unsubscribe: () => void\n private disposed = false\n\n constructor(dependencies: RuntimeEmbeddingDependencies) {\n this.registry = dependencies.registry\n this.operations = dependencies.operations\n this.context = dependencies.context\n this.observation = dependencies.observation\n this.resource = dependencies.resource\n this.maxCachedHandles = resolveMaxCachedHandles(dependencies.options?.maxCachedHandles)\n // A remembered handle closes over one adapter and one retry policy. The\n // moment the topology changes, every one of them is potentially stale.\n this.unsubscribe = this.registry.onAdaptersUpdated(() => this.handles.clear())\n }\n\n /**\n * Resolve one route + model into a handle, synchronously.\n *\n * @param optionsValue - the caller's {@link EmbeddingHandleOptions}; validated here.\n * @returns a handle bound to the adapter and retry policy live at this moment.\n * @throws {import('../../errors/agent-sdk-error.ts').AgentSdkError} `RUNTIME_CLOSING`\n * / `RUNTIME_CLOSED` when the runtime is no longer admitting work.\n * @throws {EmbeddingError} `EMBEDDING_CONFIGURATION_INVALID` for an unusable\n * options object, or `EMBEDDING_ADAPTER_MISSING` when no embedding adapter\n * claims the route + model pair.\n */\n model(optionsValue: unknown): EmbeddingModelHandle {\n // FIRST, before any option is read: a closing runtime must not hand out a\n // handle at all, not even one that would fail later (Requirement 12.6).\n this.operations.assertActive()\n const options = captureHandleOptions(optionsValue)\n const key = this.cacheKey(options)\n if (key !== undefined) {\n const cached = this.handles.get(key)\n if (cached !== undefined) {\n // Re-insert to mark it most recently used.\n this.handles.delete(key)\n this.handles.set(key, cached)\n return cached\n }\n }\n\n const registration: EmbeddingRegistration = this.registry.resolve(options.provider, options.model)\n const handle = createEmbeddingModelHandle({\n operations: this.operations,\n adapter: registration.adapter,\n options,\n // Captured at REGISTRATION time by the registry; absent means the SDK\n // defaults apply, which is not the same as \"no retry\".\n ...(registration.retryPolicy === undefined ? {} : { retryPolicy: registration.retryPolicy }),\n ...(this.context === undefined ? {} : { context: this.context }),\n ...(this.observation === undefined ? {} : { observation: this.observation }),\n ...(this.resource === undefined ? {} : { resource: this.resource }),\n })\n if (key !== undefined) this.remember(key, handle)\n return handle\n }\n\n /** Remembered handle count; exists for tests and diagnostics, not for callers. */\n get cachedHandleCount(): number { return this.handles.size }\n\n /**\n * Release the topology subscription and drop every remembered handle.\n *\n * Idempotent, and safe to call while handles are still in flight: a handle owns\n * its own lease, so dropping the manager's reference cancels nothing.\n */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.unsubscribe()\n this.handles.clear()\n }\n\n /** Insert under the bound, evicting the least recently used key first. */\n private remember(key: string, handle: EmbeddingModelHandle): void {\n this.handles.set(key, handle)\n while (this.handles.size > this.maxCachedHandles) {\n const oldest = this.handles.keys().next()\n if (oldest.done === true) break\n this.handles.delete(oldest.value)\n }\n }\n\n /**\n * The full configuration as one string, or `undefined` when this configuration\n * must not be cached.\n *\n * `JSON.stringify` over a FIXED-LENGTH array is what makes this safe: component\n * order is positional rather than derived from key iteration, and string\n * escaping is the serializer's problem, so no two distinct configurations can\n * join into the same key.\n *\n * A component of an unexpected type yields `undefined` rather than a coerced\n * key. Such a configuration is on its way to being rejected by the handle\n * anyway, and guessing a key for it is the one thing that could produce a wrong\n * hit later.\n */\n private cacheKey(options: EmbeddingHandleOptions): string | undefined {\n if (this.maxCachedHandles === 0) return undefined\n const cache = this.cacheComponent(options.cache)\n if (cache === undefined) return undefined\n const limits = this.limitsComponent(options.batchLimits)\n if (limits === undefined) return undefined\n const fallback = this.fallbackComponent(options.fallback)\n if (fallback === undefined) return undefined\n if (!isOptionalPrimitive(options.dimensions, 'number')) return undefined\n if (!isOptionalPrimitive(options.truncation, 'string')) return undefined\n if (!isOptionalPrimitive(options.expectedSpace, 'string')) return undefined\n if (!isOptionalPrimitive(options.concurrency, 'number')) return undefined\n\n return JSON.stringify([\n options.provider,\n options.model,\n options.dimensions ?? null,\n options.truncation ?? null,\n options.expectedSpace ?? null,\n options.concurrency ?? null,\n limits,\n cache,\n options.compatibilityIdentity ?? null,\n fallback,\n ])\n }\n\n /**\n * Batch limits as a positionally fixed tuple.\n *\n * `estimateTokens` is a function, so it is identified by reference the same way\n * a cache store is: two callers passing different estimators must not share a\n * handle whose batching would then use the wrong one.\n */\n private limitsComponent(\n limits: Partial<ResolvedEmbeddingBatchLimits> | undefined,\n ): readonly unknown[] | null | undefined {\n if (limits === undefined) return null\n if (!isOptionalPrimitive(limits.maxItems, 'number')) return undefined\n if (!isOptionalPrimitive(limits.maxTokens, 'number')) return undefined\n if (!isOptionalPrimitive(limits.maxBytes, 'number')) return undefined\n const estimate = limits.estimateTokens\n if (estimate !== undefined && typeof estimate !== 'function') return undefined\n return [\n limits.maxItems ?? null,\n limits.maxTokens ?? null,\n limits.maxBytes ?? null,\n estimate === undefined ? null : this.referenceId(estimate),\n ]\n }\n\n /**\n * Cache configuration as `[scope, storeReferenceId]`.\n *\n * The store enters the key by REFERENCE, never by shape: two stores with an\n * identical scope are still two different caches, and reusing a handle across\n * them would write vectors into a store the caller did not name.\n */\n private cacheComponent(\n cache: EmbeddingCacheOptions | undefined,\n ): readonly unknown[] | null | undefined {\n if (cache === undefined) return null\n if (typeof cache.scope !== 'string') return undefined\n const store: unknown = cache.store\n if (store === null || (typeof store !== 'object' && typeof store !== 'function')) return undefined\n return [cache.scope, this.referenceId(store as object)]\n }\n\n /** Fallback group as a tuple per member, in declaration order. */\n private fallbackComponent(\n fallback: readonly EmbeddingFallbackDeclaration[] | undefined,\n ): readonly unknown[] | null | undefined {\n if (fallback === undefined) return null\n const rows: unknown[] = []\n for (const entry of fallback) {\n if (typeof entry.model !== 'string' || typeof entry.compatibilityIdentity !== 'string') {\n return undefined\n }\n rows.push([entry.model, entry.compatibilityIdentity])\n }\n return rows\n }\n\n /**\n * A stable per-manager id for one object reference.\n *\n * Held in a `WeakMap`, so remembering that a store was seen never keeps it\n * alive: the id disappears with the object it named.\n */\n private referenceId(value: object): string {\n const existing = this.storeIds.get(value)\n if (existing !== undefined) return existing\n const id = `ref-${++this.storeSequence}`\n this.storeIds.set(value, id)\n return id\n }\n}\n\n/** `undefined` takes the default; anything unusable is a configuration error. */\nfunction resolveMaxCachedHandles(configured: number | undefined): number {\n if (configured === undefined) return DEFAULT_MAX_CACHED_HANDLES\n if (!Number.isInteger(configured) || configured < 0) {\n throw configurationError('`maxCachedHandles` must be a non-negative integer')\n }\n return configured\n}\n\n/** Absent, or present with exactly the expected primitive type. */\nfunction isOptionalPrimitive(value: unknown, expected: 'number' | 'string'): boolean {\n return value === undefined || typeof value === expected\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{deepFreeze as e}from"../../primitives/freeze.js";import{SDK_VERSION as t}from"../../primitives/version.js";import{createObservationRunScope as n,createOperationId as r,isSpanId as i,isTraceId as a}from"../../observation/context.js";import{NOOP_OBSERVATION_PORT as o,createCoreSpan as s,snapshotObservationSpan as c}from"../../observation/port.js";import{validateUsageCounters as l}from"../../observation/usage.js";import{normalizeModelFailure as u}from"../../errors/failure.js";const d=Object.freeze({sdkName:`ai-agent-sdk`,sdkVersion:t,runtime:`unknown`});function f(e){let t=u(e);return Object.freeze({type:`EmbeddingError`,message:`embedding operation failed; inspect the stable code and request ID`,code:t.code,...t.status===void 0?{}:{status:t.status}})}function p(e){if(!(e===void 0||!a(e.traceId)||!i(e.spanId)||typeof e.runId!=`string`||e.runId.length===0))return Object.freeze({traceId:e.traceId,spanId:e.spanId,parentSpanId:e.parentSpanId===null||i(e.parentSpanId)?e.parentSpanId:null,runId:e.runId,...e.conversationId===void 0?{}:{conversationId:e.conversationId},...e.turnId===void 0?{}:{turnId:e.turnId},...e.sessionId===void 0?{}:{sessionId:e.sessionId}})}function m(e,t){try{let n=c(e.openSpan(t));if(n!==void 0&&n.correlation.runId===t.runId)return n}catch{}return s(t)}function h(t){let i=t.context?.observation??t.observation??o,a=t.context?.resource??t.resource??d,s=t.context?.scope??n(),c=t.context?.correlation?.runId;return{port:i,resource:a,scope:s,runId:typeof c==`string`&&c.length>0?c:r(),capture(t,n,o,c){try{let l=e({schemaVersion:1,eventId:r(),sequence:s.nextSequence(),name:t,phase:n,occurredAt:new Date().toISOString(),monotonicMs:s.monotonicMs(),priority:`critical`,resource:a,correlation:o,data:c});i.capture(l)}catch{}}}}function g(t,n,i){return async a=>{let o=i(),s=r(),c=new Date().toISOString(),u=t.scope.monotonicMs(),d=m(t.port,{name:`sdk.provider.attempt`,runId:t.runId,parent:n,correlation:{attemptId:s,...n.modelCallId===void 0?{}:{modelCallId:n.modelCallId}},startedAt:c,monotonicMs:u});t.capture(`sdk.provider.attempt`,`start`,d.correlation,{provider:a.provider,model:a.model,attemptNumber:o,method:a.method,origin:a.origin,dispatchState:`not-sent`});let f;return Object.freeze({attemptId:s,attemptNumber:o,traceparent:d.traceparent,end:n=>{if(f!==void 0)return f;let r=new Date().toISOString(),i=t.scope.monotonicMs(),p=l(n.reported,!0),m=n.dispatchState===`not-sent`?`not-applicable`:p.complete?`complete`:Object.keys(p.reported).length>0?`partial`:`missing`,h=Math.max(0,i-u);d.end(n.status,r,i);let g=n.providerRequestId===void 0?d.correlation:e({...d.correlation,providerRequestId:n.providerRequestId});return t.capture(`sdk.provider.attempt`,`end`,g,{status:n.status,durationMs:h,origin:a.origin,dispatchState:n.dispatchState,coverage:m,reported:{...p.reported},...n.httpStatus===void 0?{}:{httpStatus:n.httpStatus},...n.providerRequestId===void 0?{}:{providerRequestId:n.providerRequestId},...n.error===void 0?{}:{error:{...n.error}}}),f=e({attemptId:s,spanId:d.correlation.spanId,attemptNumber:o,status:n.status,startedAt:c,endedAt:r,durationMs:h,dispatchState:n.dispatchState,coverage:m,reported:p.reported,origin:a.origin,...n.httpStatus===void 0?{}:{httpStatus:n.httpStatus},...n.providerRequestId===void 0?{}:{providerRequestId:n.providerRequestId},...n.error===void 0?{}:{error:n.error}}),f}})}}function _(e,t={}){let n=h(t),i=r(),a=p(t.context?.correlation),o=new Date().toISOString(),s=n.scope.monotonicMs(),c=m(n.port,{name:`sdk.embedding.call`,runId:n.runId,...a===void 0?{}:{parent:a},correlation:{modelCallId:i,...a?.conversationId===void 0?{}:{conversationId:a.conversationId},...a?.sessionId===void 0?{}:{sessionId:a.sessionId}},startedAt:o,monotonicMs:s});n.capture(`sdk.embedding.call`,`start`,c.correlation,{route:e.route,model:e.model,purpose:e.purpose,itemCount:e.itemCount});let l=0,u=()=>++l,d=e=>{let r=t.context,i={observation:n.port,resource:n.resource,correlation:e,scope:n.scope,...r?.logger===void 0?{}:{logger:r.logger},...r?.declareProviderAttemptAccounting===void 0?{}:{declareProviderAttemptAccounting:r.declareProviderAttemptAccounting},...r?.recordProviderRetry===void 0?{}:{recordProviderRetry:r.recordProviderRetry}};return Object.freeze(r?.startProviderAttempt===void 0?{...i,startProviderAttempt:g(n,e,u)}:{...i,startProviderAttempt:r.startProviderAttempt})},_=d(c.correlation),v=!1;return Object.freeze({context:_,beginBatch(e){let t=new Date().toISOString(),r=n.scope.monotonicMs(),a=m(n.port,{name:`sdk.embedding.batch`,runId:n.runId,parent:c.correlation,correlation:{modelCallId:i},startedAt:t,monotonicMs:r});n.capture(`sdk.embedding.batch`,`start`,a.correlation,{batchIndex:e.batchIndex,itemCount:e.itemCount,byteCount:e.byteCount,estimatedTokens:e.estimatedTokens,...e.dimensions===void 0?{}:{dimensions:e.dimensions}});let o=d(a.correlation),s=!1;return Object.freeze({context:o,end(t,i){if(s)return;s=!0;let o=n.scope.monotonicMs(),c=new Date().toISOString();a.end(t,c,o),n.capture(`sdk.embedding.batch`,`end`,a.correlation,{batchIndex:e.batchIndex,status:t,durationMs:Math.max(0,o-r),itemCount:e.itemCount,...i===void 0?{}:{error:{...f(i)}}})}})},end(t){if(v)return;v=!0;let r=n.scope.monotonicMs(),i=new Date().toISOString();c.end(t.status,i,r),n.capture(`sdk.embedding.call`,`end`,c.correlation,{status:t.status,durationMs:Math.max(0,r-s),route:e.route,model:e.model,purpose:e.purpose,itemCount:e.itemCount,...t.spaceId===void 0?{}:{spaceId:t.spaceId},cacheHits:t.cacheHits,providerAttempts:t.providerAttempts,...t.error===void 0?{}:{error:{...f(t.error)}}})}})}export{_ as beginEmbeddingCallObservation,f as safeEmbeddingFailure};
|
|
2
|
+
//# sourceMappingURL=observation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observation.js","names":[],"sources":["../../../src/composition/embedding/observation.ts"],"sourcesContent":["/**\n * Three-level observation for one embedding `Logical_Call`.\n *\n * The three levels of the conceptual model each get their own record, because a\n * cost question (\"why did this call cost that much?\") cannot be answered from a\n * single flat number (Requirement 16.1):\n *\n * ```text\n * sdk.embedding.call route, model, purpose, itemCount, spaceId,\n * cacheHits, providerAttempts\n * └─ sdk.embedding.batch itemCount, byteCount, estimatedTokens, dimensions\n * └─ sdk.provider.attempt dispatchState, httpStatus,\n * providerRequestId, reported usage\n * ```\n *\n * The attempt level is NOT new machinery. It is the same\n * `context.startProviderAttempt` / `attempt.end` pair the generation path uses,\n * so an embedding retry shows up in the same ledger, with the same fields, as a\n * generation retry (Requirement 16.6). This module only supplies that pair when\n * the caller's context does not already have one, and parents the attempt under\n * the `Physical_Batch` that provoked it. A context that already performs attempt\n * accounting is forwarded untouched — two ledgers counting the same dispatch\n * would double-count the cost.\n *\n * What deliberately never reaches a record:\n *\n * - **Raw input content.** A batch is described by `itemCount`, `byteCount`,\n * `estimatedTokens` and `dimensions`. There is no field carrying text, and no\n * code path that could put one there (Requirement 16.4).\n * - **Raw vector values.** A vector is a lossy but real reconstruction of the\n * input, so it is treated as content too. The call record carries the\n * `Space_Id` the vectors live in, never an element of one.\n * - **Credentials and provider prose.** Every failure is reduced by\n * {@link safeEmbeddingFailure} to a stable code and an HTTP status, mirroring\n * `safeProviderFailure` in the transport. Headers never enter `packages/core`\n * at all: `Http_Transport` applies `redactHeaders` before anything it reports\n * crosses into `attempt.end` (Requirement 16.5).\n *\n * Observation is best-effort throughout. A backend that throws, returns a\n * mismatched span, or rejects an event degrades the trace and never the call:\n * an embedding request does not fail because a tracer did.\n *\n * @module ai-agent-sdk/core/composition/embedding/observation\n */\n\nimport type { EmbeddingSpaceId } from '../../embedding/profile.ts'\nimport type { EmbeddingPurpose } from '../../embedding/purpose.ts'\nimport { normalizeModelFailure } from '../../errors/failure.ts'\nimport {\n createObservationRunScope,\n createOperationId,\n isSpanId,\n isTraceId,\n type CorrelationContext,\n type ObservationRunScope,\n} from '../../observation/context.ts'\nimport type {\n ObservationEvent,\n ObservationEventName,\n ObservationResource,\n OperationStatus,\n SafeErrorRecord,\n} from '../../observation/event.ts'\nimport {\n createCoreSpan,\n NOOP_OBSERVATION_PORT,\n snapshotObservationSpan,\n type ObservationPort,\n type ObservationSpan,\n type OpenObservationSpanInput,\n} from '../../observation/port.ts'\nimport type {\n EndProviderAttemptInput,\n ModelInvocationContext,\n ProviderAttemptHandle,\n StartProviderAttemptInput,\n} from '../../observation/report.ts'\nimport {\n validateUsageCounters,\n type AttemptUsageReport,\n type UsageCoverage,\n} from '../../observation/usage.ts'\nimport { deepFreeze } from '../../primitives/freeze.ts'\nimport type { JsonObject } from '../../primitives/json.ts'\nimport { SDK_VERSION } from '../../primitives/version.ts'\n\n/** Resource identity used when neither the caller nor the runtime supplied one. */\nconst ANONYMOUS_RESOURCE: ObservationResource = Object.freeze({\n sdkName: 'ai-agent-sdk' as const,\n sdkVersion: SDK_VERSION,\n runtime: 'unknown' as const,\n})\n\n/**\n * Reduce any failure to what a trace record may carry.\n *\n * The core-side mirror of the transport's `safeProviderFailure`: a stable code\n * and, when known, an HTTP status. Provider prose is dropped rather than\n * truncated, because a response body is exactly the place a credential or a\n * fragment of the caller's document tends to be echoed back.\n */\nexport function safeEmbeddingFailure(error: unknown): SafeErrorRecord {\n const failure = normalizeModelFailure(error)\n return Object.freeze({\n type: 'EmbeddingError',\n message: 'embedding operation failed; inspect the stable code and request ID',\n code: failure.code,\n ...(failure.status === undefined ? {} : { status: failure.status }),\n })\n}\n\n/** Safe facts known about one `Logical_Call` before it is dispatched. */\nexport interface EmbeddingCallObservationFacts {\n /** Route the handle was created for. */\n readonly route: string\n readonly model: string\n readonly purpose: EmbeddingPurpose\n /** Inputs of the `Logical_Call`, cache hits included. */\n readonly itemCount: number\n}\n\n/** Safe facts describing one `Physical_Batch`; none of them is content. */\nexport interface EmbeddingBatchObservationFacts {\n /** Plan coordinate of the batch, in input order. */\n readonly batchIndex: number\n readonly itemCount: number\n /** UTF-8 size of the batch's text, as measured by the planner. */\n readonly byteCount: number\n readonly estimatedTokens: number\n /** Requested output dimensions, when the caller asked for a specific width. */\n readonly dimensions?: number\n}\n\n/** How one `Logical_Call` ended, in numbers only. */\nexport interface EmbeddingCallObservationTerminal {\n readonly status: OperationStatus\n /** Space the produced vectors live in; absent when the call never got that far. */\n readonly spaceId?: EmbeddingSpaceId\n /** Inputs served from cache, so a cache-shaped cost drop is visible. */\n readonly cacheHits: number\n /** `Provider_Attempt`s spent, retries included (Requirement 16.6). */\n readonly providerAttempts: number\n readonly error?: unknown\n}\n\n/** One `Physical_Batch` record, open until {@link end} is called. */\nexport interface EmbeddingBatchObservation {\n /**\n * Context to dispatch this batch with.\n *\n * Attempts started through it are parented under this batch, so retries of one\n * batch stay distinguishable from attempts on its siblings.\n */\n readonly context: ModelInvocationContext | undefined\n /** Closes the batch record. Repeat calls are ignored. */\n end(status: OperationStatus, error?: unknown): void\n}\n\n/** One `Logical_Call` record and the factory for its batch records. */\nexport interface EmbeddingCallObservation {\n /** Context for the call-level work: snapshot capture and cache lookup. */\n readonly context: ModelInvocationContext | undefined\n beginBatch(facts: EmbeddingBatchObservationFacts): EmbeddingBatchObservation\n /** Closes the call record. Repeat calls are ignored. */\n end(terminal: EmbeddingCallObservationTerminal): void\n}\n\n/** Where the records go, when anything is listening. */\nexport interface EmbeddingObservationDependencies {\n /** Caller context; its port, resource, correlation and scope win when present. */\n readonly context?: ModelInvocationContext\n /** Runtime default port, used when the context does not carry one. */\n readonly observation?: ObservationPort\n /** Runtime resource identity, used when the context does not carry one. */\n readonly resource?: ObservationResource\n}\n\n/** A caller correlation usable as a span parent, or nothing. */\nfunction validParent(value: Partial<CorrelationContext> | undefined): CorrelationContext | undefined {\n if (value === undefined || !isTraceId(value.traceId) || !isSpanId(value.spanId)\n || typeof value.runId !== 'string' || value.runId.length === 0) return undefined\n return Object.freeze({\n traceId: value.traceId,\n spanId: value.spanId,\n parentSpanId: value.parentSpanId === null || isSpanId(value.parentSpanId) ? value.parentSpanId : null,\n runId: value.runId,\n ...(value.conversationId === undefined ? {} : { conversationId: value.conversationId }),\n ...(value.turnId === undefined ? {} : { turnId: value.turnId }),\n ...(value.sessionId === undefined ? {} : { sessionId: value.sessionId }),\n })\n}\n\n/**\n * Open a span through the backend, falling back to a core span.\n *\n * The backend's span is snapshotted rather than used directly, and a span whose\n * identity does not match what was asked for is discarded: a trace with the\n * wrong parent is worse than a trace assembled locally.\n */\nfunction openSpanSafely(port: ObservationPort, input: OpenObservationSpanInput): ObservationSpan {\n try {\n const span = snapshotObservationSpan(port.openSpan(input))\n if (span !== undefined && span.correlation.runId === input.runId) return span\n } catch {\n // A tracer that throws is a degraded trace, never a failed embedding call.\n }\n return createCoreSpan(input)\n}\n\n/** Everything the three record levels share for the lifetime of one call. */\ninterface ObservationChannel {\n readonly port: ObservationPort\n readonly resource: ObservationResource\n readonly scope: ObservationRunScope\n readonly runId: string\n capture(name: ObservationEventName, phase: 'start' | 'end', correlation: CorrelationContext, data: JsonObject): void\n}\n\nfunction createChannel(dependencies: EmbeddingObservationDependencies): ObservationChannel {\n const port = dependencies.context?.observation ?? dependencies.observation ?? NOOP_OBSERVATION_PORT\n const resource = dependencies.context?.resource ?? dependencies.resource ?? ANONYMOUS_RESOURCE\n const scope = dependencies.context?.scope ?? createObservationRunScope()\n const suppliedRunId = dependencies.context?.correlation?.runId\n const runId = typeof suppliedRunId === 'string' && suppliedRunId.length > 0\n ? suppliedRunId\n : createOperationId()\n return {\n port,\n resource,\n scope,\n runId,\n capture(name, phase, correlation, data): void {\n try {\n const event: ObservationEvent = deepFreeze({\n schemaVersion: 1,\n eventId: createOperationId(),\n sequence: scope.nextSequence(),\n name,\n phase,\n occurredAt: new Date().toISOString(),\n monotonicMs: scope.monotonicMs(),\n priority: 'critical',\n resource,\n correlation,\n data,\n })\n port.capture(event)\n } catch {\n // Observation delivery is best-effort. The receipt is not inspected here\n // because an embedding call publishes no delivery summary to reconcile it\n // against, unlike a `ModelCallReport`.\n }\n },\n }\n}\n\n/**\n * Attempt accounting for one `Physical_Batch`.\n *\n * Produces the same `sdk.provider.attempt` start/end pair the generation ledger\n * produces, so an embedding retry is countable with the same tooling. Usage is\n * passed through `validateUsageCounters` rather than trusted: a malformed\n * provider report becomes a coverage downgrade, never a zero.\n */\nfunction createAttemptStarter(\n channel: ObservationChannel,\n parent: CorrelationContext,\n nextAttemptNumber: () => number,\n): (input: StartProviderAttemptInput, signal?: AbortSignal) => Promise<ProviderAttemptHandle> {\n return async (input: StartProviderAttemptInput): Promise<ProviderAttemptHandle> => {\n const attemptNumber = nextAttemptNumber()\n const attemptId = createOperationId()\n const startedAt = new Date().toISOString()\n const startedMonotonic = channel.scope.monotonicMs()\n const span = openSpanSafely(channel.port, {\n name: 'sdk.provider.attempt',\n runId: channel.runId,\n parent,\n correlation: {\n attemptId,\n ...(parent.modelCallId === undefined ? {} : { modelCallId: parent.modelCallId }),\n },\n startedAt,\n monotonicMs: startedMonotonic,\n })\n channel.capture('sdk.provider.attempt', 'start', span.correlation, {\n provider: input.provider,\n model: input.model,\n attemptNumber,\n method: input.method,\n // Origin only: a path or query string can carry identifiers the caller\n // considers content, and `StartProviderAttemptInput` already forbids them.\n origin: input.origin,\n dispatchState: 'not-sent',\n })\n\n let report: AttemptUsageReport | undefined\n const end = (terminal: EndProviderAttemptInput): AttemptUsageReport => {\n // Idempotent: `attempt.end` is called exactly once per attempt on every\n // exit path of the transport, and a second call must not invent a record.\n if (report !== undefined) return report\n const endedAt = new Date().toISOString()\n const endedMonotonic = channel.scope.monotonicMs()\n const validated = validateUsageCounters(terminal.reported, true)\n const coverage: UsageCoverage = terminal.dispatchState === 'not-sent'\n ? 'not-applicable'\n : validated.complete\n ? 'complete'\n : Object.keys(validated.reported).length > 0 ? 'partial' : 'missing'\n const durationMs = Math.max(0, endedMonotonic - startedMonotonic)\n span.end(terminal.status, endedAt, endedMonotonic)\n const correlation = terminal.providerRequestId === undefined\n ? span.correlation\n : deepFreeze({ ...span.correlation, providerRequestId: terminal.providerRequestId })\n channel.capture('sdk.provider.attempt', 'end', correlation, {\n status: terminal.status,\n durationMs,\n origin: input.origin,\n // Reported by the transport, never re-derived here (Requirement 4.8).\n dispatchState: terminal.dispatchState,\n coverage,\n reported: { ...validated.reported },\n ...(terminal.httpStatus === undefined ? {} : { httpStatus: terminal.httpStatus }),\n ...(terminal.providerRequestId === undefined ? {} : { providerRequestId: terminal.providerRequestId }),\n ...(terminal.error === undefined ? {} : { error: { ...terminal.error } }),\n })\n report = deepFreeze({\n attemptId,\n spanId: span.correlation.spanId,\n attemptNumber,\n status: terminal.status,\n startedAt,\n endedAt,\n durationMs,\n dispatchState: terminal.dispatchState,\n coverage,\n reported: validated.reported,\n origin: input.origin,\n ...(terminal.httpStatus === undefined ? {} : { httpStatus: terminal.httpStatus }),\n ...(terminal.providerRequestId === undefined ? {} : { providerRequestId: terminal.providerRequestId }),\n ...(terminal.error === undefined ? {} : { error: terminal.error }),\n })\n return report\n }\n return Object.freeze({ attemptId, attemptNumber, traceparent: span.traceparent, end })\n }\n}\n\n/**\n * Open the `Logical_Call` record and hand back the factory for its batches.\n *\n * Called once per `embed()` / `embedMany()`, before the configuration snapshot is\n * taken, so a call that is rejected pre-dispatch still produces one record\n * showing zero attempts — the cheapest possible answer to \"did this cost\n * anything?\".\n */\nexport function beginEmbeddingCallObservation(\n facts: EmbeddingCallObservationFacts,\n dependencies: EmbeddingObservationDependencies = {},\n): EmbeddingCallObservation {\n const channel = createChannel(dependencies)\n const callId = createOperationId()\n const parent = validParent(dependencies.context?.correlation)\n const startedAt = new Date().toISOString()\n const startedMonotonic = channel.scope.monotonicMs()\n const span = openSpanSafely(channel.port, {\n name: 'sdk.embedding.call',\n runId: channel.runId,\n ...(parent === undefined ? {} : { parent }),\n correlation: {\n // Reuses the `modelCallId` slot: an embedding call IS the provider-facing\n // logical operation of this trace, and reusing the slot keeps existing\n // correlation tooling working without widening `CorrelationContext`.\n modelCallId: callId,\n ...(parent?.conversationId === undefined ? {} : { conversationId: parent.conversationId }),\n ...(parent?.sessionId === undefined ? {} : { sessionId: parent.sessionId }),\n },\n startedAt,\n monotonicMs: startedMonotonic,\n })\n channel.capture('sdk.embedding.call', 'start', span.correlation, {\n route: facts.route,\n model: facts.model,\n purpose: facts.purpose,\n itemCount: facts.itemCount,\n })\n\n // Numbered across the whole `Logical_Call`, not per batch, so the attempt\n // numbers line up with the `providerAttempts` total the result reports.\n let attemptCounter = 0\n const nextAttemptNumber = (): number => ++attemptCounter\n\n /**\n * Build the context handed to one dispatch level.\n *\n * A caller that already accounts for attempts keeps its own accounting: this\n * module supplies `startProviderAttempt` only when nothing else does.\n */\n const contextFor = (correlation: CorrelationContext): ModelInvocationContext => {\n const supplied = dependencies.context\n const base: ModelInvocationContext = {\n observation: channel.port,\n resource: channel.resource,\n correlation,\n scope: channel.scope,\n ...(supplied?.logger === undefined ? {} : { logger: supplied.logger }),\n ...(supplied?.declareProviderAttemptAccounting === undefined\n ? {}\n : { declareProviderAttemptAccounting: supplied.declareProviderAttemptAccounting }),\n ...(supplied?.recordProviderRetry === undefined\n ? {}\n : { recordProviderRetry: supplied.recordProviderRetry }),\n }\n return Object.freeze(supplied?.startProviderAttempt === undefined\n ? { ...base, startProviderAttempt: createAttemptStarter(channel, correlation, nextAttemptNumber) }\n : { ...base, startProviderAttempt: supplied.startProviderAttempt })\n }\n\n const callContext = contextFor(span.correlation)\n let ended = false\n\n return Object.freeze<EmbeddingCallObservation>({\n context: callContext,\n\n beginBatch(batch: EmbeddingBatchObservationFacts): EmbeddingBatchObservation {\n const batchStartedAt = new Date().toISOString()\n const batchStartedMonotonic = channel.scope.monotonicMs()\n const batchSpan = openSpanSafely(channel.port, {\n name: 'sdk.embedding.batch',\n runId: channel.runId,\n parent: span.correlation,\n correlation: { modelCallId: callId },\n startedAt: batchStartedAt,\n monotonicMs: batchStartedMonotonic,\n })\n // Size, not substance: four numbers that answer every cost and batching\n // question without carrying a byte of the caller's text (Requirement 16.4).\n channel.capture('sdk.embedding.batch', 'start', batchSpan.correlation, {\n batchIndex: batch.batchIndex,\n itemCount: batch.itemCount,\n byteCount: batch.byteCount,\n estimatedTokens: batch.estimatedTokens,\n ...(batch.dimensions === undefined ? {} : { dimensions: batch.dimensions }),\n })\n const batchContext = contextFor(batchSpan.correlation)\n let batchEnded = false\n return Object.freeze<EmbeddingBatchObservation>({\n context: batchContext,\n end(status: OperationStatus, error?: unknown): void {\n if (batchEnded) return\n batchEnded = true\n const endedMonotonic = channel.scope.monotonicMs()\n const endedAt = new Date().toISOString()\n batchSpan.end(status, endedAt, endedMonotonic)\n channel.capture('sdk.embedding.batch', 'end', batchSpan.correlation, {\n batchIndex: batch.batchIndex,\n status,\n durationMs: Math.max(0, endedMonotonic - batchStartedMonotonic),\n itemCount: batch.itemCount,\n ...(error === undefined ? {} : { error: { ...safeEmbeddingFailure(error) } }),\n })\n },\n })\n },\n\n end(terminal: EmbeddingCallObservationTerminal): void {\n if (ended) return\n ended = true\n const endedMonotonic = channel.scope.monotonicMs()\n const endedAt = new Date().toISOString()\n span.end(terminal.status, endedAt, endedMonotonic)\n channel.capture('sdk.embedding.call', 'end', span.correlation, {\n status: terminal.status,\n durationMs: Math.max(0, endedMonotonic - startedMonotonic),\n route: facts.route,\n model: facts.model,\n purpose: facts.purpose,\n itemCount: facts.itemCount,\n // The space the vectors live in, never a vector element.\n ...(terminal.spaceId === undefined ? {} : { spaceId: terminal.spaceId }),\n cacheHits: terminal.cacheHits,\n providerAttempts: terminal.providerAttempts,\n ...(terminal.error === undefined ? {} : { error: { ...safeEmbeddingFailure(terminal.error) } }),\n })\n },\n })\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{EMBEDDING_BATCH_DEFAULTS as e}from"../../embedding/limits.js";const t=new TextEncoder;function n(e){if(!(e===void 0||!Number.isInteger(e)||e<=0))return e}function r(e,n){let r=e.contentParts.map(e=>e.text).join(``);return{tokens:n(r),bytes:t.encode(r).byteLength}}function*i(t,i){let a=n(i.maxItems)??e.maxItems,o=n(i.maxTokens)??e.maxTokens,s=n(i.maxBytes)??e.maxBytes,c=i.estimateTokens,l=0,u=[],d=0,f=0;for(let e of t){let t=r(e,c);u.length>0&&(u.length+1>a||d+t.tokens>o||f+t.bytes>s)&&(yield{batchIndex:l,items:u,estimatedTokens:d,bytes:f},l+=1,u=[],d=0,f=0),u.push(e),d+=t.tokens,f+=t.bytes}u.length>0&&(yield{batchIndex:l,items:u,estimatedTokens:d,bytes:f})}export{i as planEmbeddingBatches};
|
|
2
|
+
//# sourceMappingURL=planner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planner.js","names":[],"sources":["../../../src/composition/embedding/planner.ts"],"sourcesContent":["/**\n * Lazy batch planner: splits one `Logical_Call` into `Physical_Batch` plans.\n *\n * The planner consumes {@link ResolvedEmbeddingBatchLimits} from\n * `embedding/limits.ts` and defines NO limit type of its own. That is what keeps\n * the dependency direction `composition/embedding/` → `embedding/` one-way\n * (DD-12, Requirements 1.6 and 19.6).\n *\n * @module ai-agent-sdk/core/composition/embedding/planner\n */\n\nimport {\n EMBEDDING_BATCH_DEFAULTS,\n type ResolvedEmbeddingBatchLimits,\n} from '../../embedding/limits.ts'\nimport type { EmbeddingItem } from '../../embedding/request.ts'\n\n/**\n * One planned `Physical_Batch`.\n *\n * `batchIndex` is the position of the batch in the plan, in input order. It is a\n * plan coordinate, not an item coordinate: item order is restored from\n * {@link EmbeddingItem.index}, which survives batching and retries.\n */\nexport interface EmbeddingBatchPlan {\n readonly batchIndex: number\n readonly items: readonly EmbeddingItem[]\n /** Estimated tokens of this batch, measured with `limits.estimateTokens`. */\n readonly estimatedTokens: number\n /** UTF-8 byte size of this batch's text. */\n readonly bytes: number\n}\n\nconst ENCODER = new TextEncoder()\n\n/** A usable positive integer bound, or `undefined` when there is none. */\nfunction usable(value: number | undefined): number | undefined {\n if (value === undefined || !Number.isInteger(value) || value <= 0) return undefined\n return value\n}\n\n/** Measured size of a single item, using the caller's shared estimator. */\nfunction measure(\n item: EmbeddingItem,\n estimateTokens: (text: string) => number,\n): { tokens: number; bytes: number } {\n const text = item.contentParts.map(part => part.text).join('')\n return { tokens: estimateTokens(text), bytes: ENCODER.encode(text).byteLength }\n}\n\n/**\n * Yields `Physical_Batch` plans in input order, closing the current batch as soon\n * as adding the next item would exceed ANY of the three limits. The three bounds\n * apply simultaneously, not as a choice of one (Requirement 4.4).\n *\n * Invariants: every batch is within every limit; every item appears exactly once\n * in exactly one batch; a batch is never empty.\n *\n * Deliberate exception: an item that alone exceeds `maxTokens` or `maxBytes`\n * still becomes a one-item batch. Cutting content is something the SDK does not\n * do — the provider rejects it and that error is the honest answer.\n *\n * Lazy by construction: at most one batch is held at a time, so peak memory is\n * bounded by the limits, not by the size of the corpus (Requirement 17.4).\n * `items` may be any iterable, so a streaming corpus is never materialised.\n */\nexport function* planEmbeddingBatches(\n items: Iterable<EmbeddingItem>,\n limits: ResolvedEmbeddingBatchLimits,\n): Generator<EmbeddingBatchPlan> {\n // A capability the catalog left `unknown` (or an unusable override) falls back\n // to EMBEDDING_BATCH_DEFAULTS: batching always needs a finite upper bound.\n const maxItems = usable(limits.maxItems) ?? EMBEDDING_BATCH_DEFAULTS.maxItems\n const maxTokens = usable(limits.maxTokens) ?? EMBEDDING_BATCH_DEFAULTS.maxTokens\n const maxBytes = usable(limits.maxBytes) ?? EMBEDDING_BATCH_DEFAULTS.maxBytes\n const estimateTokens = limits.estimateTokens\n\n let batchIndex = 0\n let current: EmbeddingItem[] = []\n let tokens = 0\n let bytes = 0\n\n for (const item of items) {\n const size = measure(item, estimateTokens)\n\n // Close on the FIRST limit the next item would breach. Only a non-empty\n // batch can be closed, which is what makes the one-item overflow batch the\n // single deliberate exception instead of an infinite loop.\n if (\n current.length > 0\n && (current.length + 1 > maxItems\n || tokens + size.tokens > maxTokens\n || bytes + size.bytes > maxBytes)\n ) {\n yield { batchIndex, items: current, estimatedTokens: tokens, bytes }\n batchIndex += 1\n current = []\n tokens = 0\n bytes = 0\n }\n\n current.push(item)\n tokens += size.tokens\n bytes += size.bytes\n }\n\n if (current.length > 0) {\n yield { batchIndex, items: current, estimatedTokens: tokens, bytes }\n }\n}\n"],"mappings":"qEAiCA,MAAM,EAAU,IAAI,YAGpB,SAAS,EAAO,EAA+C,CACzD,SAAU,IAAA,IAAa,CAAC,OAAO,UAAU,CAAK,GAAK,GAAS,GAChE,OAAO,CACT,CAGA,SAAS,EACP,EACA,EACmC,CACnC,IAAM,EAAO,EAAK,aAAa,IAAI,GAAQ,EAAK,IAAI,CAAC,CAAC,KAAK,EAAE,EAC7D,MAAO,CAAE,OAAQ,EAAe,CAAI,EAAG,MAAO,EAAQ,OAAO,CAAI,CAAC,CAAC,UAAW,CAChF,CAkBA,SAAiB,EACf,EACA,EAC+B,CAG/B,IAAM,EAAW,EAAO,EAAO,QAAQ,GAAK,EAAyB,SAC/D,EAAY,EAAO,EAAO,SAAS,GAAK,EAAyB,UACjE,EAAW,EAAO,EAAO,QAAQ,GAAK,EAAyB,SAC/D,EAAiB,EAAO,eAE1B,EAAa,EACb,EAA2B,CAAC,EAC5B,EAAS,EACT,EAAQ,EAEZ,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAO,EAAQ,EAAM,CAAc,EAMvC,EAAQ,OAAS,IACb,EAAQ,OAAS,EAAI,GACpB,EAAS,EAAK,OAAS,GACvB,EAAQ,EAAK,MAAQ,KAE1B,KAAM,CAAE,aAAY,MAAO,EAAS,gBAAiB,EAAQ,OAAM,EACnE,GAAc,EACd,EAAU,CAAC,EACX,EAAS,EACT,EAAQ,GAGV,EAAQ,KAAK,CAAI,EACjB,GAAU,EAAK,OACf,GAAS,EAAK,KAChB,CAEI,EAAQ,OAAS,IACnB,KAAM,CAAE,aAAY,MAAO,EAAS,gBAAiB,EAAQ,OAAM,EAEvE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SdkLogger } from "../../logging/types.js";
|
|
2
|
+
import { AdapterRegistrationHandle } from "../../plugin/provider-plugin.js";
|
|
3
|
+
import { ComposableModelProviderPlugin, ModelTarget } from "../provider/types.js";
|
|
4
|
+
import { EmbeddingAdapter } from "../../embedding/adapter.js";
|
|
5
|
+
//#region src/composition/embedding/plugin-types.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Contract version for the embedding plugin kind, independent of
|
|
8
|
+
* `PROVIDER_PLUGIN_API_VERSION`: a new kind starts at 1 rather than forcing the
|
|
9
|
+
* generation contract to move (Requirement 11.2).
|
|
10
|
+
*/
|
|
11
|
+
declare const EMBEDDING_PROVIDER_PLUGIN_API_VERSION: 1;
|
|
12
|
+
/** Host-side registrar handed to an embedding plugin's `setup()`. */
|
|
13
|
+
interface EmbeddingProviderRegistrar {
|
|
14
|
+
registerEmbeddingAdapter(routes: readonly string[], adapter: EmbeddingAdapter, models?: readonly string[]): AdapterRegistrationHandle;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An embedding provider plugin. Its `kind` is deliberately NOT
|
|
18
|
+
* `'model-provider-plugin'`, so a generation host never mistakes one for the
|
|
19
|
+
* other and existing generation plugins need no version bump
|
|
20
|
+
* (Requirements 11.1, 11.2).
|
|
21
|
+
*/
|
|
22
|
+
interface ComposableEmbeddingProviderPlugin {
|
|
23
|
+
readonly kind: 'embedding-provider-plugin';
|
|
24
|
+
readonly apiVersion: typeof EMBEDDING_PROVIDER_PLUGIN_API_VERSION;
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly displayName: string;
|
|
27
|
+
readonly family?: string;
|
|
28
|
+
readonly routes: readonly string[];
|
|
29
|
+
readonly defaultModel?: ModelTarget;
|
|
30
|
+
readonly setup: (registrar: EmbeddingProviderRegistrar) => void | (() => void);
|
|
31
|
+
}
|
|
32
|
+
/** Helper-only view whose registrations cannot escape predeclared route claims. */
|
|
33
|
+
interface ComposableEmbeddingProviderRegistrar {
|
|
34
|
+
readonly logger: SdkLogger;
|
|
35
|
+
registerEmbeddingAdapter(adapter: EmbeddingAdapter, options?: {
|
|
36
|
+
readonly routes?: readonly string[];
|
|
37
|
+
readonly models?: readonly string[];
|
|
38
|
+
}): AdapterRegistrationHandle;
|
|
39
|
+
}
|
|
40
|
+
type EmbeddingProviderPluginCleanupDefinition = () => undefined;
|
|
41
|
+
type EmbeddingProviderPluginDefinition = Omit<ComposableEmbeddingProviderPlugin, 'kind' | 'apiVersion' | 'setup'> & {
|
|
42
|
+
readonly setup: (registrar: ComposableEmbeddingProviderRegistrar) => undefined | EmbeddingProviderPluginCleanupDefinition;
|
|
43
|
+
};
|
|
44
|
+
/** `RuntimeOwnerOptions.providers` accepts either kind (Requirement 11.4). */
|
|
45
|
+
type ComposableRuntimeProviderPlugin = ComposableModelProviderPlugin | ComposableEmbeddingProviderPlugin;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { ComposableEmbeddingProviderPlugin, ComposableEmbeddingProviderRegistrar, ComposableRuntimeProviderPlugin, EMBEDDING_PROVIDER_PLUGIN_API_VERSION, EmbeddingProviderPluginCleanupDefinition, EmbeddingProviderPluginDefinition, EmbeddingProviderRegistrar };
|
|
48
|
+
//# sourceMappingURL=plugin-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-types.d.ts","names":[],"sources":["../../../src/composition/embedding/plugin-types.ts"],"mappings":";;;;;;;;;;cAUa;;UAGI;EACf,yBACE,2BACA,SAAS,kBACT,6BACC;;;;;;;;UASY;WACN;WACA,mBAAmB;WACnB;WACA;WACA;WACA;WACA,eAAe;WACf,QAAQ,WAAW;;;UAIb;WACN,QAAQ;EACjB,yBACE,SAAS,kBACT;aAAqB;aAAqC;MACzD;;KAGO;KACA,oCAAoC,KAC9C;WAGS,QACP,WAAW,qDACI;;;KAIP,kCACR,gCACA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-types.js","names":[],"sources":["../../../src/composition/embedding/plugin-types.ts"],"sourcesContent":["import type { EmbeddingAdapter } from '../../embedding/adapter.ts'\nimport type { SdkLogger } from '../../logging/types.ts'\nimport type { AdapterRegistrationHandle } from '../../plugin/provider-plugin.ts'\nimport type { ComposableModelProviderPlugin, ModelTarget } from '../provider/types.ts'\n\n/**\n * Contract version for the embedding plugin kind, independent of\n * `PROVIDER_PLUGIN_API_VERSION`: a new kind starts at 1 rather than forcing the\n * generation contract to move (Requirement 11.2).\n */\nexport const EMBEDDING_PROVIDER_PLUGIN_API_VERSION = 1 as const\n\n/** Host-side registrar handed to an embedding plugin's `setup()`. */\nexport interface EmbeddingProviderRegistrar {\n registerEmbeddingAdapter(\n routes: readonly string[],\n adapter: EmbeddingAdapter,\n models?: readonly string[],\n ): AdapterRegistrationHandle\n}\n\n/**\n * An embedding provider plugin. Its `kind` is deliberately NOT\n * `'model-provider-plugin'`, so a generation host never mistakes one for the\n * other and existing generation plugins need no version bump\n * (Requirements 11.1, 11.2).\n */\nexport interface ComposableEmbeddingProviderPlugin {\n readonly kind: 'embedding-provider-plugin'\n readonly apiVersion: typeof EMBEDDING_PROVIDER_PLUGIN_API_VERSION\n readonly id: string\n readonly displayName: string\n readonly family?: string\n readonly routes: readonly string[]\n readonly defaultModel?: ModelTarget\n readonly setup: (registrar: EmbeddingProviderRegistrar) => void | (() => void)\n}\n\n/** Helper-only view whose registrations cannot escape predeclared route claims. */\nexport interface ComposableEmbeddingProviderRegistrar {\n readonly logger: SdkLogger\n registerEmbeddingAdapter(\n adapter: EmbeddingAdapter,\n options?: { readonly routes?: readonly string[]; readonly models?: readonly string[] },\n ): AdapterRegistrationHandle\n}\n\nexport type EmbeddingProviderPluginCleanupDefinition = () => undefined\nexport type EmbeddingProviderPluginDefinition = Omit<\n ComposableEmbeddingProviderPlugin,\n 'kind' | 'apiVersion' | 'setup'\n> & {\n readonly setup: (\n registrar: ComposableEmbeddingProviderRegistrar,\n ) => undefined | EmbeddingProviderPluginCleanupDefinition\n}\n\n/** `RuntimeOwnerOptions.providers` accepts either kind (Requirement 11.4). */\nexport type ComposableRuntimeProviderPlugin =\n | ComposableModelProviderPlugin\n | ComposableEmbeddingProviderPlugin\n"],"mappings":"AAUA,MAAa,EAAwC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{arrayData as e,boundedText as t,capturedMethod as n,objectValue as r,ownData as i}from"../../capability/common/data.js";import{capabilityIdentityConflict as a}from"../../errors/capability-identity.js";import{COMPOSITION_LIMITS as o}from"../../capability/common/config.js";import{AgentRuntimeConstructionError as s,checkPreflightAbort as c,invalidPreflight as l}from"../common/errors.js";import"../provider/types.js";import{createProviderIdentityPlan as u,readProviderMetadata as d}from"../provider/preflight.js";import"./plugin-types.js";const f=new WeakMap,p=new WeakMap;function m(e,t,n){c(n);let r=_(e,n),i=[],a=[],o=new Map,s=new Map;for(let[e,t]of r.entries()){c(n);let r=v(t);if(r===void 0){i.push(b(e,`CAPABILITY_KIND_MISMATCH`,t));continue}if(!y(t,r)){i.push(b(e,`CAPABILITY_API_UNSUPPORTED`,t));continue}let l;try{l=d(t)}catch{n?.aborted===!0&&c(n),i.push(b(e,`CAPABILITY_STARTUP_FAILED`,t));continue}let u=o.get(l.id);if(u!==void 0){i.push(b(e,`CAPABILITY_ID_CONFLICT`,t,l.id,u));continue}o.set(l.id,e);let f=!1;for(let n of l.routes){let a=`${r}\u0000${n}`,o=s.get(a);if(o!==void 0){i.push(b(e,r===`generation`?`PROVIDER_ROUTE_CONFLICT`:`PROVIDER_OPERATION_CONFLICT`,t,l.id,o)),f=!0;break}s.set(a,e)}f||a.push({index:e,operation:r,source:t,metadata:l})}if(c(n),i.length>0)throw C(i);let f=t===void 0?void 0:S(t);if(f!==void 0&&!a.some(e=>e.metadata.defaultModel?.provider===f))throw l();c(n);let p=a.filter(e=>e.operation===`generation`),m=a.filter(e=>e.operation===`embedding`);return Object.freeze({generation:u(p.map(e=>e.metadata),p.map(e=>e.source),f),embedding:h(m.map(e=>e.metadata),m.map(e=>e.source))})}function h(e,t){let n=Object.freeze({providers:Object.freeze([...e])});return f.set(n,Object.freeze([...t])),n}function g(e,t){c(t);let r=p.get(e);if(r!==void 0)return r;let i=f.get(e);if(i===void 0)throw l();f.delete(e);try{let r=e.providers.map((e,r)=>{c(t);let a=n(i[r],`setup`);return Object.freeze({...e,setup:a})});c(t);let a=Object.freeze(r);return p.set(e,a),a}catch{throw c(t),l()}}function _(t,n){try{return e(t,o.providers).map(r)}catch{throw n?.aborted===!0&&c(n),l()}}function v(e){let t;try{t=i(e,`kind`,!1)}catch{return}if(t===`model-provider-plugin`)return`generation`;if(t===`embedding-provider-plugin`)return`embedding`}function y(e,t){let n;try{n=i(e,`apiVersion`,!1)}catch{return!1}return n===1}function b(e,t,n,r,i){let a=r??x(n);return Object.freeze({index:e,code:t,...a===void 0?{}:{pluginId:a},...i===void 0?{}:{conflictsWithIndex:i}})}function x(e){try{return t(i(e,`id`,!1),o.identityBytes)}catch{return}}function S(e){try{return t(e,o.identityBytes)}catch{throw l()}}function C(e){let t=e[0],n=w(t.code);return new s({failureCode:t.code,stage:`preflight`,reason:`invalid`,...n===void 0||t.conflictsWithIndex===void 0?{}:{conflict:a(n,t.conflictsWithIndex,t.index)},aggregate:e})}function w(e){if(e===`CAPABILITY_ID_CONFLICT`)return`provider-plugin-id`;if(e===`PROVIDER_ROUTE_CONFLICT`||e===`PROVIDER_OPERATION_CONFLICT`)return`provider-route`}export{g as captureEmbeddingMethods,h as createEmbeddingIdentityPlan,m as preflightRuntimeProviders};
|
|
2
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","names":[],"sources":["../../../src/composition/embedding/preflight.ts"],"sourcesContent":["/**\n * Whole-input startup preflight across both provider plugin kinds.\n *\n * The generation-only preflight fails fast on the first bad entry. Requirement\n * 11.6 asks for the opposite shape: scan the ENTIRE `providers` list, collect\n * every marker, apiVersion and route–operation failure, and only then decide.\n * That is what this module adds, and it is deliberately additive —\n * `preflightProviderIdentities()` keeps its fail-fast contract for callers that\n * validate one kind on its own.\n *\n * Two properties this file has to preserve:\n *\n * - Nothing executable is read. Every value comes from\n * {@link ownData}/{@link readProviderMetadata}, so a plugin's `setup` is never\n * touched here. Method capture happens later, and only when the sweep found\n * nothing, which is what makes \"plugins already `setup()` on failure = 0\" a\n * structural fact rather than a test result.\n * - The thrown error keeps `failureCode` equal to the FIRST failure's code, so\n * assertions written against fail-fast behaviour keep their meaning. The full\n * list travels in `aggregate` (DD-3).\n *\n * Duplicate detection is on the route–OPERATION pair. A generation plugin and an\n * embedding plugin may share a route (that is the whole point of resolution by\n * route + operation + model id), while two plugins of the same operation may not.\n *\n * @module ai-agent-sdk/core/composition/embedding/preflight\n */\n\nimport { COMPOSITION_LIMITS } from '../common/config.ts'\nimport { arrayData, boundedText, capturedMethod, objectValue, ownData } from '../common/data.ts'\nimport {\n AgentRuntimeConstructionError, checkPreflightAbort, invalidPreflight,\n type RuntimeConstructionAggregateEntry, type RuntimeConstructionFailureCode,\n} from '../common/errors.ts'\nimport { capabilityIdentityConflict } from '../identity/error.ts'\nimport {\n createProviderIdentityPlan, readProviderMetadata, type ProviderIdentityPlan,\n} from '../provider/preflight.ts'\nimport { PROVIDER_PLUGIN_API_VERSION, type ProviderMetadata } from '../provider/types.ts'\nimport {\n EMBEDDING_PROVIDER_PLUGIN_API_VERSION, type ComposableEmbeddingProviderPlugin,\n type EmbeddingProviderRegistrar,\n} from './plugin-types.ts'\n\n/** One failure found by the sweep, positioned in the caller's input list. */\nexport interface ProviderPreflightFailure extends RuntimeConstructionAggregateEntry {\n readonly index: number\n readonly code: Extract<\n RuntimeConstructionFailureCode,\n 'CAPABILITY_KIND_MISMATCH' | 'CAPABILITY_API_UNSUPPORTED' | 'PROVIDER_ROUTE_CONFLICT'\n | 'PROVIDER_OPERATION_CONFLICT' | 'CAPABILITY_ID_CONFLICT'\n // A malformed identity is not one of the five collectable classes, but the\n // sweep must not stop on it either; it keeps the generic code the fail-fast\n // path already reports for the same input.\n | 'CAPABILITY_STARTUP_FAILED'\n >\n /** Only when the id was readable as inert, bounded data. */\n readonly pluginId?: string\n readonly conflictsWithIndex?: number\n}\n\n/** Internal phase token for the embedding half, mirroring `ProviderIdentityPlan`. */\nexport interface EmbeddingIdentityPlan {\n readonly providers: readonly ProviderMetadata[]\n}\n\n/** An embedding plugin whose `setup` has been captured exactly once. */\nexport interface CapturedEmbeddingProvider extends ProviderMetadata {\n readonly setup: ComposableEmbeddingProviderPlugin['setup']\n}\n\n/** Both halves of one validated `providers` list. */\nexport interface RuntimeProviderPlan {\n readonly generation: ProviderIdentityPlan\n readonly embedding: EmbeddingIdentityPlan\n}\n\nconst sourcesByPlan = new WeakMap<EmbeddingIdentityPlan, readonly object[]>()\nconst methodsByPlan = new WeakMap<EmbeddingIdentityPlan, readonly CapturedEmbeddingProvider[]>()\n\n/** Which operation a plugin kind answers; route claims are namespaced by it. */\ntype Operation = 'generation' | 'embedding'\n\ninterface ScannedProvider {\n readonly index: number\n readonly operation: Operation\n readonly source: object\n readonly metadata: ProviderMetadata\n}\n\n/**\n * Validate an entire `providers` list without committing anything.\n *\n * @param input - the caller's `providers` value, of any shape.\n * @param defaultProvider - optional selected default route.\n * @param signal - startup signal; an abort wins over collected failures.\n * @throws AgentRuntimeConstructionError with `failureCode` of the first failure and\n * `aggregate` listing all of them.\n */\nexport function preflightRuntimeProviders(\n input: unknown,\n defaultProvider?: unknown,\n signal?: AbortSignal,\n): RuntimeProviderPlan {\n checkPreflightAbort(signal)\n const sources = readSources(input, signal)\n const failures: ProviderPreflightFailure[] = []\n const scanned: ScannedProvider[] = []\n const ids = new Map<string, number>()\n // Keyed by operation + route: the pair IS the namespace (Requirement 11.10).\n const routes = new Map<string, number>()\n\n for (const [index, source] of sources.entries()) {\n checkPreflightAbort(signal)\n const operation = operationOf(source)\n if (operation === undefined) {\n failures.push(failure(index, 'CAPABILITY_KIND_MISMATCH', source))\n continue\n }\n if (!supportedApiVersion(source, operation)) {\n failures.push(failure(index, 'CAPABILITY_API_UNSUPPORTED', source))\n continue\n }\n let metadata: ProviderMetadata\n try {\n metadata = readProviderMetadata(source)\n } catch {\n if (signal?.aborted === true) checkPreflightAbort(signal)\n failures.push(failure(index, 'CAPABILITY_STARTUP_FAILED', source))\n continue\n }\n const duplicateId = ids.get(metadata.id)\n if (duplicateId !== undefined) {\n failures.push(failure(index, 'CAPABILITY_ID_CONFLICT', source, metadata.id, duplicateId))\n continue\n }\n ids.set(metadata.id, index)\n let conflicted = false\n for (const route of metadata.routes) {\n const key = `${operation}\\u0000${route}`\n const first = routes.get(key)\n if (first !== undefined) {\n // A generation collision keeps its historical code; an embedding\n // collision is the new route–operation class.\n failures.push(failure(index,\n operation === 'generation' ? 'PROVIDER_ROUTE_CONFLICT' : 'PROVIDER_OPERATION_CONFLICT',\n source, metadata.id, first))\n conflicted = true\n break\n }\n routes.set(key, index)\n }\n if (conflicted) continue\n scanned.push({ index, operation, source, metadata })\n }\n\n checkPreflightAbort(signal)\n if (failures.length > 0) throw aggregateFailure(failures)\n\n const selected = defaultProvider === undefined ? undefined\n : boundedTextOrInvalid(defaultProvider)\n if (selected !== undefined\n && !scanned.some(entry => entry.metadata.defaultModel?.provider === selected)) {\n throw invalidPreflight()\n }\n checkPreflightAbort(signal)\n const generation = scanned.filter(entry => entry.operation === 'generation')\n const embedding = scanned.filter(entry => entry.operation === 'embedding')\n return Object.freeze({\n generation: createProviderIdentityPlan(\n generation.map(entry => entry.metadata), generation.map(entry => entry.source), selected,\n ),\n embedding: createEmbeddingIdentityPlan(\n embedding.map(entry => entry.metadata), embedding.map(entry => entry.source),\n ),\n })\n}\n\n/** Freeze an embedding plan from already validated metadata and retain its sources. */\nexport function createEmbeddingIdentityPlan(\n providers: readonly ProviderMetadata[],\n sources: readonly object[],\n): EmbeddingIdentityPlan {\n const plan: EmbeddingIdentityPlan = Object.freeze({ providers: Object.freeze([...providers]) })\n sourcesByPlan.set(plan, Object.freeze([...sources]))\n return plan\n}\n\n/**\n * Capture each embedding plugin's `setup` exactly once.\n *\n * Call only after the WHOLE input passed the sweep: reading a method is the first\n * observable interaction with a plugin object, so it must not happen while any\n * identity failure is still outstanding.\n */\nexport function captureEmbeddingMethods(\n plan: EmbeddingIdentityPlan,\n signal?: AbortSignal,\n): readonly CapturedEmbeddingProvider[] {\n checkPreflightAbort(signal)\n const captured = methodsByPlan.get(plan)\n if (captured !== undefined) return captured\n const sources = sourcesByPlan.get(plan)\n if (sources === undefined) throw invalidPreflight()\n // A failed capture is terminal too: never reread a partially observed table.\n sourcesByPlan.delete(plan)\n try {\n const result = plan.providers.map((provider, index) => {\n checkPreflightAbort(signal)\n const setup = capturedMethod<[EmbeddingProviderRegistrar], void | (() => void)>(\n sources[index]!, 'setup',\n )\n return Object.freeze({ ...provider, setup })\n })\n checkPreflightAbort(signal)\n const frozen = Object.freeze(result)\n methodsByPlan.set(plan, frozen)\n return frozen\n } catch {\n checkPreflightAbort(signal)\n throw invalidPreflight()\n }\n}\n\n/** The list itself is validated fail-fast: a malformed list has no per-entry failures to collect. */\nfunction readSources(input: unknown, signal?: AbortSignal): readonly object[] {\n try {\n return arrayData(input, COMPOSITION_LIMITS.providers).map(objectValue)\n } catch {\n if (signal?.aborted === true) checkPreflightAbort(signal)\n throw invalidPreflight()\n }\n}\n\n/** Marker comparison stays here so a foreign property-access throw is never rethrown. */\nfunction operationOf(source: object): Operation | undefined {\n let kind: unknown\n try {\n kind = ownData(source, 'kind', false)\n } catch {\n return undefined\n }\n if (kind === 'model-provider-plugin') return 'generation'\n if (kind === 'embedding-provider-plugin') return 'embedding'\n return undefined\n}\n\nfunction supportedApiVersion(source: object, operation: Operation): boolean {\n let apiVersion: unknown\n try {\n apiVersion = ownData(source, 'apiVersion', false)\n } catch {\n return false\n }\n return apiVersion === (operation === 'generation'\n ? PROVIDER_PLUGIN_API_VERSION\n : EMBEDDING_PROVIDER_PLUGIN_API_VERSION)\n}\n\n/**\n * Build one failure row.\n *\n * `pluginId` is best-effort by design: an entry that failed its marker check may\n * still expose a readable id, and support needs it, but a plugin object that\n * throws on reflection must not turn a collected failure into a thrown one.\n */\nfunction failure(\n index: number,\n code: ProviderPreflightFailure['code'],\n source: object,\n knownId?: string,\n conflictsWithIndex?: number,\n): ProviderPreflightFailure {\n const pluginId = knownId ?? readableId(source)\n return Object.freeze({\n index, code,\n ...(pluginId === undefined ? {} : { pluginId }),\n ...(conflictsWithIndex === undefined ? {} : { conflictsWithIndex }),\n })\n}\n\nfunction readableId(source: object): string | undefined {\n try {\n return boundedText(ownData(source, 'id', false), COMPOSITION_LIMITS.identityBytes)\n } catch {\n return undefined\n }\n}\n\nfunction boundedTextOrInvalid(value: unknown): string {\n try {\n return boundedText(value, COMPOSITION_LIMITS.identityBytes)\n } catch {\n throw invalidPreflight()\n }\n}\n\n/** `failureCode` is the first failure's code; every failure travels in `aggregate` (DD-3). */\nfunction aggregateFailure(\n failures: readonly ProviderPreflightFailure[],\n): AgentRuntimeConstructionError {\n const first = failures[0]!\n const namespace = conflictNamespace(first.code)\n return new AgentRuntimeConstructionError({\n failureCode: first.code, stage: 'preflight', reason: 'invalid',\n ...(namespace === undefined || first.conflictsWithIndex === undefined ? {} : {\n conflict: capabilityIdentityConflict(namespace, first.conflictsWithIndex, first.index),\n }),\n aggregate: failures,\n })\n}\n\nfunction conflictNamespace(\n code: ProviderPreflightFailure['code'],\n): 'provider-plugin-id' | 'provider-route' | undefined {\n if (code === 'CAPABILITY_ID_CONFLICT') return 'provider-plugin-id'\n if (code === 'PROVIDER_ROUTE_CONFLICT' || code === 'PROVIDER_OPERATION_CONFLICT') return 'provider-route'\n return undefined\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AgentSdkError as e}from"../../errors/agent-sdk-error.js";import{EMBEDDING_ERROR_CODES as t,EmbeddingError as n}from"../../embedding/errors.js";const r=t=>new e(t,`EMBEDDING_ROUTE_CONFLICT`),i=t=>new e(t,`EMBEDDING_REGISTRATION_INVALID`);function a(e,t){return{...typeof e==`string`&&e.length>0?{provider:e}:{},...typeof t==`string`&&t.length>0?{model:t}:{}}}var o=class{registrations=new Set;index=new Map;listeners=new Set;registerEmbeddingAdapter(t,n,r){let i=s(r),a={entries:[],released:!1};a.entries=this.prepareEntries(t,n,i,a),this.registrations.add(a),this.commit();let o=(()=>{a.released||(a.released=!0,this.registrations.delete(a),a.entries=[],this.commit())});return o.replace=t=>{if(a.released)throw new e(`a disposed embedding registration cannot replace its routes`,`EMBEDDING_REGISTRATION_DISPOSED`);let r=this.prepareEntries(t,n,i,a);a.entries=r,this.commit()},o}resolve(e,r){let i=typeof e==`string`?this.index.get(e):void 0,o=i===void 0?void 0:(typeof r==`string`?i.byModel.get(r):void 0)??i.wide;if(o===void 0)throw new n(`no embedding adapter is registered for route "${String(e)}" and model "${String(r)}"`,t.ADAPTER_MISSING,a(e,r));return Object.freeze({route:o.route,provider:{...o.provider},adapter:o.adapter,...o.retryPolicy===void 0?{}:{retryPolicy:o.retryPolicy},...o.models===void 0?{}:{models:Object.freeze([...o.models])}})}hasRoute(e){return typeof e==`string`&&this.index.has(e)}listRoutes(){return Object.freeze([...this.index.keys()])}onAdaptersUpdated(e){return this.listeners.add(e),()=>void this.listeners.delete(e)}prepareEntries(e,t,n,a){if(!Array.isArray(e)||e.length===0)throw i(`an embedding adapter must register at least one provider route`);if(typeof t!=`object`||!t||typeof t.embedBatch!=`function`)throw i(`an embedding registration requires an EmbeddingAdapter`);let o=new Set,s=[];for(let c of e){if(typeof c!=`string`||c.length===0)throw i(`embedding adapter provider route names must be non-empty strings`);if(o.has(c))throw r(`embedding route "${c}" is claimed twice by one registration`);this.checkVacancy(c,n,a);let e=t.providerInfo(c);if(typeof e!=`object`||!e||e.id!==c||typeof e.name!=`string`||e.name.length===0)throw i(`embedding adapter metadata for route "${c}" must preserve its id and carry a non-empty name`);let l=t.providerRetryPolicy(c);o.add(c),s.push(Object.freeze({route:c,provider:Object.freeze({id:e.id,name:e.name}),adapter:t,...l===void 0?{}:{retryPolicy:l},...n===void 0?{}:{models:n}}))}return Object.freeze(s)}checkVacancy(e,t,n){for(let i of this.registrations)if(i!==n){for(let n of i.entries)if(n.route===e){if(n.models===void 0&&t===void 0)throw r(`an embedding adapter for route "${e}" is already registered`);if(n.models!==void 0&&t!==void 0){for(let i of t)if(n.models.has(i))throw r(`an embedding adapter for route "${e}" and model "${i}" is already registered`)}}}}commit(){let e=new Map;for(let t of this.registrations)for(let n of t.entries){let t=e.get(n.route);if(t===void 0&&(t={byModel:new Map},e.set(n.route,t)),n.models===void 0)t.wide=n;else for(let e of n.models)t.byModel.set(e,n)}this.index=e,this.emitAdaptersUpdated()}emitAdaptersUpdated(){for(let e of[...this.listeners])try{e()}catch{}}};function s(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)throw i(`embedding adapter model selection must be non-empty`);let t=new Set;for(let n of e){if(typeof n!=`string`||n.length===0)throw i(`embedding adapter model ids must be non-empty strings`);if(t.has(n))throw i(`embedding adapter model selection must be unique`);t.add(n)}return t}export{o as EmbeddingRegistry};
|
|
2
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","names":[],"sources":["../../../src/composition/embedding/registry.ts"],"sourcesContent":["/**\n * The embedding adapter registry: resolution by route + operation + model id.\n *\n * Deliberately a SEPARATE object from `ModelRegistry` rather than a second map\n * inside it. Both are keyed by route, but they answer different operations, and\n * keeping them apart is what makes it structurally impossible for a\n * `ModelAdapter` to be handed an embedding call or an `EmbeddingAdapter` a\n * generation call (Requirement 11.10). A route may legitimately carry a\n * generation plugin AND an embedding plugin at the same time.\n *\n * Within one route, resolution is two-tier: entries that declared `models[]`\n * match first, then a route-wide entry (one that declared no models), and when\n * neither exists the lookup fails fast with `EMBEDDING_ADAPTER_MISSING`\n * (Requirement 3.5). Model-scoped beats route-wide because a plugin that names\n * ids is making a narrower, more specific claim than one that claims the route.\n *\n * Registration mirrors `ModelRegistry` conventions: validate-then-commit so a\n * rejected candidate leaves the registry exactly as it was, all-or-nothing over\n * the whole route set, and a disposer handle that also supports `replace()`.\n *\n * @module ai-agent-sdk/core/composition/embedding/registry\n */\n\nimport type { ProviderInfo } from '../../contract/model-info.ts'\nimport type { ResolvedRetryPolicy } from '../../contract/retry-policy.ts'\nimport type { EmbeddingAdapter } from '../../embedding/adapter.ts'\nimport { EMBEDDING_ERROR_CODES, EmbeddingError } from '../../embedding/errors.ts'\nimport { AgentSdkError } from '../../errors/agent-sdk-error.ts'\nimport type { AdapterRegistrationHandle } from '../../plugin/provider-plugin.ts'\nimport type { EmbeddingProviderRegistrar } from './plugin-types.ts'\n\n/** One resolved embedding registration, detached from registry internals. */\nexport interface EmbeddingRegistration {\n /** The route this registration was resolved for. */\n readonly route: string\n /** Adapter-declared route metadata, validated at registration time. */\n readonly provider: ProviderInfo\n /** The adapter that will perform `Provider_Attempt`s for this route. */\n readonly adapter: EmbeddingAdapter\n /** Captured at REGISTRATION time; absent means \"accept the runtime defaults\". */\n readonly retryPolicy?: ResolvedRetryPolicy\n /** Model ids this registration claims; absent means the whole route. */\n readonly models?: readonly string[]\n}\n\n/** One route entry held by exactly one registration. */\ninterface RouteEntry {\n readonly route: string\n readonly provider: ProviderInfo\n readonly adapter: EmbeddingAdapter\n readonly retryPolicy?: ResolvedRetryPolicy\n /** `undefined` marks a route-wide entry. */\n readonly models?: ReadonlySet<string>\n}\n\n/** Mutable per-registration state; `replace()` rewrites `entries` wholesale. */\ninterface RegistrationState {\n entries: readonly RouteEntry[]\n released: boolean\n}\n\n/** Resolution index rebuilt from the live registrations after every commit. */\ninterface RouteIndex {\n readonly byModel: Map<string, RouteEntry>\n wide?: RouteEntry\n}\n\nconst conflictError = (message: string) => new AgentSdkError(message, 'EMBEDDING_ROUTE_CONFLICT')\nconst invalidError = (message: string) => new AgentSdkError(message, 'EMBEDDING_REGISTRATION_INVALID')\n\n/** Only pass identity facts the error contract accepts as non-empty strings. */\nfunction errorIdentity(route: unknown, model: unknown): { provider?: string; model?: string } {\n return {\n ...(typeof route === 'string' && route.length > 0 ? { provider: route } : {}),\n ...(typeof model === 'string' && model.length > 0 ? { model } : {}),\n }\n}\n\n/**\n * Routes embedding calls to registered `EmbeddingAdapter`s.\n *\n * Construct one per runtime, beside — never inside — the `ModelRegistry`.\n */\nexport class EmbeddingRegistry implements EmbeddingProviderRegistrar {\n private readonly registrations = new Set<RegistrationState>()\n private index = new Map<string, RouteIndex>()\n private readonly listeners = new Set<() => void>()\n\n /**\n * Register one adapter for the given routes, optionally scoped to model ids.\n *\n * All-or-nothing: if ANY route conflicts, nothing is registered, because a\n * partial registration would leave the caller believing a route exists when it\n * does not.\n * @param routes - every route this registration serves; must be non-empty and unique.\n * @param adapter - the adapter that performs the embedding requests.\n * @param models - exact model ids this registration claims; omit to claim the routes.\n * @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.\n */\n registerEmbeddingAdapter(\n routes: readonly string[],\n adapter: EmbeddingAdapter,\n models?: readonly string[],\n ): AdapterRegistrationHandle {\n const claimed = capturedModels(models)\n const state: RegistrationState = { entries: [], released: false }\n state.entries = this.prepareEntries(routes, adapter, claimed, state)\n this.registrations.add(state)\n this.commit()\n\n const handle = (() => {\n if (state.released) return\n state.released = true\n this.registrations.delete(state)\n state.entries = []\n this.commit()\n }) as AdapterRegistrationHandle\n\n handle.replace = (next: readonly string[]): void => {\n // Re-registering after disposal would leak: nothing remains that could\n // release what this call would put back into the index.\n if (state.released) {\n throw new AgentSdkError(\n 'a disposed embedding registration cannot replace its routes',\n 'EMBEDDING_REGISTRATION_DISPOSED',\n )\n }\n const prepared = this.prepareEntries(next, adapter, claimed, state)\n state.entries = prepared\n this.commit()\n }\n return handle\n }\n\n /**\n * Resolve the adapter for one route + model id, embedding operation only.\n *\n * @param route - provider route key.\n * @param model - exact model id, as the caller asked for it.\n * @throws EmbeddingError `EMBEDDING_ADAPTER_MISSING` when nothing claims the pair,\n * including when the route only carries a generation adapter (Requirement 3.5).\n */\n resolve(route: string, model: string): EmbeddingRegistration {\n const routeIndex = typeof route === 'string' ? this.index.get(route) : undefined\n const entry = routeIndex === undefined ? undefined\n : (typeof model === 'string' ? routeIndex.byModel.get(model) : undefined) ?? routeIndex.wide\n if (entry === undefined) {\n throw new EmbeddingError(\n `no embedding adapter is registered for route \"${String(route)}\" and model \"${String(model)}\"`,\n EMBEDDING_ERROR_CODES.ADAPTER_MISSING,\n errorIdentity(route, model),\n )\n }\n return Object.freeze({\n route: entry.route,\n provider: { ...entry.provider },\n adapter: entry.adapter,\n ...(entry.retryPolicy === undefined ? {} : { retryPolicy: entry.retryPolicy }),\n ...(entry.models === undefined ? {} : { models: Object.freeze([...entry.models]) }),\n })\n }\n\n /** Whether any embedding entry — model-scoped or route-wide — claims this route. */\n hasRoute(route: string): boolean {\n return typeof route === 'string' && this.index.has(route)\n }\n\n /** Every route with at least one embedding entry, in index order. */\n listRoutes(): readonly string[] {\n return Object.freeze([...this.index.keys()])\n }\n\n /**\n * Observe embedding topology changes (registration, replacement, disposal).\n * @param listener - payload-free notification; re-read {@link listRoutes} for new state.\n * @returns a disposer that removes exactly this listener.\n */\n onAdaptersUpdated(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => void this.listeners.delete(listener)\n }\n\n /**\n * Validate one candidate route set without mutating anything, treating entries\n * the same registration already holds as available.\n */\n private prepareEntries(\n routes: readonly string[],\n adapter: EmbeddingAdapter,\n models: ReadonlySet<string> | undefined,\n owner: RegistrationState,\n ): readonly RouteEntry[] {\n if (!Array.isArray(routes) || routes.length === 0) {\n throw invalidError('an embedding adapter must register at least one provider route')\n }\n if (adapter === null || typeof adapter !== 'object' || typeof adapter.embedBatch !== 'function') {\n throw invalidError('an embedding registration requires an EmbeddingAdapter')\n }\n const unique = new Set<string>()\n const entries: RouteEntry[] = []\n for (const route of routes) {\n if (typeof route !== 'string' || route.length === 0) {\n throw invalidError('embedding adapter provider route names must be non-empty strings')\n }\n if (unique.has(route)) {\n throw conflictError(`embedding route \"${route}\" is claimed twice by one registration`)\n }\n this.checkVacancy(route, models, owner)\n const info = adapter.providerInfo(route)\n if (info === null || typeof info !== 'object' || info.id !== route\n || typeof info.name !== 'string' || info.name.length === 0) {\n throw invalidError(\n `embedding adapter metadata for route \"${route}\" must preserve its id and carry a non-empty name`,\n )\n }\n const retryPolicy = adapter.providerRetryPolicy(route)\n unique.add(route)\n entries.push(Object.freeze({\n route,\n provider: Object.freeze({ id: info.id, name: info.name }),\n adapter,\n // Captured at REGISTRATION time, not per call: an adapter whose policy\n // follows mutable configuration re-registers through `handle.replace`.\n ...(retryPolicy === undefined ? {} : { retryPolicy }),\n ...(models === undefined ? {} : { models }),\n }))\n }\n return Object.freeze(entries)\n }\n\n /**\n * A model-scoped claim and a route-wide claim coexist by design — that IS the\n * two-tier resolution. Two claims of the SAME tier over the same id do not.\n */\n private checkVacancy(\n route: string,\n models: ReadonlySet<string> | undefined,\n owner: RegistrationState,\n ): void {\n for (const state of this.registrations) {\n if (state === owner) continue\n for (const entry of state.entries) {\n if (entry.route !== route) continue\n if (entry.models === undefined && models === undefined) {\n throw conflictError(`an embedding adapter for route \"${route}\" is already registered`)\n }\n if (entry.models === undefined || models === undefined) continue\n for (const model of models) {\n if (entry.models.has(model)) {\n throw conflictError(\n `an embedding adapter for route \"${route}\" and model \"${model}\" is already registered`,\n )\n }\n }\n }\n }\n }\n\n /**\n * Rebuild the resolution index in ONE synchronous section, so no caller can\n * observe the registry between a release and a re-registration.\n */\n private commit(): void {\n const index = new Map<string, RouteIndex>()\n for (const state of this.registrations) {\n for (const entry of state.entries) {\n let routeIndex = index.get(entry.route)\n if (routeIndex === undefined) {\n routeIndex = { byModel: new Map<string, RouteEntry>() }\n index.set(entry.route, routeIndex)\n }\n if (entry.models === undefined) routeIndex.wide = entry\n else for (const model of entry.models) routeIndex.byModel.set(model, entry)\n }\n }\n this.index = index\n this.emitAdaptersUpdated()\n }\n\n /**\n * Notify topology observers.\n *\n * Each listener is contained independently: a topology notification is not a\n * veto, so one broken observer must not starve the others or roll back a\n * registration that already happened.\n */\n private emitAdaptersUpdated(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener()\n } catch {\n // Contained deliberately; see above.\n }\n }\n }\n}\n\n/** Copy and validate the optional model claim so later mutation cannot widen it. */\nfunction capturedModels(models: readonly string[] | undefined): ReadonlySet<string> | undefined {\n if (models === undefined) return undefined\n if (!Array.isArray(models) || models.length === 0) {\n throw invalidError('embedding adapter model selection must be non-empty')\n }\n const captured = new Set<string>()\n for (const model of models) {\n if (typeof model !== 'string' || model.length === 0) {\n throw invalidError('embedding adapter model ids must be non-empty strings')\n }\n if (captured.has(model)) {\n throw invalidError('embedding adapter model selection must be unique')\n }\n captured.add(model)\n }\n return captured\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{MODEL_ERROR_CODES as e}from"../../errors/model-error.js";import{normalizeModelFailure as t}from"../../errors/failure.js";import{backoffDelayMs as n,resolveRetryPolicy as r}from"../../contract/retry-policy.js";import{EMBEDDING_ERROR_CODES as i,EmbeddingError as a}from"../../embedding/errors.js";const o=Object.freeze({phase:`pending`});function s(t){return t===e.ABORTED||t===i.ABORTED}function c(e,t,n){return e instanceof a?e:new a(t.message,t.code,{cause:e,provider:n.provider,model:n.model,itemIndexes:n.items.map(e=>e.index)})}function l(e,t){return t?.aborted===!0?Promise.resolve(!1):new Promise(n=>{let r=()=>{clearTimeout(i),n(!1)},i=setTimeout(()=>{t?.removeEventListener(`abort`,r),n(!0)},e);t?.addEventListener(`abort`,r,{once:!0})})}function u(e,t,r,i){let a=t.providerRetryAfterMs;return a!==void 0&&Number.isFinite(a)&&a>0?a<=e.maxDelayMs?a:e.mode===`always`?n(e,r,i):`give-up`:n(e,r,i)}function d(e,t){let n=e?.startProviderAttempt;return e===void 0||n===void 0?e:{...e,startProviderAttempt:async(e,r)=>{let i=await n(e,r);return{attemptId:i.attemptId,attemptNumber:i.attemptNumber,traceparent:i.traceparent,end:e=>(t(e.dispatchState),i.end(e))}}}}function f(e,n={}){let f=n.policy??r(void 0,`embedding.retryPolicy`);if(f.mode===`always`&&n.signal===void 0)throw new a(`an always retry policy requires an AbortSignal`,i.CONFIGURATION_INVALID);let p=n.random??Math.random,m=n.sleep??l,h=new Map,g=e=>n.signal?.aborted===!0||e.signal?.aborted===!0,_=(e,t,n,r,i)=>{let a=Object.freeze({phase:`failed`,error:r,retryable:!1,dispatch:i});return h.set(e,a),Object.freeze({batchIndex:e,itemIndexes:Object.freeze(t.items.map(e=>e.index)),state:a,attempts:n.length,attemptRecords:Object.freeze([...n]),warnings:Object.freeze([])})};return{stateOf(e){return h.get(e)??o},states(){return new Map(h)},async dispatch(r,o,l){let v=l??n.context,y=h.get(r);if(y!==void 0&&y.phase===`succeeded`)throw new a(`embedding batch ${r} already succeeded and must not be dispatched again`,i.CONFIGURATION_INVALID,{provider:o.provider,model:o.model});let b=Object.freeze(o.items.map(e=>e.index)),x=[],S=0;for(;;){let l=x.length+1;if(g(o))return _(r,o,x,new a(`embedding call aborted before dispatch`,i.ABORTED,{provider:o.provider,model:o.model,itemIndexes:[...b]}),x.length===0?`not-sent`:`unknown`);h.set(r,Object.freeze({phase:`in-flight`,attempt:l}));let y,C=d(v,e=>{y=e}),w;try{w=await e.embedBatch(o,C)}catch(e){let d=t(e),C=y??`unknown`;x.push(Object.freeze({attempt:l,outcome:`failure`,dispatch:C,failureCode:d.code}));let w=c(e,d,o);if(g(o)||s(d.code))return _(r,o,x,w,C);let T=f.mode===`always`||f.retryableCodes.includes(d.code)&&S<f.maxRetries,E=T?u(f,d,S+1,p):`give-up`;if(!T||E===`give-up`){let e=Object.freeze({phase:`failed`,error:w,retryable:f.mode===`always`||f.retryableCodes.includes(d.code),dispatch:C});return h.set(r,e),Object.freeze({batchIndex:r,itemIndexes:b,state:e,attempts:x.length,attemptRecords:Object.freeze([...x]),warnings:Object.freeze([])})}S+=1,v?.recordProviderRetry?.({nextAttemptNumber:S+1,delayMs:E,failureCode:d.code});try{n.onRetry?.({provider:o.provider,model:o.model,batchIndex:r,attempt:S,maxRetries:f.mode===`normal`?f.maxRetries:void 0,failureCode:d.code,dispatch:C,delayMs:E})}catch{}if(!await m(E,n.signal??o.signal))return _(r,o,x,new a(`embedding call aborted while waiting to retry`,i.ABORTED,{provider:o.provider,model:o.model,itemIndexes:[...b],cause:e}),C);continue}let T=y??`sent`;x.push(Object.freeze({attempt:l,outcome:`success`,dispatch:T}));let E=Object.freeze({phase:`succeeded`,vectors:w.vectors});return h.set(r,E),Object.freeze({batchIndex:r,itemIndexes:b,state:E,attempts:x.length,attemptRecords:Object.freeze([...x]),...w.usage===void 0?{}:{usage:w.usage},...w.providerRequestId===void 0?{}:{providerRequestId:w.providerRequestId},warnings:Object.freeze([...w.warnings??[]])})}}}}export{f as createEmbeddingRetryLedger};
|
|
2
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","names":[],"sources":["../../../src/composition/embedding/retry.ts"],"sourcesContent":["/**\n * The ONE retry layer for embedding.\n *\n * `Embedding_Runtime` owns retry; an `Embedding_Adapter` performs exactly one\n * `Provider_Attempt` per `embedBatch()` call and never retries inside\n * (Requirement 4.3). Keeping retry here is what makes a `Provider_Attempt`\n * countable: the number of attempts a caller is billed for equals the number of\n * times this file called the adapter.\n *\n * Four facts drive every branch below:\n *\n * 1. **Only unsuccessful batches are retried.** A batch that reached\n * `succeeded` is removed from every later retry pass of the same\n * `Logical_Call` — its vectors already exist, and re-sending it would be a\n * second charge for a result we hold (Requirement 4.7). The ledger enforces\n * this structurally: a terminal state is never re-entered.\n * 2. **Dispatch state comes from the transport, it is not re-derived.**\n * `Http_Transport` sets `dispatchState = 'unknown'` immediately before\n * `fetch` and only raises it to `'sent'` once a response exists, so this file\n * reads the value the transport reported through `attempt.end` instead of\n * guessing from the shape of the error. When nothing reported a state, the\n * record stays `'unknown'`: only the transport is in a position to claim\n * `'not-sent'`, and a timeout in particular is never that (Requirement 4.8).\n * 3. **No fallback model.** A failure of the primary model propagates to the\n * caller. Fallback is only ever configured within a group sharing one\n * `compatibilityIdentity`, and that condition is checked when the handle is\n * built, not after a failure has happened (Requirements 6.6, 6.7).\n * 4. **Order is not this file's business.** Vectors are handed back per batch and\n * the caller writes them at `item.index`, so retries and out-of-order\n * settlement cannot disturb output order (Requirement 4.6).\n *\n * Concurrency lives in `limiter.ts`; this file drives one batch at a time and is\n * composed underneath it.\n *\n * @module ai-agent-sdk/core/composition/embedding/retry\n */\n\nimport {\n backoffDelayMs,\n resolveRetryPolicy,\n type ResolvedRetryPolicy,\n} from '../../contract/retry-policy.ts'\nimport { EMBEDDING_ERROR_CODES, EmbeddingError } from '../../embedding/errors.ts'\nimport type { EmbeddingBatchRequest } from '../../embedding/request.ts'\nimport type { EmbeddingBatchResult, EmbeddingVector, EmbeddingWarning } from '../../embedding/result.ts'\nimport { normalizeModelFailure, type ModelFailure } from '../../errors/failure.ts'\nimport { MODEL_ERROR_CODES } from '../../errors/model-error.ts'\nimport type {\n EndProviderAttemptInput,\n ModelInvocationContext,\n ProviderAttemptHandle,\n} from '../../observation/report.ts'\nimport type { AttemptUsageReport, DispatchState } from '../../observation/usage.ts'\n\n/**\n * Lifecycle of one `Physical_Batch` inside a single `Logical_Call`.\n *\n * `succeeded` and `failed` are terminal. That is the whole mechanism behind\n * Requirement 4.7: retry only ever considers a batch that is not yet terminal.\n */\nexport type BatchState =\n | { readonly phase: 'pending' }\n | { readonly phase: 'in-flight'; readonly attempt: number }\n | { readonly phase: 'succeeded'; readonly vectors: readonly EmbeddingVector[] }\n | {\n readonly phase: 'failed'\n readonly error: EmbeddingError\n readonly retryable: boolean\n /** As reported by the transport; `'unknown'` whenever nothing reported. */\n readonly dispatch: DispatchState\n }\n\n/** One retry that is about to be waited out, for logging and metrics. */\nexport interface EmbeddingRetryAttempt {\n readonly provider: string\n readonly model: string\n /** Plan coordinate of the batch being retried. */\n readonly batchIndex: number\n /** 1-based retry number. */\n readonly attempt: number\n /** Retry ceiling, or `undefined` under an `always` policy. */\n readonly maxRetries: number | undefined\n /** Stable code of the failure being recovered from. */\n readonly failureCode: string\n /** Dispatch state the transport reported for the failed attempt. */\n readonly dispatch: DispatchState\n readonly delayMs: number\n}\n\n/** What one `Provider_Attempt` on a batch left behind. */\nexport interface EmbeddingAttemptRecord {\n /** 1-based attempt number within this batch. */\n readonly attempt: number\n readonly outcome: 'success' | 'failure'\n /** From the transport, never re-derived (Requirement 4.8). */\n readonly dispatch: DispatchState\n /** Stable failure code, absent on success. */\n readonly failureCode?: string\n}\n\n/** Everything a `Logical_Call` needs to know about one settled batch. */\nexport interface EmbeddingBatchOutcome {\n readonly batchIndex: number\n /** Input indexes this batch carried, in `Logical_Call` numbering. */\n readonly itemIndexes: readonly number[]\n /** Terminal state: `succeeded` or `failed`. */\n readonly state: BatchState\n /** `Provider_Attempt`s spent, retries included (Requirement 16.6). */\n readonly attempts: number\n /** One record per adapter call, in order. */\n readonly attemptRecords: readonly EmbeddingAttemptRecord[]\n /**\n * Raw provider usage evidence from the attempt that settled the batch.\n *\n * `unknown` on purpose: proving it is a usage shape belongs to\n * `validateEmbeddingUsage`, and absence must never become a zero.\n */\n readonly usage?: unknown\n readonly providerRequestId?: string\n readonly warnings: readonly EmbeddingWarning[]\n}\n\n/** Dispatch surface of a `Prepared_Embedding_Call`; the ledger needs nothing else. */\nexport interface EmbeddingBatchDispatcher {\n embedBatch(\n batch: EmbeddingBatchRequest,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingBatchResult>\n}\n\n/** Knobs for {@link createEmbeddingRetryLedger}. */\nexport interface EmbeddingRetryOptions {\n /** Route policy; omission takes the shared SDK defaults. */\n readonly policy?: ResolvedRetryPolicy\n /** Sample in `[0, 1)` for jitter; injectable so tests can be deterministic. */\n readonly random?: () => number\n /** Caller signal or runtime `close()`; an abort is never a transient fault. */\n readonly signal?: AbortSignal\n /** Invocation context; wrapped so transport-reported dispatch states are observed. */\n readonly context?: ModelInvocationContext\n /** Observe each scheduled retry. Failures in the observer are swallowed. */\n readonly onRetry?: (attempt: EmbeddingRetryAttempt) => void\n /** Sleep hook; resolves `false` when the signal fired first. Injectable for tests. */\n readonly sleep?: (delayMs: number, signal?: AbortSignal) => Promise<boolean>\n}\n\n/**\n * Per-`Logical_Call` retry driver and state ledger.\n *\n * One ledger per `Logical_Call`, shared across every batch of that call, which\n * is what lets it guarantee that a `succeeded` batch is never dispatched again.\n */\nexport interface EmbeddingRetryLedger {\n /**\n * Dispatch one `Physical_Batch` and retry it until it settles.\n *\n * Resolves with a terminal outcome instead of throwing, because only the\n * caller knows whether a failed batch should abandon the whole call or leave\n * the batches that already succeeded intact. There is no fallback model: a\n * failure here is the failure the caller sees.\n *\n * @param context - overrides the ledger's context for this batch only. One\n * ledger spans the whole `Logical_Call` while observation is per-batch, so this\n * is how an attempt gets parented under the batch that provoked it. The\n * override is wrapped for dispatch-state observation exactly like the\n * ledger-wide context, so nothing about Requirement 4.8 changes.\n */\n dispatch(\n batchIndex: number,\n request: EmbeddingBatchRequest,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingBatchOutcome>\n /** Current state of one batch; `pending` for a batch never dispatched. */\n stateOf(batchIndex: number): BatchState\n /** Immutable view of every batch state seen so far. */\n states(): ReadonlyMap<number, BatchState>\n}\n\nconst PENDING: BatchState = Object.freeze({ phase: 'pending' as const })\n\n/** Codes that mean \"the caller stopped this\", never \"the provider wobbled\". */\nfunction isAbortCode(code: string): boolean {\n return code === MODEL_ERROR_CODES.ABORTED || code === EMBEDDING_ERROR_CODES.ABORTED\n}\n\n/**\n * Turn any thrown value into an {@link EmbeddingError} without losing its code.\n *\n * An `EmbeddingError` from the adapter passes through untouched: it already\n * carries `itemIndexes` and `limit`, and re-wrapping would drop exactly the\n * facts a caller needs to re-chunk the offending inputs. Anything else keeps its\n * normalized stable code — a rate limit stays `MODEL_RATE_LIMIT` so the retry\n * allow-list still recognises it.\n */\nfunction asEmbeddingError(\n value: unknown,\n failure: ModelFailure,\n request: EmbeddingBatchRequest,\n): EmbeddingError {\n if (value instanceof EmbeddingError) return value\n return new EmbeddingError(failure.message, failure.code, {\n cause: value,\n provider: request.provider,\n model: request.model,\n itemIndexes: request.items.map(item => item.index),\n })\n}\n\n/** Sleep, resolving early and reporting `false` if the signal aborts first. */\nfunction cancellableDelay(delayMs: number, signal?: AbortSignal): Promise<boolean> {\n if (signal?.aborted === true) return Promise.resolve(false)\n return new Promise((resolve) => {\n const onAbort = (): void => {\n clearTimeout(timer)\n resolve(false)\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve(true)\n }, delayMs)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\n/** Honour a sane provider-requested `retry-after`, else local bounded backoff. */\nfunction delayFor(\n policy: ResolvedRetryPolicy,\n failure: ModelFailure,\n attempt: number,\n random: () => number,\n): number | 'give-up' {\n const requested = failure.providerRetryAfterMs\n if (requested !== undefined && Number.isFinite(requested) && requested > 0) {\n if (requested <= policy.maxDelayMs) return requested\n // The provider asked for longer than this policy will wait. Under a bounded\n // policy that is a refusal: sleeping less than asked just earns another rate\n // limit. An `always` policy has nowhere to give up to.\n return policy.mode === 'always' ? backoffDelayMs(policy, attempt, random) : 'give-up'\n }\n return backoffDelayMs(policy, attempt, random)\n}\n\n/**\n * Wrap an invocation context so the dispatch state the TRANSPORT reports is\n * captured, rather than inferred from the error afterwards.\n *\n * The wrapper is transparent: it forwards `startProviderAttempt` untouched and\n * only tees the `dispatchState` passed to `attempt.end`. When a context has no\n * attempt accounting there is nothing to observe, and the record stays\n * `'unknown'` — that is the honest answer, and it is the answer Requirement 4.8\n * demands for a timeout.\n */\nfunction observeDispatchState(\n context: ModelInvocationContext | undefined,\n sink: (state: DispatchState) => void,\n): ModelInvocationContext | undefined {\n const start = context?.startProviderAttempt\n if (context === undefined || start === undefined) return context\n return {\n ...context,\n startProviderAttempt: async (input, signal): Promise<ProviderAttemptHandle> => {\n const handle = await start(input, signal)\n return {\n attemptId: handle.attemptId,\n attemptNumber: handle.attemptNumber,\n traceparent: handle.traceparent,\n end: (end: EndProviderAttemptInput): AttemptUsageReport => {\n sink(end.dispatchState)\n return handle.end(end)\n },\n }\n },\n }\n}\n\n/**\n * Create the retry ledger for one `Logical_Call`.\n *\n * An `always` policy without a signal is rejected here rather than at the first\n * failure: unbounded retry is bounded only by cancellation, so a call that\n * cannot be cancelled would never terminate.\n */\nexport function createEmbeddingRetryLedger(\n dispatcher: EmbeddingBatchDispatcher,\n options: EmbeddingRetryOptions = {},\n): EmbeddingRetryLedger {\n const policy = options.policy ?? resolveRetryPolicy(undefined, 'embedding.retryPolicy')\n if (policy.mode === 'always' && options.signal === undefined) {\n throw new EmbeddingError(\n 'an always retry policy requires an AbortSignal',\n EMBEDDING_ERROR_CODES.CONFIGURATION_INVALID,\n )\n }\n const random = options.random ?? Math.random\n const sleep = options.sleep ?? cancellableDelay\n const states = new Map<number, BatchState>()\n\n /**\n * Whether either signal has fired.\n *\n * A function rather than an inline check so the answer is re-read after every\n * `await`: a signal that aborts while a request is in flight is precisely the\n * case that matters.\n */\n const cancelled = (request: EmbeddingBatchRequest): boolean =>\n options.signal?.aborted === true || request.signal?.aborted === true\n\n const abortedOutcome = (\n batchIndex: number,\n request: EmbeddingBatchRequest,\n records: readonly EmbeddingAttemptRecord[],\n error: EmbeddingError,\n dispatch: DispatchState,\n ): EmbeddingBatchOutcome => {\n const state: BatchState = Object.freeze({\n phase: 'failed' as const,\n error,\n // An abort is the caller's decision, so it is terminal by definition.\n retryable: false,\n dispatch,\n })\n states.set(batchIndex, state)\n return Object.freeze({\n batchIndex,\n itemIndexes: Object.freeze(request.items.map(item => item.index)),\n state,\n attempts: records.length,\n attemptRecords: Object.freeze([...records]),\n warnings: Object.freeze([]),\n })\n }\n\n return {\n stateOf(batchIndex: number): BatchState {\n return states.get(batchIndex) ?? PENDING\n },\n\n states(): ReadonlyMap<number, BatchState> {\n return new Map(states)\n },\n\n async dispatch(\n batchIndex: number,\n request: EmbeddingBatchRequest,\n batchContext?: ModelInvocationContext,\n ): Promise<EmbeddingBatchOutcome> {\n const invocationContext = batchContext ?? options.context\n const existing = states.get(batchIndex)\n if (existing !== undefined && existing.phase === 'succeeded') {\n // Requirement 4.7 as an invariant rather than a convention: a batch whose\n // vectors are already held is never handed to the provider again.\n throw new EmbeddingError(\n `embedding batch ${batchIndex} already succeeded and must not be dispatched again`,\n EMBEDDING_ERROR_CODES.CONFIGURATION_INVALID,\n { provider: request.provider, model: request.model },\n )\n }\n\n const itemIndexes = Object.freeze(request.items.map(item => item.index))\n const records: EmbeddingAttemptRecord[] = []\n let retries = 0\n\n for (;;) {\n const attemptNumber = records.length + 1\n // Report the abort before spending an attempt on a call already cancelled.\n if (cancelled(request)) {\n return abortedOutcome(\n batchIndex,\n request,\n records,\n new EmbeddingError(\n 'embedding call aborted before dispatch',\n EMBEDDING_ERROR_CODES.ABORTED,\n { provider: request.provider, model: request.model, itemIndexes: [...itemIndexes] },\n ),\n // No adapter call was ever made on this pass, which the runtime knows\n // first-hand. Once an attempt HAS been made, the runtime stops\n // claiming anything and leaves the state `unknown`.\n records.length === 0 ? 'not-sent' : 'unknown',\n )\n }\n\n states.set(batchIndex, Object.freeze({ phase: 'in-flight' as const, attempt: attemptNumber }))\n\n // Reported by the transport through `attempt.end`; unreported stays unknown.\n let reportedDispatch: DispatchState | undefined\n const context = observeDispatchState(invocationContext, (state) => {\n reportedDispatch = state\n })\n\n let result: EmbeddingBatchResult\n try {\n result = await dispatcher.embedBatch(request, context)\n } catch (error: unknown) {\n const failure = normalizeModelFailure(error)\n const dispatch = reportedDispatch ?? 'unknown'\n records.push(Object.freeze({\n attempt: attemptNumber,\n outcome: 'failure' as const,\n dispatch,\n failureCode: failure.code,\n }))\n\n const embeddingError = asEmbeddingError(error, failure, request)\n if (cancelled(request) || isAbortCode(failure.code)) {\n return abortedOutcome(batchIndex, request, records, embeddingError, dispatch)\n }\n\n const eligible = policy.mode === 'always'\n || (policy.retryableCodes.includes(failure.code) && retries < policy.maxRetries)\n const delayMs = eligible ? delayFor(policy, failure, retries + 1, random) : 'give-up'\n if (!eligible || delayMs === 'give-up') {\n const state: BatchState = Object.freeze({\n phase: 'failed' as const,\n error: embeddingError,\n // `retryable` describes the failure, not the budget: a rate limit\n // that ran out of attempts is still a retryable KIND of failure,\n // and a caller deciding whether to re-run later needs that apart\n // from \"this call gave up\".\n retryable: policy.mode === 'always' || policy.retryableCodes.includes(failure.code),\n dispatch,\n })\n states.set(batchIndex, state)\n return Object.freeze({\n batchIndex,\n itemIndexes,\n state,\n attempts: records.length,\n attemptRecords: Object.freeze([...records]),\n warnings: Object.freeze([]),\n })\n }\n\n retries += 1\n invocationContext?.recordProviderRetry?.({\n nextAttemptNumber: retries + 1,\n delayMs,\n failureCode: failure.code,\n })\n try {\n options.onRetry?.({\n provider: request.provider,\n model: request.model,\n batchIndex,\n attempt: retries,\n maxRetries: policy.mode === 'normal' ? policy.maxRetries : undefined,\n failureCode: failure.code,\n dispatch,\n delayMs,\n })\n } catch {\n // Metrics and logging observers do not own request availability.\n }\n\n if (!await sleep(delayMs, options.signal ?? request.signal)) {\n return abortedOutcome(\n batchIndex,\n request,\n records,\n new EmbeddingError(\n 'embedding call aborted while waiting to retry',\n EMBEDDING_ERROR_CODES.ABORTED,\n { provider: request.provider, model: request.model, itemIndexes: [...itemIndexes], cause: error },\n ),\n dispatch,\n )\n }\n continue\n }\n\n // A response exists, so the request reached the provider. The transport's\n // own report still wins when it made one.\n const dispatch = reportedDispatch ?? 'sent'\n records.push(Object.freeze({\n attempt: attemptNumber,\n outcome: 'success' as const,\n dispatch,\n }))\n const state: BatchState = Object.freeze({\n phase: 'succeeded' as const,\n vectors: result.vectors,\n })\n states.set(batchIndex, state)\n return Object.freeze({\n batchIndex,\n itemIndexes,\n state,\n attempts: records.length,\n attemptRecords: Object.freeze([...records]),\n // Absent usage is left absent: the aggregator downgrades coverage\n // rather than counting an unreported batch as zero tokens.\n ...(result.usage === undefined ? {} : { usage: result.usage }),\n ...(result.providerRequestId === undefined\n ? {}\n : { providerRequestId: result.providerRequestId }),\n warnings: Object.freeze([...(result.warnings ?? [])]),\n })\n }\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{classifyEmbeddingUsageStatus as e,validateEmbeddingUsage as t}from"../../embedding/usage.js";function n(e){if(e.usage===void 0)return{malformed:!1};let n=t(e.usage),r=n.invalidFields.length>0||n.overflow,i=n.overflow?void 0:n.reported;return i===void 0?{malformed:!0}:{reported:i,malformed:r}}function r(e){return Object.freeze({code:`usage-malformed`,...e.length===0?{}:{itemIndexes:Object.freeze([...e])},message:`provider reported usage that could not be read as embedding token counts`})}function i(e){return Object.freeze({code:`usage-unreported`,...e.length===0?{}:{itemIndexes:Object.freeze([...e])},message:`provider reported no usage for this batch; no token count was assumed`})}function a(e,t){let n=new Set;for(let r of e)for(let e of r.itemIndexes)Number.isInteger(e)&&e>=0&&e<t&&n.add(e);return n.size}function o(t){let o=Number.isSafeInteger(t.inputCount)&&t.inputCount>0?t.inputCount:0,s=t.batches,c=[],l=0,u=0,d=0,f=0,p=!0;for(let e of s){u+=Number.isSafeInteger(e.attempts)&&e.attempts>0?e.attempts:0;let t=n(e);e.usage===void 0?c.push(i(e.itemIndexes)):t.malformed&&c.push(r(e.itemIndexes)),t.reported!==void 0&&(l+=1,d+=t.reported.inputTokens,t.reported.totalTokens===void 0?p=!1:f+=t.reported.totalTokens)}let m=e(s.length,l),h=a(s,o),g=m===`complete`?Object.freeze(p?{inputTokens:d,totalTokens:f}:{inputTokens:d}):void 0;return Object.freeze({report:Object.freeze({status:m,...g===void 0?{}:{tokens:g},batches:s.length,batchesWithUsage:l,providerAttempts:u,inputsFromCache:o-h,inputsFromProvider:h}),warnings:Object.freeze(c)})}export{o as aggregateEmbeddingUsage};
|
|
2
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","names":[],"sources":["../../../src/composition/embedding/usage.ts"],"sourcesContent":["/**\n * Aggregates per-batch usage evidence into one honest `Logical_Call` report.\n *\n * The vocabulary (`EmbeddingTokenUsage`, `EmbeddingUsageReport`,\n * `EmbeddingUsageStatus`, `validateEmbeddingUsage`,\n * `classifyEmbeddingUsageStatus`) is owned by `embedding/usage.ts`. This file\n * adds only the aggregation: it walks the evidence collected from every\n * `Physical_Batch` that was actually dispatched and folds it into a single\n * report plus warnings.\n *\n * Two rules drive every branch here:\n *\n * 1. Nothing is invented. A batch whose provider reported no readable usage\n * contributes no tokens, and its absence downgrades the whole call to\n * `partial` or `missing` rather than being counted as a zero\n * (Requirements 16.2, 16.3). The absence is also stated out loud: a batch that\n * reported nothing raises `usage-unreported`, one whose report could not be\n * read raises `usage-malformed`.\n * 2. Cache and provider input counts are derived from the same evidence, so\n * `inputsFromCache + inputsFromProvider === inputCount` holds by\n * construction rather than by a caller remembering to keep them in step\n * (Requirement 5.4).\n *\n * @module ai-agent-sdk/core/composition/embedding/usage\n */\n\nimport type { EmbeddingWarning } from '../../embedding/result.ts'\nimport type { EmbeddingTokenUsage, EmbeddingUsageReport } from '../../embedding/usage.ts'\nimport { classifyEmbeddingUsageStatus, validateEmbeddingUsage } from '../../embedding/usage.ts'\n\n/**\n * What one dispatched `Physical_Batch` left behind.\n *\n * `usage` is deliberately `unknown`: it is raw provider evidence that has not\n * been proven to be a usage shape yet. `validateEmbeddingUsage` is the only\n * thing allowed to decide what it means.\n */\nexport interface EmbeddingBatchUsageEvidence {\n /** Input indexes this batch carried, in `Logical_Call` numbering. */\n readonly itemIndexes: readonly number[]\n /** `Provider_Attempt`s spent on this batch, retries included (Requirement 16.6). */\n readonly attempts: number\n /** Absent when the provider reported nothing at all. */\n readonly usage?: unknown\n}\n\n/** Everything the aggregator needs about one `Logical_Call`. */\nexport interface EmbeddingUsageAggregationInput {\n /** Inputs of the `Logical_Call`, cache hits included. */\n readonly inputCount: number\n /** Evidence for the batches that were sent to the provider; cache hits have none. */\n readonly batches: readonly EmbeddingBatchUsageEvidence[]\n}\n\n/** The report and the warnings that explain it. */\nexport interface EmbeddingUsageAggregation {\n readonly report: EmbeddingUsageReport\n readonly warnings: readonly EmbeddingWarning[]\n}\n\ninterface BatchReading {\n /** Present only when the provider reported a readable input bucket. */\n readonly reported?: EmbeddingTokenUsage\n readonly malformed: boolean\n}\n\n/**\n * Turns one batch's raw evidence into a readable-or-not decision.\n *\n * `overflow` is treated as unreadable, not as a large number: a count past safe\n * integer precision is authority lost, so it may not enter a published total.\n */\nfunction readBatch(evidence: EmbeddingBatchUsageEvidence): BatchReading {\n if (evidence.usage === undefined) return { malformed: false }\n const validation = validateEmbeddingUsage(evidence.usage)\n const malformed = validation.invalidFields.length > 0 || validation.overflow\n const reported = validation.overflow ? undefined : validation.reported\n return reported === undefined ? { malformed: true } : { reported, malformed }\n}\n\nfunction malformedWarning(itemIndexes: readonly number[]): EmbeddingWarning {\n return Object.freeze<EmbeddingWarning>({\n code: 'usage-malformed',\n ...(itemIndexes.length === 0 ? {} : { itemIndexes: Object.freeze([...itemIndexes]) }),\n message: 'provider reported usage that could not be read as embedding token counts',\n })\n}\n\n/**\n * Raised for a batch that was dispatched and came back with no usage at all.\n *\n * This is the absence half of Requirement 16.2, and it belongs here rather than\n * in an adapter: `status: 'missing'`/`'partial'` and the warning that explains it\n * are the same statement about the same evidence, and only the aggregator knows\n * a batch was dispatched. An endpoint that reports nothing by design — Gemini's\n * `batchEmbedContents` — therefore needs no per-provider warning code, and no\n * provider can report a `0` in place of the silence and have it pass unremarked.\n */\nfunction unreportedWarning(itemIndexes: readonly number[]): EmbeddingWarning {\n return Object.freeze<EmbeddingWarning>({\n code: 'usage-unreported',\n ...(itemIndexes.length === 0 ? {} : { itemIndexes: Object.freeze([...itemIndexes]) }),\n message: 'provider reported no usage for this batch; no token count was assumed',\n })\n}\n\n/**\n * Counts the distinct inputs the provider actually saw.\n *\n * Indexes outside the `Logical_Call` range are ignored rather than trusted:\n * counting them would let `inputsFromProvider` exceed `inputCount` and break the\n * sum invariant that Requirement 5.4 asks for.\n */\nfunction countProviderInputs(batches: readonly EmbeddingBatchUsageEvidence[], inputCount: number): number {\n const seen = new Set<number>()\n for (const batch of batches) {\n for (const index of batch.itemIndexes) {\n if (Number.isInteger(index) && index >= 0 && index < inputCount) seen.add(index)\n }\n }\n return seen.size\n}\n\n/**\n * Folds batch evidence into one `EmbeddingUsageReport`.\n *\n * `status` follows the coverage of the batches that were dispatched, so a call\n * served entirely from cache reports `missing` with `batches: 0` instead of\n * claiming complete knowledge of a cost that was never incurred. `tokens` is\n * published only for `complete`, and `totalTokens` only when every readable\n * batch reported one — a partial sum of totals would understate the call.\n */\nexport function aggregateEmbeddingUsage(input: EmbeddingUsageAggregationInput): EmbeddingUsageAggregation {\n const inputCount = Number.isSafeInteger(input.inputCount) && input.inputCount > 0 ? input.inputCount : 0\n const batches = input.batches\n const warnings: EmbeddingWarning[] = []\n\n let batchesWithUsage = 0\n // The sum of every attempt spent on the call, retries included. Zero here can\n // only come from an empty batch list, never from a default (Requirement 16.6).\n let providerAttempts = 0\n let inputTokens = 0\n let totalTokens = 0\n let everyReadableBatchHasTotal = true\n\n for (const batch of batches) {\n providerAttempts += Number.isSafeInteger(batch.attempts) && batch.attempts > 0 ? batch.attempts : 0\n const reading = readBatch(batch)\n // Absence and unreadability are different facts about the provider, so they\n // carry different codes; neither one contributes a token count.\n if (batch.usage === undefined) warnings.push(unreportedWarning(batch.itemIndexes))\n else if (reading.malformed) warnings.push(malformedWarning(batch.itemIndexes))\n if (reading.reported === undefined) continue\n batchesWithUsage += 1\n inputTokens += reading.reported.inputTokens\n if (reading.reported.totalTokens === undefined) everyReadableBatchHasTotal = false\n else totalTokens += reading.reported.totalTokens\n }\n\n const status = classifyEmbeddingUsageStatus(batches.length, batchesWithUsage)\n const inputsFromProvider = countProviderInputs(batches, inputCount)\n const tokens = status === 'complete'\n ? Object.freeze<EmbeddingTokenUsage>(everyReadableBatchHasTotal\n ? { inputTokens, totalTokens }\n : { inputTokens })\n : undefined\n\n return Object.freeze({\n report: Object.freeze<EmbeddingUsageReport>({\n status,\n ...(tokens === undefined ? {} : { tokens }),\n batches: batches.length,\n batchesWithUsage,\n providerAttempts,\n // Derived from the same evidence, so the two always sum to `inputCount`.\n inputsFromCache: inputCount - inputsFromProvider,\n inputsFromProvider,\n }),\n warnings: Object.freeze(warnings),\n })\n}\n"],"mappings":"oGAwEA,SAAS,EAAU,EAAqD,CACtE,GAAI,EAAS,QAAU,IAAA,GAAW,MAAO,CAAE,UAAW,EAAM,EAC5D,IAAM,EAAa,EAAuB,EAAS,KAAK,EAClD,EAAY,EAAW,cAAc,OAAS,GAAK,EAAW,SAC9D,EAAW,EAAW,SAAW,IAAA,GAAY,EAAW,SAC9D,OAAO,IAAa,IAAA,GAAY,CAAE,UAAW,EAAK,EAAI,CAAE,WAAU,WAAU,CAC9E,CAEA,SAAS,EAAiB,EAAkD,CAC1E,OAAO,OAAO,OAAyB,CACrC,KAAM,kBACN,GAAI,EAAY,SAAW,EAAI,CAAC,EAAI,CAAE,YAAa,OAAO,OAAO,CAAC,GAAG,CAAW,CAAC,CAAE,EACnF,QAAS,0EACX,CAAC,CACH,CAYA,SAAS,EAAkB,EAAkD,CAC3E,OAAO,OAAO,OAAyB,CACrC,KAAM,mBACN,GAAI,EAAY,SAAW,EAAI,CAAC,EAAI,CAAE,YAAa,OAAO,OAAO,CAAC,GAAG,CAAW,CAAC,CAAE,EACnF,QAAS,uEACX,CAAC,CACH,CASA,SAAS,EAAoB,EAAiD,EAA4B,CACxG,IAAM,EAAO,IAAI,IACjB,IAAK,IAAM,KAAS,EAClB,IAAK,IAAM,KAAS,EAAM,YACpB,OAAO,UAAU,CAAK,GAAK,GAAS,GAAK,EAAQ,GAAY,EAAK,IAAI,CAAK,EAGnF,OAAO,EAAK,IACd,CAWA,SAAgB,EAAwB,EAAkE,CACxG,IAAM,EAAa,OAAO,cAAc,EAAM,UAAU,GAAK,EAAM,WAAa,EAAI,EAAM,WAAa,EACjG,EAAU,EAAM,QAChB,EAA+B,CAAC,EAElC,EAAmB,EAGnB,EAAmB,EACnB,EAAc,EACd,EAAc,EACd,EAA6B,GAEjC,IAAK,IAAM,KAAS,EAAS,CAC3B,GAAoB,OAAO,cAAc,EAAM,QAAQ,GAAK,EAAM,SAAW,EAAI,EAAM,SAAW,EAClG,IAAM,EAAU,EAAU,CAAK,EAG3B,EAAM,QAAU,IAAA,GAAW,EAAS,KAAK,EAAkB,EAAM,WAAW,CAAC,EACxE,EAAQ,WAAW,EAAS,KAAK,EAAiB,EAAM,WAAW,CAAC,EACzE,EAAQ,WAAa,IAAA,KACzB,GAAoB,EACpB,GAAe,EAAQ,SAAS,YAC5B,EAAQ,SAAS,cAAgB,IAAA,GAAW,EAA6B,GACxE,GAAe,EAAQ,SAAS,YACvC,CAEA,IAAM,EAAS,EAA6B,EAAQ,OAAQ,CAAgB,EACtE,EAAqB,EAAoB,EAAS,CAAU,EAC5D,EAAS,IAAW,WACtB,OAAO,OAA4B,EACjC,CAAE,cAAa,aAAY,EAC3B,CAAE,aAAY,CAAC,EACjB,IAAA,GAEJ,OAAO,OAAO,OAAO,CACnB,OAAQ,OAAO,OAA6B,CAC1C,SACA,GAAI,IAAW,IAAA,GAAY,CAAC,EAAI,CAAE,QAAO,EACzC,QAAS,EAAQ,OACjB,mBACA,mBAEA,gBAAiB,EAAa,EAC9B,oBACF,CAAC,EACD,SAAU,OAAO,OAAO,CAAQ,CAClC,CAAC,CACH"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
//#region src/composition/lifecycle/types.d.ts
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Append-only. `RuntimeOperations.beginClose()` reads `operations[0]` as the source of
|
|
4
|
+
* `activeRunsAtClose` / `abortedRuns` / `unsettledRuns`, so `'agent-run'` must stay first.
|
|
5
|
+
*/
|
|
6
|
+
declare const RUNTIME_OPERATION_KINDS: readonly ["agent-run", "model-catalog", "manual-compaction", "team-operation", "embedding-call"];
|
|
3
7
|
type RuntimeOperationKind = typeof RUNTIME_OPERATION_KINDS[number];
|
|
4
8
|
interface RuntimeOperationCloseSummary {
|
|
5
9
|
readonly kind: RuntimeOperationKind;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/composition/lifecycle/types.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/composition/lifecycle/types.ts"],"mappings":";;;;;cAIa;KAID,8BAA8B;UAEzB;WACN,MAAM;WACN;WACA;WACA;WACA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=Object.freeze([`agent-run`,`model-catalog`,`manual-compaction`,`team-operation`]);export{e as RUNTIME_OPERATION_KINDS};
|
|
1
|
+
const e=Object.freeze([`agent-run`,`model-catalog`,`manual-compaction`,`team-operation`,`embedding-call`]);export{e as RUNTIME_OPERATION_KINDS};
|
|
2
2
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../src/composition/lifecycle/types.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../src/composition/lifecycle/types.ts"],"sourcesContent":["/**\n * Append-only. `RuntimeOperations.beginClose()` reads `operations[0]` as the source of\n * `activeRunsAtClose` / `abortedRuns` / `unsettledRuns`, so `'agent-run'` must stay first.\n */\nexport const RUNTIME_OPERATION_KINDS = Object.freeze([\n 'agent-run', 'model-catalog', 'manual-compaction', 'team-operation', 'embedding-call',\n] as const)\n\nexport type RuntimeOperationKind = typeof RUNTIME_OPERATION_KINDS[number]\n\nexport interface RuntimeOperationCloseSummary {\n readonly kind: RuntimeOperationKind\n readonly activeAtClose: number\n readonly aborted: number\n readonly settled: number\n readonly unsettled: number\n}\n\nexport interface QuiescenceReport {\n readonly quiescenceEnd: 'settled' | 'timeout' | 'caller-abort'\n readonly deadlineReached: boolean\n readonly activeRunsAtClose: number\n readonly abortedRuns: number\n readonly unsettledRuns: number\n readonly operations: readonly RuntimeOperationCloseSummary[]\n}\n\nexport interface OperationLease {\n readonly signal: AbortSignal\n readonly whenSealed: Promise<void>\n /** The callback must commit synchronously and must not perform user code or await. */\n publish(commit: () => void): boolean\n settle(): void\n}\n\nexport interface OperationOptions {\n readonly signal?: AbortSignal\n readonly timeoutMs?: number\n}\n"],"mappings":"AAIA,MAAa,EAA0B,OAAO,OAAO,CACnD,YAAa,gBAAiB,oBAAqB,iBAAkB,gBACvE,CAAU"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{checkPreflightAbort as e}from"./common/errors.js";import{
|
|
1
|
+
import{checkPreflightAbort as e}from"./common/errors.js";import{captureProviderMethods as t}from"./provider/preflight.js";import{captureEmbeddingMethods as n,preflightRuntimeProviders as r}from"./embedding/preflight.js";import{captureExporterMethods as i,preflightExporterIdentities as a}from"./exporter/preflight.js";function o(o,s,c,l){e(l);let u=r(o,c,l),d=a(s,l),f=t(u.generation,l),p=n(u.embedding,l),m=i(d,l);return Object.freeze({selection:u.generation,providers:f,embeddingProviders:p,exporters:m})}export{o as preflightRuntimeCapabilities};
|
|
2
2
|
//# sourceMappingURL=preflight.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.js","names":[],"sources":["../../src/composition/preflight.ts"],"sourcesContent":["import { checkPreflightAbort } from './common/errors.ts'\nimport { captureExporterMethods, preflightExporterIdentities } from './exporter/preflight.ts'\nimport type { RuntimeObservationExporterRegistration } from './exporter/types.ts'\nimport { captureProviderMethods
|
|
1
|
+
{"version":3,"file":"preflight.js","names":[],"sources":["../../src/composition/preflight.ts"],"sourcesContent":["import { checkPreflightAbort } from './common/errors.ts'\nimport {\n captureEmbeddingMethods, preflightRuntimeProviders,\n type CapturedEmbeddingProvider, type RuntimeProviderPlan,\n} from './embedding/preflight.ts'\nimport { captureExporterMethods, preflightExporterIdentities } from './exporter/preflight.ts'\nimport type { RuntimeObservationExporterRegistration } from './exporter/types.ts'\nimport { captureProviderMethods } from './provider/preflight.ts'\nimport type { CapturedProvider, ProviderSelection } from './provider/types.ts'\n\nexport type { ProviderPreflightFailure, RuntimeProviderPlan } from './embedding/preflight.ts'\n\nexport interface RuntimeCapabilityPlan {\n readonly selection: ProviderSelection\n readonly providers: readonly CapturedProvider[]\n /** Embedding plugins, captured but not yet activated (Requirement 11.6). */\n readonly embeddingProviders: readonly CapturedEmbeddingProvider[]\n readonly exporters: readonly RuntimeObservationExporterRegistration[]\n}\n\n/**\n * The whole runtime identity pass must finish before the first executable property\n * is read.\n *\n * `providers` is swept as ONE list across both plugin kinds, so every identity\n * failure in it is reported together and no plugin method is read while any of\n * them stands (Requirement 11.6).\n */\nexport function preflightRuntimeCapabilities(\n providers: unknown, exporters: unknown, defaultProvider?: unknown, signal?: AbortSignal,\n): RuntimeCapabilityPlan {\n checkPreflightAbort(signal)\n const providerPlan: RuntimeProviderPlan = preflightRuntimeProviders(providers, defaultProvider, signal)\n const exporterPlan = preflightExporterIdentities(exporters, signal)\n const capturedProviders = captureProviderMethods(providerPlan.generation, signal)\n const capturedEmbedding = captureEmbeddingMethods(providerPlan.embedding, signal)\n const capturedExporters = captureExporterMethods(exporterPlan, signal)\n return Object.freeze({\n selection: providerPlan.generation, providers: capturedProviders,\n embeddingProviders: capturedEmbedding, exporters: capturedExporters,\n })\n}\n"],"mappings":"8TA4BA,SAAgB,EACd,EAAoB,EAAoB,EAA2B,EAC5C,CACvB,EAAoB,CAAM,EAC1B,IAAM,EAAoC,EAA0B,EAAW,EAAiB,CAAM,EAChG,EAAe,EAA4B,EAAW,CAAM,EAC5D,EAAoB,EAAuB,EAAa,WAAY,CAAM,EAC1E,EAAoB,EAAwB,EAAa,UAAW,CAAM,EAC1E,EAAoB,EAAuB,EAAc,CAAM,EACrE,OAAO,OAAO,OAAO,CACnB,UAAW,EAAa,WAAY,UAAW,EAC/C,mBAAoB,EAAmB,UAAW,CACpD,CAAC,CACH"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{AgentSdkError as e}from"../../errors/agent-sdk-error.js";import{arrayData as t,boundedText as n}from"../../capability/common/data.js";import{beginCoreCapabilityOperation as r}from"../../platform/capability-operation.js";import{COMPOSITION_LIMITS as i}from"../../capability/common/config.js";import{AgentRuntimeConstructionError as a,checkPreflightAbort as o}from"../common/errors.js";const s=()=>new e(`Provider registration is outside setup or cleanup`,`PROVIDER_REGISTRAR_SEALED`),c=()=>new e(`Provider registration must cover its declared routes exactly once`,`PROVIDER_CLAIMS_INVALID`);function
|
|
1
|
+
import{AgentSdkError as e}from"../../errors/agent-sdk-error.js";import{arrayData as t,boundedText as n}from"../../capability/common/data.js";import{beginCoreCapabilityOperation as r}from"../../platform/capability-operation.js";import{COMPOSITION_LIMITS as i}from"../../capability/common/config.js";import{AgentRuntimeConstructionError as a,checkPreflightAbort as o}from"../common/errors.js";const s=()=>new e(`Provider registration is outside setup or cleanup`,`PROVIDER_REGISTRAR_SEALED`),c=()=>new e(`Provider registration must cover its declared routes exactly once`,`PROVIDER_CLAIMS_INVALID`),l=s,u=c;function d(e){if((typeof e!=`object`||!e)&&typeof e!=`function`)return!1;let t;try{t=Reflect.get(e,`then`)}catch{return!0}return typeof t==`function`&&(new Promise((n,r)=>{Reflect.apply(t,e,[n,r])}).catch(()=>void 0),!0)}function f(e,t){return Object.freeze({kind:`provider-registration`,id:e,status:t===void 0?`closed`:t===`CAPABILITY_CLEANUP_TIMEOUT`?`timed-out`:`failed`,...t===void 0?{}:{error:Object.freeze({code:t,stage:`provider-cleanup`,message:`Provider cleanup did not complete synchronously`})}})}function p(e,s,c,p,m,h){o(p);let g=h?.installed??[],_=g.length;for(let[h,v]of s.entries()){let s=r(c.child({providerIndex:_+h}),`core-provider`,`setup`),y=`setup`,b=`CAPABILITY_STARTUP_FAILED`,x,S,C=!1,w=!1,T,E,D=`provider-${_+h}`,O=()=>{if(C)return x??f(D);w||(E??=p?.aborted===!0),C=!0,y=`cleanup`;let e;try{w||(T??=m?.rollback()),S!==void 0&&(T!==void 0&&T.now()>=T.at?e=`CAPABILITY_CLEANUP_TIMEOUT`:d(S())&&(e=`PROVIDER_CLEANUP_ASYNC_UNSUPPORTED`))}catch{e=`CAPABILITY_CLEANUP_FAILED`}finally{y=`closed`}let t=f(D,e);return x=t,t};try{if(o(p),m!==void 0&&m.startup.now()>=m.startup.at)throw b=`CAPABILITY_STARTUP_TIMEOUT`,Error(`Provider startup deadline exhausted`);let r=e.install({id:v.id,displayName:v.displayName,family:v.family,setup(e){let r=[],a=()=>{if(y!==`setup`)throw l();o(p)},f=()=>{if(y!==`setup`&&y!==`cleanup`&&y!==`closed`)throw l()},m=(e,o)=>{a();let s=t(e,i.routesPerProvider).map(e=>n(e,i.identityBytes)),c=new Set(r.filter(e=>e.active&&e!==o).flatMap(e=>e.routes));for(let e of s){if(!v.routes.includes(e)||c.has(e))throw u();c.add(e)}return Object.freeze(s)},h=Object.freeze({logger:c,registerAdapter(t,n){let i=m(t);if(i.length===0)throw u();let a=e.registerAdapter(i,n),o={active:!0,routes:i};r.push(o);let s=(()=>{f(),o.active&&=(a(),!1)});return s.replace=e=>{if(!o.active)throw l();let t=m(e,o);a.replace(t),o.routes=t},Object.freeze(s)},use(t){a();let n=e.use(t),r=!1;return()=>{f(),!r&&(r=!0,n())}}});try{let e=v.setup(h);if(y=`sealed`,S=typeof e==`function`?e:void 0,d(e))throw b=`PROVIDER_SETUP_ASYNC_UNSUPPORTED`,Error(`Asynchronous provider setup is unsupported`);if(e!==void 0&&typeof e!=`function`)throw Error(`Invalid provider setup result`);o(p);let t=r.filter(e=>e.active).flatMap(e=>e.routes);if(t.length!==v.routes.length||v.routes.some(e=>!t.includes(e)))throw u();return s.success(),()=>{if(O().status!==`closed`)throw Error(`Provider cleanup did not complete`)}}catch(e){throw p?.aborted===!0?s.abort():s.fail(e),E=p?.aborted===!0,y=`sealed`,S!==void 0&&(x=O()),e}finally{y===`setup`&&(y=`sealed`)}}});w=!0;let a;g.push(Object.freeze({id:v.id,close(e){if(a!==void 0)return a;T=e,a=f(D);try{r()}catch{a=x??f(D,`CAPABILITY_CLEANUP_FAILED`)}return a}})),o(p)}catch(e){let t=E??p?.aborted===!0;t?s.abort():s.fail(e);let n=[];x!==void 0&&n.push(x);let r=m?.rollback();for(let e of[...g].reverse())n.push(e.close(r));throw new a({failureCode:t?`CAPABILITY_STARTUP_ABORTED`:b,stage:`provider-setup`,reason:t?`aborted`:b===`CAPABILITY_STARTUP_TIMEOUT`?`timed-out`:`failed`,component:{kind:`provider-plugin`,id:D},cleanup:n})}}return Object.freeze([...g])}export{p as activateProviders,d as asynchronous,f as closeRow,c as providerClaimError,s as providerLifecycleError};
|
|
2
2
|
//# sourceMappingURL=activation.js.map
|