From 4c320cbb6bfc5594ec832fc7a2aaaa4512126bd0 Mon Sep 17 00:00:00 2001 From: Dominic Nguyen Date: Tue, 29 Sep 2026 12:59:10 -0700 Subject: [PATCH 1/3] Read embedding settings from runtime config; warn on invalid ints --- ts/packages/aiclient/src/embeddingProvider.ts | 69 ++++++------------- ts/packages/aiclient/src/openai.ts | 9 +++ ts/packages/config/src/runtime/build.ts | 6 +- ts/packages/knowPro/src/conversation.ts | 7 +- 4 files changed, 38 insertions(+), 53 deletions(-) diff --git a/ts/packages/aiclient/src/embeddingProvider.ts b/ts/packages/aiclient/src/embeddingProvider.ts index ac974402d2..1606507e80 100644 --- a/ts/packages/aiclient/src/embeddingProvider.ts +++ b/ts/packages/aiclient/src/embeddingProvider.ts @@ -5,6 +5,7 @@ import { TextEmbeddingModel } from "./models.js"; import { createEmbeddingModel } from "./openai.js"; import { EnvVars } from "./apiTypes.js"; import { createLocalEmbeddingModel } from "./localEmbedding.js"; +import { getRuntimeConfig } from "./runtimeConfig.js"; import { createCopilotEmbeddingModel, DefaultCopilotEmbeddingModel, @@ -23,60 +24,40 @@ export type EmbeddingProvider = | "copilot" | "none"; -// Flattened form of the config `embedding:` section. -enum EmbeddingEnvVars { - PROVIDER = "TYPEAGENT_EMBEDDING_PROVIDER", - MODEL = "TYPEAGENT_EMBEDDING_MODEL", - CACHE_DIR = "TYPEAGENT_EMBEDDING_CACHE_DIR", - SIZE = "TYPEAGENT_EMBEDDING_SIZE", - MAX_BATCH_SIZE = "TYPEAGENT_EMBEDDING_MAX_BATCH_SIZE", -} - // Embedding sizes of well-known defaults, used when no size is configured. const LocalDefaultEmbeddingSize = 384; // Xenova/all-MiniLM-L6-v2 const HostedDefaultEmbeddingSize = 1536; // ada-002 / text-embedding-3-small -function readPositiveInt(name: string): number | undefined { - const raw = process.env[name]?.trim(); - if (!raw) return undefined; - const n = Number(raw); - return Number.isInteger(n) && n > 0 ? n : undefined; +// The `embedding:` section of the runtime config. +function getEmbeddingConfig() { + return getRuntimeConfig().embedding; } /** * The embedding vector size for the configured provider. An explicit - * `embedding.size` (`TYPEAGENT_EMBEDDING_SIZE`) always wins; otherwise the - * default model's size is used (set `size` when using a non-default model) (384 for the local MiniLM model, 1536 for - * hosted endpoints). Configuration only; never loads a model. + * `embedding.size` always wins; otherwise the default model's size is used: + * `LocalDefaultEmbeddingSize` for the local provider, + * `HostedDefaultEmbeddingSize` for hosted endpoints. Set `size` when using a + * non-default model. Configuration only; never loads a model. */ export function getEmbeddingSize(): number { - const configured = readPositiveInt(EmbeddingEnvVars.SIZE); + const configured = getEmbeddingConfig()?.size; if (configured !== undefined) return configured; return getEmbeddingProvider() === "local" ? LocalDefaultEmbeddingSize : HostedDefaultEmbeddingSize; } -function isEmbeddingProvider(value: string): value is EmbeddingProvider { - return ( - value === "local" || - value === "openai" || - value === "azure" || - value === "copilot" || - value === "none" - ); -} - /** * Determine the configured embedding provider using configuration only * (no network access, no model loading). An explicit - * `TYPEAGENT_EMBEDDING_PROVIDER` always wins; otherwise the provider is + * `embedding.provider` always wins; otherwise the provider is * inferred from the presence of hosted embedding endpoints, defaulting to * "none" when nothing is configured. */ export function getEmbeddingProvider(): EmbeddingProvider { - const explicit = process.env[EmbeddingEnvVars.PROVIDER]?.trim(); - if (explicit && isEmbeddingProvider(explicit)) { + const explicit = getEmbeddingConfig()?.provider; + if (explicit !== undefined) { return explicit; } if ( @@ -113,37 +94,31 @@ export function tryCreateEmbeddingModel( dimensions?: number, ): TextEmbeddingModel | undefined { const provider = getEmbeddingProvider(); + const config = getEmbeddingConfig(); switch (provider) { case "none": return undefined; case "local": return createLocalEmbeddingModel({ - model: process.env[EmbeddingEnvVars.MODEL]?.trim() || undefined, - cacheDir: - process.env[EmbeddingEnvVars.CACHE_DIR]?.trim() || - undefined, - maxBatchSize: readPositiveInt(EmbeddingEnvVars.MAX_BATCH_SIZE), + model: config?.model, + cacheDir: config?.cacheDir, + maxBatchSize: config?.maxBatchSize, }); case "copilot": return createCopilotEmbeddingModel( - process.env[EmbeddingEnvVars.MODEL]?.trim() || - DefaultCopilotEmbeddingModel, + config?.model ?? DefaultCopilotEmbeddingModel, undefined, undefined, { - dimensions: - dimensions ?? readPositiveInt(EmbeddingEnvVars.SIZE), - maxBatchSize: readPositiveInt( - EmbeddingEnvVars.MAX_BATCH_SIZE, - ), + dimensions: dimensions ?? config?.size, + maxBatchSize: config?.maxBatchSize, }, ); default: { - dimensions ??= readPositiveInt(EmbeddingEnvVars.SIZE); + dimensions ??= config?.size; const options = { - modelName: - process.env[EmbeddingEnvVars.MODEL]?.trim() || undefined, - maxBatchSize: readPositiveInt(EmbeddingEnvVars.MAX_BATCH_SIZE), + modelName: config?.model, + maxBatchSize: config?.maxBatchSize, }; return endpoint !== undefined ? createEmbeddingModel(endpoint, dimensions, options) diff --git a/ts/packages/aiclient/src/openai.ts b/ts/packages/aiclient/src/openai.ts index 2f025039a2..de7d550bf0 100644 --- a/ts/packages/aiclient/src/openai.ts +++ b/ts/packages/aiclient/src/openai.ts @@ -877,6 +877,15 @@ export function createEmbeddingModel( // https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input const maxBatchSize = Math.min(options?.maxBatchSize ?? 2048, 2048); + // Trace config overrides of the pool's settings for troubleshooting. + if ( + options?.modelName !== undefined || + options?.maxBatchSize !== undefined + ) { + debugOpenAI( + `Embedding overrides for ${pool.modelKey}: model=${options.modelName ?? settings.modelName}, maxBatchSize=${maxBatchSize}`, + ); + } const defaultParams: any = settings.provider === "azure" ? {} diff --git a/ts/packages/config/src/runtime/build.ts b/ts/packages/config/src/runtime/build.ts index 3c76c6c6f3..777b29df59 100644 --- a/ts/packages/config/src/runtime/build.ts +++ b/ts/packages/config/src/runtime/build.ts @@ -1006,7 +1006,8 @@ function buildEmbedding( }; } -// Pop a positive integer; malformed values are left in extra untouched. +// Pop a positive integer; malformed values are left in extra untouched +// and reported, so a typo (e.g. `size: -1`) does not silently fall back. function popPositiveInt( flat: Map, key: string, @@ -1015,6 +1016,9 @@ function popPositiveInt( if (raw === undefined) return undefined; const n = Number(raw); if (Number.isInteger(n) && n > 0) return n; + console.warn( + `${key}: expected a positive integer, got "${raw}"; ignoring.`, + ); flat.set(key, raw); return undefined; } diff --git a/ts/packages/knowPro/src/conversation.ts b/ts/packages/knowPro/src/conversation.ts index d61d90fc20..1ef425ad51 100644 --- a/ts/packages/knowPro/src/conversation.ts +++ b/ts/packages/knowPro/src/conversation.ts @@ -32,11 +32,8 @@ export function createConversationSettings( // May be undefined when no embedding provider is configured (e.g. Copilot // self-host without a local embedder). Embedding-backed indexes then no-op // and search degrades to exact/alias/edit-distance matching. - if (embeddingModel === undefined) { - embeddingModel = tryCreateEmbeddingModel(); - embeddingSize ??= getEmbeddingSize(); - } - embeddingSize ??= 1536; + embeddingModel ??= tryCreateEmbeddingModel(); + embeddingSize ??= getEmbeddingSize(); if (embeddingModel === undefined && !embeddingUnavailableAnnounced) { embeddingUnavailableAnnounced = true; console.warn( From 42ec617867087da54f54276a8fb9268125235b3e Mon Sep 17 00:00:00 2001 From: Dominic Nguyen Date: Tue, 29 Sep 2026 16:48:41 -0700 Subject: [PATCH 2/3] Refresh runtime config after changing embedding env vars --- .../agentServer/server/test/conversationNameIndex.spec.ts | 7 ++++++- ts/packages/copilot-memory-plugin/package.json | 1 + .../copilot-memory-plugin/src/shared/memory-client.ts | 3 +++ ts/pnpm-lock.yaml | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ts/packages/agentServer/server/test/conversationNameIndex.spec.ts b/ts/packages/agentServer/server/test/conversationNameIndex.spec.ts index ab6e1855b4..3039d412db 100644 --- a/ts/packages/agentServer/server/test/conversationNameIndex.spec.ts +++ b/ts/packages/agentServer/server/test/conversationNameIndex.spec.ts @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import type { TextEmbeddingModel } from "@typeagent/aiclient"; +import { + initRuntimeConfigFromProcessEnv, + type TextEmbeddingModel, +} from "@typeagent/aiclient"; import { createConversationNameIndex } from "../src/conversationNameIndex.js"; // A tiny deterministic embedding space so tests can assert semantic matches @@ -88,6 +91,7 @@ describe("conversationNameIndex", () => { try { process.env.TYPEAGENT_EMBEDDING_PROVIDER = "local"; delete process.env.AZURE_OPENAI_ENDPOINT_EMBEDDING; + initRuntimeConfigFromProcessEnv(); expect(() => createConversationNameIndex()).not.toThrow(); } finally { if (previousProvider === undefined) { @@ -101,6 +105,7 @@ describe("conversationNameIndex", () => { process.env.AZURE_OPENAI_ENDPOINT_EMBEDDING = previousAzureEndpoint; } + initRuntimeConfigFromProcessEnv(); } }); diff --git a/ts/packages/copilot-memory-plugin/package.json b/ts/packages/copilot-memory-plugin/package.json index 443eea7aa3..689d072556 100644 --- a/ts/packages/copilot-memory-plugin/package.json +++ b/ts/packages/copilot-memory-plugin/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", + "@typeagent/aiclient": "workspace:*", "@typeagent/config": "workspace:*", "@typeagent/conversation-memory": "workspace:*", "proper-lockfile": "^4.1.2", diff --git a/ts/packages/copilot-memory-plugin/src/shared/memory-client.ts b/ts/packages/copilot-memory-plugin/src/shared/memory-client.ts index 9965e05647..30d8bb39c2 100644 --- a/ts/packages/copilot-memory-plugin/src/shared/memory-client.ts +++ b/ts/packages/copilot-memory-plugin/src/shared/memory-client.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { initRuntimeConfigFromProcessEnv } from "@typeagent/aiclient"; import { loadConfigSync } from "@typeagent/config"; import { ConversationMemory, @@ -229,6 +230,8 @@ function ensureModelConfig(): void { delete process.env[key]; } } + // Rebuild the cached runtime config so the removed providers take effect. + initRuntimeConfigFromProcessEnv(); } async function openStore(paths: MemoryPaths): Promise { diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index a05e8d2519..cefa70efa0 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -4699,6 +4699,9 @@ importers: '@modelcontextprotocol/sdk': specifier: 1.26.0 version: 1.26.0(zod@3.25.76) + '@typeagent/aiclient': + specifier: workspace:* + version: link:../aiclient '@typeagent/config': specifier: workspace:* version: link:../config From bc7347a8bc166f9416725daeeab46f2c44587610 Mon Sep 17 00:00:00 2001 From: typeagent-bot Date: Tue, 29 Sep 2026 23:59:12 +0000 Subject: [PATCH 3/3] docs: regenerate README.AUTOGEN.md, command reference, and action browser --- ts/packages/copilot-memory-plugin/README.AUTOGEN.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ts/packages/copilot-memory-plugin/README.AUTOGEN.md b/ts/packages/copilot-memory-plugin/README.AUTOGEN.md index 1edf8608f7..e07d56b43f 100644 --- a/ts/packages/copilot-memory-plugin/README.AUTOGEN.md +++ b/ts/packages/copilot-memory-plugin/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # @typeagent/copilot-memory-plugin — AI-generated documentation @@ -26,6 +26,7 @@ _No public exports declared in `package.json`._ Workspace: +- [@typeagent/aiclient](../../packages/aiclient/README.md) - [@typeagent/config](../../packages/config/README.md) - [@typeagent/conversation-memory](../../packages/memory/conversation/README.md) @@ -45,6 +46,6 @@ _3 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `e6466ea5a66a5b1e3dc1ee6ee99b7068ab8b547c` on `2026-09-24T04:21:03.480Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/copilot-memory-plugin docs:verify-links` to spot-check._ +_Auto-generated against commit `42ec617867087da54f54276a8fb9268125235b3e` on `2026-09-29T23:56:44.528Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/copilot-memory-plugin docs:verify-links` to spot-check._