akm-cli 0.9.14 → 0.9.15-beta.2
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/CHANGELOG.md +559 -0
- package/STABILITY.md +6 -3
- package/dist/akm +54 -1
- package/dist/akm-migrate +34 -1
- package/dist/assets/prompts/reflect-feedback-framing.md +1 -0
- package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -0
- package/dist/assets/prompts/reflect-llm-schema-contract.md +2 -0
- package/dist/assets/tasks/core/improve.yml +1 -1
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +1 -1
- package/dist/cli/retired-commands.js +0 -1
- package/dist/cli/shared.js +9 -0
- package/dist/cli/unknown-flags.js +1 -0
- package/dist/cli.js +40 -3
- package/dist/commands/config-cli.js +85 -3
- package/dist/commands/env/env-cli.js +1 -42
- package/dist/commands/env/env.js +1 -1
- package/dist/commands/env/secret-cli.js +1 -2
- package/dist/commands/health/checks.js +357 -63
- package/dist/commands/health/engine-usage.js +45 -0
- package/dist/commands/health/improve-metrics.js +18 -0
- package/dist/commands/health/llm-usage.js +41 -1
- package/dist/commands/health/plugin-staleness.js +7 -3
- package/dist/commands/health/version-drift.js +93 -0
- package/dist/commands/health/windows.js +3 -1
- package/dist/commands/health.js +44 -9
- package/dist/commands/improve/consolidate/chunking.js +4 -2
- package/dist/commands/improve/improve-cli.js +99 -5
- package/dist/commands/improve/improve-report.js +154 -0
- package/dist/commands/improve/improve-result-file.js +45 -33
- package/dist/commands/improve/improve-strategies.js +133 -3
- package/dist/commands/improve/improve-usage-report.js +182 -0
- package/dist/commands/improve/improve.js +40 -3
- package/dist/commands/improve/locks.js +28 -78
- package/dist/commands/improve/planner.js +1 -0
- package/dist/commands/improve/preparation.js +9 -1
- package/dist/commands/improve/reflect.js +44 -4
- package/dist/commands/models-cli.js +50 -1
- package/dist/commands/proposal/repository.js +8 -3
- package/dist/commands/proposal/validators/proposal-quality-validators.js +41 -6
- package/dist/commands/proposal/validators/proposal-validators.js +24 -0
- package/dist/commands/read/search-cli.js +38 -2
- package/dist/commands/read/show.js +103 -4
- package/dist/commands/sources/info.js +5 -1
- package/dist/commands/sources/installed-stashes.js +58 -16
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/stash-cli.js +48 -0
- package/dist/commands/tasks/tasks-cli.js +49 -2
- package/dist/commands/workflow-cli.js +86 -12
- package/dist/core/asset/markdown-fragments.js +35 -0
- package/dist/core/config/config-schema.js +14 -0
- package/dist/core/config/config.js +302 -24
- package/dist/core/config/schema/embedding.js +41 -0
- package/dist/core/env-secret-ref.js +58 -5
- package/dist/core/errors.js +30 -0
- package/dist/core/file-lock.js +49 -15
- package/dist/core/improve-result.js +51 -0
- package/dist/core/loopback.js +17 -0
- package/dist/core/parent-watchdog.js +64 -0
- package/dist/core/paths.js +11 -0
- package/dist/core/run-lock.js +107 -0
- package/dist/core/sensitive-marker-path.js +19 -0
- package/dist/core/state-db.js +74 -14
- package/dist/indexer/index-rebuild-lock.js +73 -0
- package/dist/indexer/index-writer-lock.js +40 -1
- package/dist/indexer/index-written-assets.js +29 -1
- package/dist/indexer/indexer.js +93 -29
- package/dist/indexer/materialize-embeddings.js +564 -48
- package/dist/indexer/search/db-search.js +49 -2
- package/dist/indexer/search/search-source.js +23 -1
- package/dist/integrations/agent/engine-resolution.js +96 -6
- package/dist/integrations/agent/execution-definitions.js +6 -15
- package/dist/integrations/agent/execution-lowering.js +6 -1
- package/dist/integrations/agent/execution-preparation.js +1 -1
- package/dist/integrations/agent/model-map.js +123 -20
- package/dist/integrations/agent/prompts.js +40 -8
- package/dist/integrations/agent/runner-dispatch.js +9 -3
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/llm/client.js +8 -3
- package/dist/llm/embedder.js +20 -8
- package/dist/llm/embedders/local.js +10 -2
- package/dist/llm/embedders/remote.js +497 -32
- package/dist/output/shapes/helpers.js +38 -2
- package/dist/output/shapes/models-list.js +16 -0
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/shapes.js +4 -0
- package/dist/output/text/command-format.js +29 -0
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/improve-report.js +27 -0
- package/dist/{commands/env/marker-path.js → output/text/models.js} +4 -3
- package/dist/output/text/show-format.js +4 -0
- package/dist/output/text.js +4 -0
- package/dist/scripts/akm-migrate-node.js +25146 -21759
- package/dist/scripts/akm-migrate.js +24271 -20885
- package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
- package/dist/storage/repositories/improve-runs-repository.js +34 -0
- package/dist/storage/repositories/index-fts-repository.js +49 -6
- package/dist/storage/repositories/index-schema.js +16 -0
- package/dist/storage/repositories/index-vec-repository.js +30 -0
- package/dist/storage/repositories/workflow-runs-repository.js +55 -18
- package/dist/tasks/backends/cron.js +14 -7
- package/dist/tasks/run/run-native-task.js +23 -1
- package/dist/tasks/run/run-workflow-task.js +16 -0
- package/dist/workflows/exec/child-workflow.js +2 -2
- package/dist/workflows/exec/dispatch-redaction.js +21 -9
- package/dist/workflows/exec/run-workflow.js +6 -5
- package/dist/workflows/runtime/runs.js +33 -5
- package/docs/migration/release-notes/0.9.15.md +133 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/reference/cli.md +271 -30
- package/docs/reference/configuration.md +234 -21
- package/docs/reference/data-and-telemetry.md +8 -0
- package/docs/reference/tasks.md +16 -1
- package/docs/reference/workflow-schema.md +5 -1
- package/package.json +1 -1
- package/schemas/akm-config.json +47 -0
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* `frontmatter` is optional — the proposal queue parses it from `content`
|
|
26
26
|
* during validation. We carry it through if the agent supplies it.
|
|
27
27
|
*/
|
|
28
|
+
import reflectFeedbackFraming from "../../assets/prompts/reflect-feedback-framing.md" with { type: "text" };
|
|
28
29
|
import reflectLlmFramedContract from "../../assets/prompts/reflect-llm-framed-contract.md" with { type: "text" };
|
|
29
30
|
import reflectLlmSchemaContract from "../../assets/prompts/reflect-llm-schema-contract.md" with { type: "text" };
|
|
30
31
|
import reflectOutputRepair from "../../assets/prompts/reflect-output-repair.md" with { type: "text" };
|
|
@@ -56,6 +57,25 @@ function hintForType(type) {
|
|
|
56
57
|
function knownTypeList() {
|
|
57
58
|
return [...placementTypes()].sort().join(", ");
|
|
58
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Default cap (in characters) on the asset content injected into
|
|
62
|
+
* {@link buildReflectPrompt}, and the floor a caller-supplied
|
|
63
|
+
* {@link ReflectPromptInput.contentBudgetChars} is never allowed to go below.
|
|
64
|
+
* Exists to stay well under OS ARG_MAX when the prompt is passed as a CLI
|
|
65
|
+
* argument to opencode/claude — large assets (wiki snapshots, long runbooks)
|
|
66
|
+
* would otherwise trigger E2BIG on posix_spawn. Agent/SDK runners always use
|
|
67
|
+
* this flat value; the direct-LLM path can raise it per #952 (see the reflect
|
|
68
|
+
* dispatch site in `src/commands/improve/reflect.ts`).
|
|
69
|
+
*/
|
|
70
|
+
export const REFLECT_CONTENT_CAP = 12_000;
|
|
71
|
+
/**
|
|
72
|
+
* Marker appended to truncated asset content when it exceeds the active
|
|
73
|
+
* content budget (#952). Exported so `sanitizeReflectPayload` can detect a
|
|
74
|
+
* model that echoed this notice back into its rewrite instead of proposing
|
|
75
|
+
* real content, and so the output contracts can reference the exact string
|
|
76
|
+
* to forbid.
|
|
77
|
+
*/
|
|
78
|
+
export const REFLECT_TRUNCATION_MARKER = "... [truncated — focus on the visible portion]";
|
|
59
79
|
/**
|
|
60
80
|
* Common envelope every prompt asks the agent to honour when NO draft file
|
|
61
81
|
* path is available. The wrapper code uses `JSON.parse(stdout)` to extract
|
|
@@ -87,6 +107,7 @@ function fileWriteContract(draftFilePath) {
|
|
|
87
107
|
`Write the complete improved asset content to: ${draftFilePath}`,
|
|
88
108
|
"Use your file-editing tools to create or overwrite that file.",
|
|
89
109
|
"Do NOT output JSON to stdout. Do NOT print the file contents. Just write the file.",
|
|
110
|
+
`Never include the text "${REFLECT_TRUNCATION_MARKER}" or any other content from outside the provided asset content in the file you write.`,
|
|
90
111
|
"When done, output a single line on stdout: DRAFT_WRITTEN confidence=<0.0-1.0>",
|
|
91
112
|
"`confidence` is REQUIRED and must be your honest self-rated [0, 1] score for this proposal:",
|
|
92
113
|
" • 0.90+ — fixes a real defect or adds load-bearing missing content; reviewer would clearly accept.",
|
|
@@ -124,10 +145,14 @@ export function reflectLlmResponseContract(mode, targetScoped) {
|
|
|
124
145
|
.replace("{{FIELD_RULE}}", targetScoped
|
|
125
146
|
? "The response has exactly the required fields `content`, `confidence`, and `frontmatterPatch`; do not echo `ref` or arbitrary `frontmatter`."
|
|
126
147
|
: "The response has exactly the required fields `ref`, `content`, `confidence`, and `frontmatterPatch`; `ref` must identify the selected asset.")
|
|
148
|
+
.replaceAll("{{TRUNCATION_MARKER}}", REFLECT_TRUNCATION_MARKER)
|
|
127
149
|
.trim();
|
|
128
150
|
}
|
|
129
151
|
const refLine = targetScoped ? "" : "AKM_REFLECT_REF: <selected asset ref>\n";
|
|
130
|
-
return reflectLlmFramedContract
|
|
152
|
+
return reflectLlmFramedContract
|
|
153
|
+
.replace("{{REF_LINE}}", refLine)
|
|
154
|
+
.replaceAll("{{TRUNCATION_MARKER}}", REFLECT_TRUNCATION_MARKER)
|
|
155
|
+
.trim();
|
|
131
156
|
}
|
|
132
157
|
export function buildReflectOutputRepairPrompt(mode, targetScoped) {
|
|
133
158
|
return reflectOutputRepair.replace("{{OUTPUT_CONTRACT}}", reflectLlmResponseContract(mode, targetScoped)).trim();
|
|
@@ -194,6 +219,10 @@ export function buildReflectPrompt(input) {
|
|
|
194
219
|
}
|
|
195
220
|
// Change 3 & 4 — feedback moved before asset content; missing else branch added
|
|
196
221
|
if (input.feedback && input.feedback.length > 0) {
|
|
222
|
+
// #952 — feedback lines are unverified reports, not verified facts. Without
|
|
223
|
+
// this caveat, models fabricated whole new sections asserting whatever a
|
|
224
|
+
// feedback line claimed (invented incident dates, ports, disk layouts).
|
|
225
|
+
sections.push(reflectFeedbackFraming.trim());
|
|
197
226
|
sections.push("Recent feedback / signals:");
|
|
198
227
|
for (const line of input.feedback)
|
|
199
228
|
sections.push(`- ${line}`);
|
|
@@ -238,17 +267,20 @@ export function buildReflectPrompt(input) {
|
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
if (input.assetContent?.trim()) {
|
|
241
|
-
// Cap
|
|
242
|
-
// passed as a CLI argument to opencode/claude
|
|
243
|
-
// long runbooks) would otherwise trigger
|
|
244
|
-
|
|
270
|
+
// Cap defaults to REFLECT_CONTENT_CAP (12 000 chars) to stay well under OS
|
|
271
|
+
// ARG_MAX when the prompt is passed as a CLI argument to opencode/claude
|
|
272
|
+
// — large assets (wiki snapshots, long runbooks) would otherwise trigger
|
|
273
|
+
// E2BIG on posix_spawn. Direct-LLM callers never touch argv, so they may
|
|
274
|
+
// pass a larger, context-aware `contentBudgetChars` (#952); it is never
|
|
275
|
+
// allowed below the flat floor, which the caller enforces before calling in.
|
|
276
|
+
const contentCap = input.contentBudgetChars ?? REFLECT_CONTENT_CAP;
|
|
245
277
|
const body = input.assetContent.trimEnd();
|
|
246
|
-
const truncated = body.length >
|
|
278
|
+
const truncated = body.length > contentCap;
|
|
247
279
|
sections.push(truncated
|
|
248
|
-
? `Current asset content (first ${
|
|
280
|
+
? `Current asset content (first ${contentCap} chars — full asset is ${body.length} chars):`
|
|
249
281
|
: "Current asset content (verbatim):");
|
|
250
282
|
sections.push("```");
|
|
251
|
-
sections.push(truncated ? `${body.slice(0,
|
|
283
|
+
sections.push(truncated ? `${body.slice(0, contentCap)}\n${REFLECT_TRUNCATION_MARKER}` : body);
|
|
252
284
|
sections.push("```");
|
|
253
285
|
}
|
|
254
286
|
else if (input.ref) {
|
|
@@ -20,7 +20,7 @@ import { assertNever } from "../../core/assert.js";
|
|
|
20
20
|
import { collectSensitiveValues, isEnvPassthroughValueSafeToExpose, redactSensitiveText, redactSensitiveValue, } from "../../core/redaction.js";
|
|
21
21
|
import { spawnEnvNamesFor } from "../../core/spawn-env.js";
|
|
22
22
|
import { closeServer as disposeOpencodeSdkServers, opencodeSdkServerEnvironmentNames, runOpencodeSdk, } from "../harnesses/opencode-sdk/sdk-runner.js";
|
|
23
|
-
import { lookupApiKeyFileValue, lookupCredentialFromEnv, materializeLlmConnection, materializeLlmConnectionWithCredential, resolveLlmCredentialValue, } from "./engine-resolution.js";
|
|
23
|
+
import { lookupApiKeyFileValue, lookupApiKeySecretRefValue, lookupCredentialFromEnv, materializeLlmConnection, materializeLlmConnectionWithCredential, resolveLlmCredentialValue, } from "./engine-resolution.js";
|
|
24
24
|
import { materializeLlmRunnerConnection, materializeLlmRunnerConnectionWithCredential, } from "./runner.js";
|
|
25
25
|
import { runAgent } from "./spawn.js";
|
|
26
26
|
const liveRunnerDispatchLeases = new WeakMap();
|
|
@@ -134,10 +134,10 @@ export function acquireRunnerDispatchLease(spec, envSource = process.env) {
|
|
|
134
134
|
},
|
|
135
135
|
});
|
|
136
136
|
const primaryCredential = spec.kind === "llm"
|
|
137
|
-
? resolveLlmCredentialValue(spec.engine, spec.credential, spec.apiKeyFile, credentialSource)
|
|
137
|
+
? resolveLlmCredentialValue(spec.engine, spec.credential, spec.apiKeyFile, spec.apiKeySecretRef, credentialSource)
|
|
138
138
|
: undefined;
|
|
139
139
|
const fallbackCredential = spec.kind === "sdk"
|
|
140
|
-
? resolveLlmCredentialValue(spec.engine, spec.fallbackCredential, spec.fallbackApiKeyFile, credentialSource)
|
|
140
|
+
? resolveLlmCredentialValue(spec.engine, spec.fallbackCredential, spec.fallbackApiKeyFile, spec.fallbackApiKeySecretRef, credentialSource)
|
|
141
141
|
: undefined;
|
|
142
142
|
const handle = Object.create(null);
|
|
143
143
|
Object.defineProperty(handle, "toJSON", {
|
|
@@ -229,6 +229,12 @@ export function collectDispatchSensitiveValues(spec, opts, envSource = opts.envS
|
|
|
229
229
|
add(lookupApiKeyFileValue(spec.apiKeyFile));
|
|
230
230
|
if (spec.kind === "sdk" && spec.fallbackApiKeyFile)
|
|
231
231
|
add(lookupApiKeyFileValue(spec.fallbackApiKeyFile));
|
|
232
|
+
// #953: a secret-store-backed credential is read at dispatch too — same
|
|
233
|
+
// best-effort scrub-set inclusion rationale.
|
|
234
|
+
if (spec.kind === "llm" && spec.apiKeySecretRef)
|
|
235
|
+
add(lookupApiKeySecretRefValue(spec.apiKeySecretRef));
|
|
236
|
+
if (spec.kind === "sdk" && spec.fallbackApiKeySecretRef)
|
|
237
|
+
add(lookupApiKeySecretRefValue(spec.fallbackApiKeySecretRef));
|
|
232
238
|
if (spec.kind !== "llm") {
|
|
233
239
|
for (const value of Object.values(spec.profile.env ?? {}))
|
|
234
240
|
add(value);
|
|
@@ -9,6 +9,7 @@ export function materializeLlmRunnerConnection(runner) {
|
|
|
9
9
|
connection: runner.connection,
|
|
10
10
|
...(runner.credential ? { credential: runner.credential } : {}),
|
|
11
11
|
...(runner.apiKeyFile ? { apiKeyFile: runner.apiKeyFile } : {}),
|
|
12
|
+
...(runner.apiKeySecretRef ? { apiKeySecretRef: runner.apiKeySecretRef } : {}),
|
|
12
13
|
timeoutMs: runner.timeoutMs ?? null,
|
|
13
14
|
});
|
|
14
15
|
}
|
|
@@ -19,6 +20,7 @@ export function materializeLlmRunnerConnectionWithCredential(runner, credentialV
|
|
|
19
20
|
connection: runner.connection,
|
|
20
21
|
...(runner.credential ? { credential: runner.credential } : {}),
|
|
21
22
|
...(runner.apiKeyFile ? { apiKeyFile: runner.apiKeyFile } : {}),
|
|
23
|
+
...(runner.apiKeySecretRef ? { apiKeySecretRef: runner.apiKeySecretRef } : {}),
|
|
22
24
|
timeoutMs: runner.timeoutMs ?? null,
|
|
23
25
|
}, credentialValue);
|
|
24
26
|
}
|
package/dist/llm/client.js
CHANGED
|
@@ -290,12 +290,17 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
|
|
|
290
290
|
},
|
|
291
291
|
}
|
|
292
292
|
: {};
|
|
293
|
+
// #949: which of these two wire forms a backend honors is a fact about the
|
|
294
|
+
// backend (and any gateway in front of it), not about akm's own `provider`
|
|
295
|
+
// label — a llama.cpp build honors chat_template_kwargs, a bare
|
|
296
|
+
// `enable_thinking` is honored by nothing observed, and a gateway
|
|
297
|
+
// (freellmapi, Bifrost) can drop either one depending on how it was built.
|
|
298
|
+
// Send both whenever thinking is explicitly resolved so the same engine
|
|
299
|
+
// block keeps working across a direct vhost or any gateway in front of it.
|
|
293
300
|
const resolvedEnableThinking = options?.enableThinking ?? config.enableThinking;
|
|
294
301
|
const thinkingParams = resolvedEnableThinking === undefined
|
|
295
302
|
? {}
|
|
296
|
-
:
|
|
297
|
-
? { chat_template_kwargs: { enable_thinking: resolvedEnableThinking } }
|
|
298
|
-
: { enable_thinking: resolvedEnableThinking };
|
|
303
|
+
: { chat_template_kwargs: { enable_thinking: resolvedEnableThinking }, enable_thinking: resolvedEnableThinking };
|
|
299
304
|
const reasoningEffortParams = config.reasoningEffort === undefined ? {} : { reasoning_effort: config.reasoningEffort };
|
|
300
305
|
const requestBody = JSON.stringify({
|
|
301
306
|
model: config.model,
|
package/dist/llm/embedder.js
CHANGED
|
@@ -100,32 +100,44 @@ async function embedOnce(text, embeddingConfig, signal) {
|
|
|
100
100
|
* skipped and why; the result array holds `undefined` at those indices.
|
|
101
101
|
* Uses the LocalEmbedder.embedBatch path for the local transformer pipeline,
|
|
102
102
|
* which processes texts in chunks of 32 for genuine batched inference.
|
|
103
|
+
*
|
|
104
|
+
* `onBatch`, when given, fires once per provider/local batch as it completes
|
|
105
|
+
* (#954) so a caller can commit each batch's rows durably as they land
|
|
106
|
+
* rather than buffering the whole call — see `EmbeddingBatchCommit`.
|
|
103
107
|
*/
|
|
104
|
-
export async function embedBatch(texts, embeddingConfig, signal, onSkip) {
|
|
105
|
-
if (embedderOverrides?.embedBatch)
|
|
106
|
-
return embedderOverrides.embedBatch(texts, embeddingConfig, signal);
|
|
108
|
+
export async function embedBatch(texts, embeddingConfig, signal, onSkip, onBatch) {
|
|
109
|
+
if (embedderOverrides?.embedBatch) {
|
|
110
|
+
return embedderOverrides.embedBatch(texts, embeddingConfig, signal, onSkip, onBatch);
|
|
111
|
+
}
|
|
107
112
|
if (texts.length === 0)
|
|
108
113
|
return [];
|
|
109
114
|
// Deterministic mode (env-gated, test/bench only): model-free, stable.
|
|
110
115
|
if (isDeterministicEmbedEnabled()) {
|
|
111
|
-
|
|
116
|
+
const embeddings = texts.map((t) => deterministicEmbed(t));
|
|
117
|
+
// One onBatch commit for the whole call, like the local/remote batched
|
|
118
|
+
// paths — firing once per text opened one materializer transaction per
|
|
119
|
+
// entry in deterministic (test/bench) mode.
|
|
120
|
+
onBatch?.(embeddings.map((_embedding, i) => i), embeddings);
|
|
121
|
+
return embeddings;
|
|
112
122
|
}
|
|
113
123
|
if (embeddingConfig && hasRemoteEndpoint(embeddingConfig)) {
|
|
114
|
-
return new RemoteEmbedder(embeddingConfig).embedBatch(texts, signal, onSkip);
|
|
124
|
+
return new RemoteEmbedder(embeddingConfig).embedBatch(texts, signal, onSkip, onBatch);
|
|
115
125
|
}
|
|
116
126
|
// Local transformer: use the batched path (chunks of 32 via LocalEmbedder).
|
|
117
127
|
// When a localModel override is set we cannot share the singleton (which uses
|
|
118
128
|
// the default model), so fall back to per-text embedWithModel in that case.
|
|
119
129
|
const localModel = embeddingConfig?.localModel;
|
|
120
130
|
if (!localModel) {
|
|
121
|
-
return getLocalEmbedder().embedBatch(texts, signal);
|
|
131
|
+
return getLocalEmbedder().embedBatch(texts, signal, onBatch);
|
|
122
132
|
}
|
|
123
133
|
const results = [];
|
|
124
|
-
for (const text of texts) {
|
|
134
|
+
for (const [i, text] of texts.entries()) {
|
|
125
135
|
if (signal?.aborted) {
|
|
126
136
|
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
127
137
|
}
|
|
128
|
-
|
|
138
|
+
const embedding = await getLocalEmbedder().embedWithModel(text, localModel);
|
|
139
|
+
results.push(embedding);
|
|
140
|
+
onBatch?.([i], [embedding]);
|
|
129
141
|
}
|
|
130
142
|
return results;
|
|
131
143
|
}
|
|
@@ -81,8 +81,13 @@ export class LocalEmbedder {
|
|
|
81
81
|
* Embed a batch of texts. Processes in chunks of `LOCAL_BATCH_SIZE` (32) so
|
|
82
82
|
* the transformers pipeline can run genuine batched inference rather than one
|
|
83
83
|
* call per text. Each chunk is checked against the AbortSignal between calls.
|
|
84
|
+
*
|
|
85
|
+
* `onBatch`, when given, fires once per chunk (indices into `texts` + the
|
|
86
|
+
* chunk's embeddings) as it completes (#954) — parity with
|
|
87
|
+
* `RemoteEmbedder.embedBatch`'s per-provider-batch commit callback, so a
|
|
88
|
+
* caller committing per batch does not need to special-case the local path.
|
|
84
89
|
*/
|
|
85
|
-
async embedBatch(texts, signal) {
|
|
90
|
+
async embedBatch(texts, signal, onBatch) {
|
|
86
91
|
if (texts.length === 0)
|
|
87
92
|
return [];
|
|
88
93
|
if (signal?.aborted) {
|
|
@@ -109,9 +114,12 @@ export class LocalEmbedder {
|
|
|
109
114
|
if (batch !== chunk.length || batchResult.data.length !== batch * dim) {
|
|
110
115
|
throw new Error("unexpected pipeline return shape for batch input");
|
|
111
116
|
}
|
|
117
|
+
const chunkEmbeddings = [];
|
|
112
118
|
for (let row = 0; row < chunk.length; row++) {
|
|
113
|
-
|
|
119
|
+
chunkEmbeddings.push(Array.from(batchResult.data.subarray(row * dim, (row + 1) * dim)));
|
|
114
120
|
}
|
|
121
|
+
results.push(...chunkEmbeddings);
|
|
122
|
+
onBatch?.(Array.from({ length: chunk.length }, (_, row) => i + row), chunkEmbeddings);
|
|
115
123
|
}
|
|
116
124
|
return results;
|
|
117
125
|
}
|