Skip to content

Fix/ai model selector#33

Merged
djanogly merged 8 commits intodevfrom
fix/ai-model-selector
Apr 16, 2026
Merged

Fix/ai model selector#33
djanogly merged 8 commits intodevfrom
fix/ai-model-selector

Conversation

@djanogly
Copy link
Copy Markdown
Contributor

This pull request introduces significant improvements to AI model selection and discovery in the workspace settings UI and backend. The main changes include dynamic discovery of available AI models from the configured AI gateway, improved normalization and validation of model IDs, and consistent handling of embedding models. The UI now allows users to select from discovered models or enter a raw model ID, with enhanced feedback and error handling. Backend logic has been refactored to support these features and ensure robust model management.

AI Model Discovery and Selection Improvements

  • Added dynamic discovery of available AI models from the AI gateway, replacing the previous static list. The backend now queries the gateway for models, filters out non-generation models, deduplicates, and falls back to the selected/default model if needed. This is reflected in both the backend (listAvailableModels as an authenticated action) and the frontend, which now fetches and displays discovered models. [1] [2] [3] [4] [5]
  • The settings UI now provides a dropdown of discovered models and an input for manual entry, with improved placeholder and helper text. The model value is normalized before saving or using it. [1] [2] [3] [4]

Model Normalization and Validation

  • Introduced normalization functions for model IDs in both frontend and backend, ensuring whitespace is trimmed and formats are consistent. The backend now validates model strings more robustly, providing clearer error messages for invalid formats. [1] [2] [3] [4]

Embedding Model Consistency

  • Centralized embedding model defaults and resolution logic using DEFAULT_CONTENT_EMBEDDING_MODEL and resolveContentEmbeddingModel across all relevant backend modules, ensuring consistent embedding model handling for both knowledge and content embeddings. [1] [2] [3] [4] [5] [6] [7]

Minor Improvements and Cleanup

  • The frontend no longer displays "text-embedding-3-large" as an embedding model option, focusing on supported/legacy models only.
  • The backend now filters out responses without a messageId in getConversationResponses, improving data integrity.

These changes collectively make AI model selection more flexible, robust, and user-friendly, while ensuring backend and frontend consistency.

@djanogly djanogly requested a review from Copilot April 16, 2026 04:58
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
opencom-landing Ready Ready Preview, Comment Apr 16, 2026 9:22pm
opencom-web Ready Ready Preview, Comment Apr 16, 2026 9:22pm

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Dynamic AI model discovery and embedding model dimension validation

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Implement dynamic AI model discovery from configured gateway instead of static list
  - Backend queries gateway API with timeout and filters non-generation models
  - Frontend fetches discovered models and allows manual entry with normalization
• Restrict embedding model selection to 1536-dimension vectors for index compatibility
  - Add embedding model validation and fallback logic
  - Remove unsupported text-embedding-3-large option from UI
• Improve model ID validation and error messages for better user guidance
  - Support both provider/model format and raw model IDs
  - Enhance diagnostic messages for invalid configurations
• Refactor AI gateway utilities for consistent provider label detection
  - Extract helper functions for API key, base URL, and provider detection
  - Support multiple gateway configurations (OpenAI, Vercel, custom)
Diagram
flowchart LR
  A["AI Gateway"] -->|"fetch /models"| B["discoverAvailableAIModels"]
  B -->|"filter & dedupe"| C["listAvailableModels action"]
  C -->|"discovered models"| D["Frontend useAIAgentSectionConvex"]
  D -->|"display dropdown + input"| E["AIAgentSection UI"]
  F["Embedding Model"] -->|"validate dimensions"| G["resolveContentEmbeddingModel"]
  G -->|"1536-dim compatible"| H["Save to aiAgentSettings"]
Loading

Grey Divider

File Changes

1. apps/web/src/app/settings/hooks/useSettingsSectionsConvex.ts ✨ Enhancement +42/-4

Convert model discovery to authenticated action with effect

apps/web/src/app/settings/hooks/useSettingsSectionsConvex.ts


2. packages/convex/convex/aiAgent.ts ✨ Enhancement +138/-20

Implement dynamic model discovery and embedding validation

packages/convex/convex/aiAgent.ts


3. packages/convex/convex/aiAgentActions.ts ✨ Enhancement +16/-12

Relax model format validation and improve error messages

packages/convex/convex/aiAgentActions.ts


View more (13)
4. packages/convex/convex/aiAgentActionsKnowledge.ts ✨ Enhancement +4/-2

Use centralized embedding model resolution logic

packages/convex/convex/aiAgentActionsKnowledge.ts


5. packages/convex/convex/embeddings.ts ✨ Enhancement +4/-2

Apply embedding model validation and normalization

packages/convex/convex/embeddings.ts


6. packages/convex/convex/lib/aiGateway.ts ✨ Enhancement +63/-6

Extract gateway configuration and provider detection utilities

packages/convex/convex/lib/aiGateway.ts


7. packages/convex/convex/lib/embeddingModels.ts ✨ Enhancement +40/-0

New module for embedding model validation and compatibility

packages/convex/convex/lib/embeddingModels.ts


8. packages/convex/convex/suggestions.ts ✨ Enhancement +8/-4

Use centralized embedding model resolution

packages/convex/convex/suggestions.ts


9. packages/convex/convex/schema/inboxConversationTables.ts ✨ Enhancement +1/-0

Add AI turn sequence tracking field

packages/convex/convex/schema/inboxConversationTables.ts


10. packages/convex/convex/schema/operationsAiTables.ts ✨ Enhancement +2/-1

Use constant for embedding vector dimensions

packages/convex/convex/schema/operationsAiTables.ts


11. packages/convex/convex/schema/operationsReportingTables.ts 🐞 Bug fix +2/-1

Make messageId optional and add attempt status field

packages/convex/convex/schema/operationsReportingTables.ts


12. packages/convex/convex/_generated/api.d.ts ⚙️ Configuration changes +2/-0

Add embedding models library to generated API types

packages/convex/convex/_generated/api.d.ts


13. packages/convex/tests/aiAgent.test.ts 🧪 Tests +13/-1

Update tests for action-based model discovery

packages/convex/tests/aiAgent.test.ts


14. packages/convex/tests/aiAgentRuntimeSafety.test.ts 🧪 Tests +16/-6

Relax model format validation tests

packages/convex/tests/aiAgentRuntimeSafety.test.ts


15. packages/convex/tests/embeddingModels.test.ts 🧪 Tests +33/-0

New tests for embedding model compatibility logic

packages/convex/tests/embeddingModels.test.ts


16. apps/web/src/app/settings/AIAgentSection.tsx ✨ Enhancement +21/-6

Add model dropdown and manual input with normalization

apps/web/src/app/settings/AIAgentSection.tsx


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 16, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. getConversationResponses missing return type📘 Rule violation ≡ Correctness
Description
The exported Convex query handler getConversationResponses does not declare an explicit return
type, so its contract is inferred and can drift as the implementation changes. This violates the
requirement to annotate shared query/mutation handler return types for stability across frontend and
cross-function callers.
Code

packages/convex/convex/aiAgent.ts[611]

+    return responses.filter((response) => response.messageId !== undefined).map((response) => ({
Evidence
PR Compliance ID 96845 requires explicit return type annotations on shared Convex query/mutation
handlers. In getConversationResponses, the handler is declared as `handler: async (ctx, args) => {
... } with no : Promise<...>` return type annotation, and this function’s implementation was
modified in this PR.

Rule 96845: Explicit return types for shared Convex query/mutation handlers
packages/convex/convex/aiAgent.ts[593-628]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`getConversationResponses` is an exported Convex `query` whose `handler` lacks an explicit return type annotation, leaving the API contract inferred.
## Issue Context
This handler is part of the shared Convex surface (exported query) and was modified in this PR, so its return type should be explicitly annotated to stabilize the contract.
## Fix Focus Areas
- packages/convex/convex/aiAgent.ts[593-628]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. updateSettings missing return type📘 Rule violation ≡ Correctness
Description
The exported Convex authMutation handler updateSettings does not declare an explicit return
type, making its mutation contract implicit. This violates the requirement to annotate shared
query/mutation handler return types for stable API contracts.
Code

packages/convex/convex/aiAgent.ts[R383-384]

+      if (args.embeddingModel !== undefined)
+        updates.embeddingModel = resolveContentEmbeddingModel(args.embeddingModel);
Evidence
PR Compliance ID 96845 requires explicit return type annotations on shared Convex query/mutation
handlers. updateSettings is an exported mutation and its handler is declared as `handler: async
(ctx, args) => { ... }` with no explicit return type while its implementation was modified in this
PR.

Rule 96845: Explicit return types for shared Convex query/mutation handlers
packages/convex/convex/aiAgent.ts[336-408]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`updateSettings` is an exported Convex mutation whose `handler` lacks an explicit return type annotation.
## Issue Context
This mutation is part of the shared Convex API surface and was modified in this PR; add an explicit return type (e.g., `Promise<Id<...>>` or an explicit result object) to prevent inferred-contract drift.
## Fix Focus Areas
- packages/convex/convex/aiAgent.ts[336-408]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. AIAgentSection mixes API+rendering📘 Rule violation ⌂ Architecture
Description
AIAgentSection performs orchestration (calling updateSettings to persist remote state) inside
the same component that renders the settings UI, rather than isolating orchestration in a
controller/hook/container. This conflicts with the requirement to separate orchestration logic from
rendering in UI components.
Code

apps/web/src/app/settings/AIAgentSection.tsx[R51-64]

+    const nextModel = normalizeModelValue(model);
try {
await updateSettings({
  workspaceId,
  enabled,
-        model,
+        model: nextModel,
  confidenceThreshold,
  knowledgeSources: knowledgeSources as ("articles" | "internalArticles" | "snippets")[],
  personality: personality || undefined,
  handoffMessage: handoffMessage || undefined,
  suggestionsEnabled,
  embeddingModel,
});
+      setModel(nextModel);
Evidence
PR Compliance ID 96868 requires separating orchestration from rendering in UI components. The
changed code shows the component directly invoking the backend mutation (updateSettings(...))
within the rendering component’s logic (handleSave).

Rule 96868: Separate orchestration logic from rendering in UI components
apps/web/src/app/settings/AIAgentSection.tsx[48-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `AIAgentSection` UI component contains orchestration logic (persisting settings via `updateSettings`) alongside rendering, which should be isolated into a controller/hook/container.
## Issue Context
This component was modified in this PR; extracting the save/discovery orchestration into a dedicated hook/controller improves compliance with the separation rule and keeps the UI component presentational.
## Fix Focus Areas
- apps/web/src/app/settings/AIAgentSection.tsx[48-70]
- apps/web/src/app/settings/hooks/useSettingsSectionsConvex.ts[281-324]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Non-generation model leak🐞 Bug ≡ Correctness
Description
isLikelyGenerationModel only checks prefixes at the very start of the model id, so
provider-prefixed non-generation IDs like openai/text-embedding-3-small will be misclassified as
generation models and shown in the settings dropdown. This can cause users to select
embedding/moderation models for text generation, leading to runtime failures or confusing behavior.
Code

packages/convex/convex/aiAgent.ts[R135-142]

+function isLikelyGenerationModel(modelId: string): boolean {
+  const normalized = modelId.trim().toLowerCase();
+  if (!normalized) {
+    return false;
+  }
+
+  return !NON_GENERATION_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
+}
Evidence
discoverAvailableAIModels filters gateway-returned IDs with isLikelyGenerationModel(model.id);
isLikelyGenerationModel uses startsWith(prefix) on the full (possibly provider-prefixed) id, so
prefixed embedding IDs won’t match text-embedding- and will pass the generation filter.

packages/convex/convex/aiAgent.ts[99-191]
packages/convex/convex/aiAgent.ts[135-142]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Model discovery may include non-generation models when the upstream gateway returns provider-prefixed IDs (e.g. `openai/text-embedding-3-small`). The current `isLikelyGenerationModel` checks prefixes against the full string, so these slip through.
## Issue Context
`discoverAvailableAIModels()` filters the gateway response using `isLikelyGenerationModel(model.id)` before mapping IDs into `AvailableAIModel`.
## Fix Focus Areas
- packages/convex/convex/aiAgent.ts[135-190]
## Suggested approach
- Update `isLikelyGenerationModel` to evaluate prefixes against the *actual model name* portion:
- If `modelId` contains `/`, check the substring after the first `/` (or last `/`, depending on expected formats).
- Example:
- `const raw = modelId.includes('/') ? modelId.split('/').slice(1).join('/') : modelId;`
- then run the `NON_GENERATION_MODEL_PREFIXES` check against `raw.trim().toLowerCase()`.
- Add a small unit test (if available in this package) that asserts `openai/text-embedding-3-small` is excluded.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Raw model provider mislabel🐞 Bug ⚙ Maintainability
Description
createAvailableAIModel sets provider to the first segment of id.split('/'), so a raw model id
without / becomes its own provider (e.g. provider: 'glm-5-turbo'). This makes the provider field
unreliable and produces confusing UI labels like glm-5-turbo (glm-5-turbo).
Code

packages/convex/convex/aiAgent.ts[R120-133]

+function createAvailableAIModel(value: string | undefined): AvailableAIModel | null {
+  const normalizedId = normalizeAvailableModelId(value);
+  if (!normalizedId) {
+    return null;
+  }
+
+  const [provider, ...modelParts] = normalizedId.split("/");
+  const model = modelParts.join("/") || normalizedId;
+  return {
+    id: normalizedId,
+    name: model,
+    provider,
+  };
+}
Evidence
listAvailableModels explicitly supports falling back to selectedModel, and the UI explicitly
supports entering “raw model IDs”. When selectedModel is raw (no /), createAvailableAIModel
assigns provider to the full id because split('/') returns a single-element array.

packages/convex/convex/aiAgent.ts[120-133]
packages/convex/convex/aiAgent.ts[833-846]
apps/web/src/app/settings/AIAgentSection.tsx[156-164]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`createAvailableAIModel` mislabels `provider` for raw model IDs (no `/`), causing confusing dropdown entries and unreliable provider display.
## Issue Context
`listAvailableModels` appends `createAvailableAIModel(args.selectedModel)` as a fallback, and the UI allows raw IDs.
## Fix Focus Areas
- packages/convex/convex/aiAgent.ts[120-133]
- packages/convex/convex/aiAgent.ts[833-846]
## Suggested approach
- Detect raw IDs in `createAvailableAIModel` (no `/`). For those:
- Keep `id` as-is (raw),
- Set `provider` to `getAIGatewayProviderLabel(getAIBaseURL(getAIGatewayApiKey()))` (or accept `providerLabel` as a parameter),
- Set `name` to the raw id.
- Alternatively, adjust the return shape to omit/empty `provider` for raw IDs and handle display in the UI accordingly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

6. Embedding model casing preserved🐞 Bug ⚙ Maintainability
Description
resolveContentEmbeddingModel validates support using a lowercased comparison but returns the
original trimmed input unchanged when supported, which defeats normalization and can lead to
non-canonical values being stored/propagated. This increases the chance of inconsistent settings and
harder-to-debug runtime differences (e.g. Text-Embedding-3-Small vs text-embedding-3-small).
Code

packages/convex/convex/lib/embeddingModels.ts[R27-30]

+export function resolveContentEmbeddingModel(model: string | undefined): string {
+  return isContentEmbeddingModelSupportedByCurrentIndex(model)
+    ? model?.trim() || DEFAULT_CONTENT_EMBEDDING_MODEL
+    : DEFAULT_CONTENT_EMBEDDING_MODEL;
Evidence
Support checks are done using normalizeModelName(...).toLowerCase(), but the resolved value
returned is model?.trim() (original casing). Callers then pass this resolved string into
aiClient.embedding(modelName) and persist it in settings updates, so non-canonical values can
propagate through the system.

packages/convex/convex/lib/embeddingModels.ts[6-31]
packages/convex/convex/embeddings.ts[318-326]
packages/convex/convex/aiAgent.ts[229-235]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`resolveContentEmbeddingModel` performs a case-insensitive support check but returns the original casing, which undermines the goal of normalization and can propagate inconsistent values.
## Issue Context
Multiple call sites (embeddings generation, suggestions search, AI settings persistence) rely on `resolveContentEmbeddingModel` for safe values.
## Fix Focus Areas
- packages/convex/convex/lib/embeddingModels.ts[6-31]
## Suggested approach
- Return canonical constants when supported:
- If normalized == DEFAULT_CONTENT_EMBEDDING_MODEL => return DEFAULT_CONTENT_EMBEDDING_MODEL
- If normalized == LEGACY_CONTENT_EMBEDDING_MODEL => return LEGACY_CONTENT_EMBEDDING_MODEL
- Else default
- This keeps DB values consistent and avoids casing-related drift across clients.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Diagnostic provider override ignored🐞 Bug ◔ Observability
Description
getAIConfigurationDiagnostic accepts environment.aiGatewayProviderLabel but does not use it,
instead recomputing the provider label from process/env each time. This can produce incorrect
provider metadata in diagnostics and makes the override parameter misleading for tests and alternate
gateway configurations.
Code

packages/convex/convex/aiAgentActions.ts[R274-281]

export const getAIConfigurationDiagnostic = (
modelString: string,
-  environment: { aiGatewayApiKey?: string } = {
+  environment: { aiGatewayApiKey?: string; aiGatewayProviderLabel?: string } = {
aiGatewayApiKey: process.env.AI_GATEWAY_API_KEY,
+    aiGatewayProviderLabel: getAIGatewayProviderLabel(),
}
): AIConfigurationDiagnostic | null => {
const trimmedModel = modelString.trim();
Evidence
The function signature includes aiGatewayProviderLabel?: string and even sets a default, but the
implementation uses getAIGatewayProviderLabel() directly when deriving the provider for raw model
IDs, never reading environment.aiGatewayProviderLabel.

packages/convex/convex/aiAgentActions.ts[274-320]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`getAIConfigurationDiagnostic` takes `environment.aiGatewayProviderLabel` but ignores it, which can lead to incorrect provider names in diagnostic payloads.
## Issue Context
The function is used to record runtime diagnostics; correctness of `provider` matters for debugging.
## Fix Focus Areas
- packages/convex/convex/aiAgentActions.ts[274-317]
## Suggested approach
- Replace `getAIGatewayProviderLabel()` calls inside `getAIConfigurationDiagnostic` with:
- `const providerLabel = environment.aiGatewayProviderLabel ?? getAIGatewayProviderLabel();`
- Use `providerLabel` when `parts.length !== 2`.
- Optionally remove the parameter if it’s not needed, but current tests already pass it so using it is the least surprising fix.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: checks

Failed stage: Summarize check results [❌]

Failed test name: aiAgent authorization semantics > returns AI responses for authorized agents

Failure summary:

The action failed due to two blocking CI gates failing:

- pnpm security:convex-any-args-gate failed with exit code 1 because the script
scripts/ci-convex-any-args-gate.js detected an expired v.any() exception:
- Expired v.any()
exception for packages/convex/convex/testAdmin.ts:runTestMutation (expired 2026-04-15) (see log
lines 474-479).

- pnpm test:convex (Vitest) failed because one test assertion failed in
packages/convex/tests/aiAgentAuthorizationSemantics.test.ts:
- Failing test: aiAgent authorization
semantics > returns AI responses for authorized agents
- Assertion failure at
tests/aiAgentAuthorizationSemantics.test.ts:119:20: expected [] to have a length of 2 but got +0
(see log lines 633-666).

Note: Lint steps reported many warnings but 0 errors, so lint did not directly fail the job.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

205:  packages/types lint: Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. Assuming latest React version for linting.
206:  packages/ui lint: Done
207:  packages/types lint: Done
208:  packages/convex lint$ eslint convex scripts tests --ext .ts
209:  apps/landing lint$ next lint
210:  apps/landing lint: `next lint` is deprecated and will be removed in Next.js 16.
211:  apps/landing lint: For new projects, use create-next-app to choose your preferred linter.
212:  apps/landing lint: For existing projects, migrate to the ESLint CLI:
213:  apps/landing lint: npx @next/codemod@canary next-lint-to-eslint-cli .
214:  packages/convex lint: Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. Assuming latest React version for linting.
215:  apps/landing lint:  ⚠ The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/app/api-reference/config/eslint#migrating-existing-config
216:  apps/landing lint: Attention: Next.js now collects completely anonymous telemetry regarding usage.
217:  apps/landing lint: This information is used to shape Next.js' roadmap and prioritize features.
218:  apps/landing lint: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
219:  apps/landing lint: https://nextjs.org/telemetry
220:  apps/landing lint: ✔ No ESLint warnings or errors
221:  apps/landing lint: Done
...

376:  packages/convex lint:   90:12  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
377:  packages/convex lint: /home/runner/work/opencom/opencom/packages/convex/tests/visitorDirectoryAuthorizationSemantics.test.ts
378:  packages/convex lint:    62:81  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
379:  packages/convex lint:    74:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
380:  packages/convex lint:    77:81  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
381:  packages/convex lint:    91:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
382:  packages/convex lint:   116:13  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
383:  packages/convex lint:   138:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
384:  packages/convex lint:   165:13  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
385:  packages/convex lint:   187:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
386:  packages/convex lint:   200:65  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
387:  packages/convex lint:   215:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
388:  packages/convex lint:   233:65  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
389:  packages/convex lint:   254:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
390:  packages/convex lint:   348:65  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
391:  packages/convex lint: ✖ 146 problems (0 errors, 146 warnings)
392:  packages/convex lint: Done
393:  apps/mobile lint$ eslint . --ext .ts,.tsx
394:  apps/web lint$ eslint src --ext .ts,.tsx
395:  apps/mobile lint: /home/runner/work/opencom/opencom/apps/mobile/src/contexts/BackendContext.tsx
396:  apps/mobile lint:   33:6  warning  React Hook useEffect has a missing dependency: 'loadBackendStorage'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
397:  apps/mobile lint: ✖ 1 problem (0 errors, 1 warning)
398:  apps/mobile lint: Done
399:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/app/login/page.test.tsx
400:  apps/web lint:   45:42  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
401:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/app/settings/MessengerSettingsSection.test.tsx
402:  apps/web lint:   24:61  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
403:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/app/signup/page.test.tsx
404:  apps/web lint:   42:42  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
405:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/components/ResponsiveLayout.tsx
406:  apps/web lint:   152:6  warning  React Hook useEffect has a missing dependency: 'closePanel'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
407:  apps/web lint: /home/runner/work/opencom/opencom/apps/web/src/contexts/BackendContext.tsx
408:  apps/web lint:   86:6  warning  React Hook useEffect has a missing dependency: 'loadBackendStorage'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
409:  apps/web lint: ✖ 5 problems (0 errors, 5 warnings)
410:  apps/web lint: Done
...

412:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/components/ConversationView.test.tsx
413:  apps/widget lint:    53:6   warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
414:  apps/widget lint:    55:32  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
415:  apps/widget lint:    71:48  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
416:  apps/widget lint:    96:74  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
417:  apps/widget lint:   265:37  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
418:  apps/widget lint:   266:35  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
419:  apps/widget lint:   284:49  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
420:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/components/ConversationView.tsx
421:  apps/widget lint:   162:6  warning  React Hook useEffect has missing dependencies: 'sessionTokenRef' and 'visitorId'. Either include them or remove the dependency array  react-hooks/exhaustive-deps
422:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/components/HelpCenter.tsx
423:  apps/widget lint:   149:6  warning  React Hook useEffect has a missing dependency: 'setSelectedCollectionKey'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
424:  apps/widget lint:   158:6  warning  React Hook useEffect has a missing dependency: 'setSelectedCollectionKey'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
425:  apps/widget lint: /home/runner/work/opencom/opencom/apps/widget/src/hooks/useWidgetConversationFlow.ts
426:  apps/widget lint:   192:6  warning  React Hook useCallback has a missing dependency: 'visitorIdRef'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
427:  apps/widget lint: ✖ 11 problems (0 errors, 11 warnings)
428:  apps/widget lint: Done
...

463:  ##[endgroup]
464:  > opencom@0.1.0 security:convex-auth-guard /home/runner/work/opencom/opencom
465:  > node scripts/ci-convex-auth-guard.js
466:  [convex-auth-guard] OK: validated 118 privileged raw exports across 34 module snapshot(s).
467:  [convex-auth-guard] Guard markers: requirePermission, hasPermission, getAuthenticatedUserFromSession, resolveVisitorFromSession, requireValidOrigin, assertWebhookInternalAccess
468:  ##[group]Run pnpm security:convex-any-args-gate
469:  �[36;1mpnpm security:convex-any-args-gate�[0m
470:  shell: /usr/bin/bash -e {0}
471:  env:
472:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
473:  ##[endgroup]
474:  > opencom@0.1.0 security:convex-any-args-gate /home/runner/work/opencom/opencom
475:  > node scripts/ci-convex-any-args-gate.js
476:  [convex-any-args-gate] Violations detected:
477:  - Expired v.any() exception for packages/convex/convex/testAdmin.ts:runTestMutation (expired 2026-04-15)
478:  ELIFECYCLE  Command failed with exit code 1.
479:  ##[error]Process completed with exit code 1.
480:  ##[group]Run pnpm security:secret-scan
...

498:  > node scripts/ci-security-headers-check.js
499:  [security-headers-check] OK: web and landing header requirements validated.
500:  ##[group]Run pnpm test:convex
501:  �[36;1mpnpm test:convex�[0m
502:  shell: /usr/bin/bash -e {0}
503:  env:
504:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
505:  ##[endgroup]
506:  > opencom@0.1.0 test:convex /home/runner/work/opencom/opencom
507:  > pnpm --filter @opencom/convex test
508:  > @opencom/convex@0.1.0 test /home/runner/work/opencom/opencom/packages/convex
509:  > vitest run
510:  �[1m�[46m RUN �[49m�[22m �[36mv4.0.17 �[39m�[90m/home/runner/work/opencom/opencom/packages/convex�[39m
511:  �[32m✓�[39m tests/runtimeTypeHardeningGuard.test.ts �[2m(�[22m�[2m26 tests�[22m�[2m)�[22m�[32m 41�[2mms�[22m�[39m
512:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mclears prior diagnostics and continues generation when configuration is valid
513:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
514:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
515:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
516:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:262:20
517:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
518:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mretries once when first generation returns empty text and succeeds on retry
519:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
520:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
521:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
522:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:344:20
523:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
524:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2momits temperature for gpt-5 reasoning models
525:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
526:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
527:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
528:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:420:5
529:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
530:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mstores the handoff message while retaining generated candidate context
531:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
532:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
533:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
534:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:492:20
535:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
536:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mdoes not hand off when a resolved answer includes an optional human escalation offer
537:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
538:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
539:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
540:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:572:20
541:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
542:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mpersists a handoff message when generation fails
543:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
544:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
545:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
546:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:646:20
547:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
548:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mpersists a handoff message when generation fails
549:  �[22m�[39mAI generation error: Error: gateway timeout
550:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:602:40
551:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:145:11
552:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:26
553:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1243:20
554:  at new Promise (<anonymous>)
555:  at runWithTimeout (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1209:10)
556:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1653:37
557:  at Traces.$ (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/vitest@4.0.17_@opentelemetry+api@1.9.0_@types+node@20.19.30_@vitest+ui@4.0.18_jiti@1.21.7_jsd_znh4lc65ld7o7n72tbcfnzmloy/node_modules/�[4mvitest�[24m/dist/chunks/traces.CCmnQaNT.js:142:27)
558:  at trace (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/vitest@4.0.17_@opentelemetry+api@1.9.0_@types+node@20.19.30_@vitest+ui@4.0.18_jiti@1.21.7_jsd_znh4lc65ld7o7n72tbcfnzmloy/node_modules/�[4mvitest�[24m/dist/chunks/test.B8ej_ZHS.js:239:21)
559:  at runTest (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1653:12)
560:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mfalls back to a persisted bot message if handoff fails after generation error
561:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
562:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
563:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
564:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:721:20
565:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
566:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mfalls back to a persisted bot message if handoff fails after generation error
567:  �[22m�[39mAI generation error: Error: provider unavailable
568:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:680:40
569:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:145:11
570:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:26
571:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1243:20
572:  at new Promise (<anonymous>)
573:  at runWithTimeout (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1209:10)
574:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1653:37
575:  at Traces.$ (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/vitest@4.0.17_@opentelemetry+api@1.9.0_@types+node@20.19.30_@vitest+ui@4.0.18_jiti@1.21.7_jsd_znh4lc65ld7o7n72tbcfnzmloy/node_modules/�[4mvitest�[24m/dist/chunks/traces.CCmnQaNT.js:142:27)
576:  at trace (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/vitest@4.0.17_@opentelemetry+api@1.9.0_@types+node@20.19.30_@vitest+ui@4.0.18_jiti@1.21.7_jsd_znh4lc65ld7o7n72tbcfnzmloy/node_modules/�[4mvitest�[24m/dist/chunks/test.B8ej_ZHS.js:239:21)
577:  at runTest (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1653:12)
578:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mfalls back to a persisted bot message if handoff fails after generation error
579:  �[22m�[39mFailed to handoff after AI generation error: Error: handoff unavailable
580:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:710:15
581:  at Mock (file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+spy@4.0.17/node_modules/�[4m@vitest/spy�[24m/dist/index.js:285:34)
582:  at handleGenerationFailure �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:635:31�[90m)�[39m
583:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
584:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:806:14�[90m)�[39m
585:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:721:20
586:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
587:  �[90mstderr�[2m | tests/aiAgentRuntimeSafety.test.ts�[2m > �[22m�[2maiAgentActions runtime safety�[2m > �[22m�[2mtreats empty model output as a generation failure and hands off
588:  �[22m�[39mKnowledge retrieval failed; continuing without knowledge context: TypeError: runAction is not a function
589:  at Function.handler [as _handler] �[90m(/home/runner/work/opencom/opencom/packages/convex/�[39mconvex/aiAgentActions.ts:584:32�[90m)�[39m
590:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
591:  at �[90m/home/runner/work/opencom/opencom/packages/convex/�[39mtests/aiAgentRuntimeSafety.test.ts:820:20
592:  at file:///home/runner/work/opencom/opencom/node_modules/�[4m.pnpm�[24m/@vitest+runner@4.0.17/node_modules/�[4m@vitest/runner�[24m/dist/index.js:915:20
593:  �[32m✓�[39m tests/aiAgentRuntimeSafety.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 62�[2mms�[22m�[39m
594:  �[32m✓�[39m tests/reportingCsatEligibilitySemantics.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
595:  �[32m✓�[39m tests/visitorDirectoryAuthorizationSemantics.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
596:  �[90mstderr�[2m | tests/notificationRouting.test.ts�[2m > �[22m�[2mnotification routing�[2m > �[22m�[2mremoves invalid agent and visitor tokens after transport errors
597:  �[22m�[39m[Push] Failed to send to ExponentPushToken[agent-invalid]: DeviceNotRegistered: The device is not registered
598:  [Push] Failed to send to ExponentPushToken[visitor-invalid]: DeviceNotRegistered: The device is not registered
599:  �[32m✓�[39m tests/auditLogs.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
600:  �[32m✓�[39m tests/notificationRouting.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[33m 387�[2mms�[22m�[39m
601:  �[32m✓�[39m tests/hotpathQueries.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
602:  �[32m✓�[39m tests/migration.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
603:  �[31m❯�[39m tests/aiAgentAuthorizationSemantics.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m | �[22m�[31m1 failed�[39m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
604:  �[32m✓�[39m rejects agent access when conversation read permission is missing�[32m 5�[2mms�[22m�[39m
...

617:  �[32m✓�[39m tests/permissions.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
618:  �[32m✓�[39m tests/authWrappers.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
619:  �[32m✓�[39m tests/webhookSecurity.test.ts �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
620:  �[32m✓�[39m tests/emailCampaignPolicyGuard.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
621:  �[32m✓�[39m tests/identityVerification.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
622:  �[32m✓�[39m tests/reportingCsatMetricsSemantics.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
623:  �[32m✓�[39m tests/validatorMigrationRegression.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
624:  �[32m✓�[39m tests/notificationEmailBatching.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
625:  �[32m✓�[39m tests/embeddingModels.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 3�[2mms�[22m�[39m
626:  �[32m✓�[39m tests/aiAgentPublicSettings.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
627:  �[32m✓�[39m tests/discovery.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
628:  �[32m✓�[39m tests/aiAgentHandoffPath.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 4�[2mms�[22m�[39m
629:  �[32m✓�[39m tests/testAdminSecretValidation.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
630:  �[32m✓�[39m tests/visitorReadableId.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 3�[2mms�[22m�[39m
631:  �[32m✓�[39m tests/aiAgentActionsPath.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 3�[2mms�[22m�[39m
632:  �[31m⎯⎯⎯⎯⎯⎯⎯�[39m�[1m�[41m Failed Tests 1 �[49m�[22m�[31m⎯⎯⎯⎯⎯⎯⎯�[39m
633:  �[41m�[1m FAIL �[22m�[49m tests/aiAgentAuthorizationSemantics.test.ts�[2m > �[22maiAgent authorization semantics�[2m > �[22mreturns AI responses for authorized agents
634:  �[31m�[1mAssertionError�[22m: expected [] to have a length of 2 but got +0�[39m
635:  �[32m- Expected�[39m
636:  �[31m+ Received�[39m
637:  �[32m- 2�[39m
638:  �[31m+ 0�[39m
639:  �[36m �[2m❯�[22m tests/aiAgentAuthorizationSemantics.test.ts:�[2m119:20�[22m�[39m
640:  �[90m117| �[39m    })�[33m;�[39m
641:  �[90m118| �[39m
642:  �[90m119| �[39m    �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveLength�[39m(�[34m2�[39m)�[33m;�[39m
643:  �[90m   | �[39m                   �[31m^�[39m
644:  �[90m120| �[39m    �[34mexpect�[39m(result[�[34m0�[39m]�[33m.�[39mconversationId)�[33m.�[39m�[34mtoBe�[39m(conversationId)�[33m;�[39m
645:  �[90m121| �[39m  })�[33m;�[39m
646:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯�[22m�[39m
647:  �[2m Test Files �[22m �[1m�[31m1 failed�[39m�[22m�[2m | �[22m�[1m�[32m32 passed�[39m�[22m�[90m (33)�[39m
648:  �[2m      Tests �[22m �[1m�[31m1 failed�[39m�[22m�[2m | �[22m�[1m�[32m241 passed�[39m�[22m�[90m (242)�[39m
649:  �[2m   Start at �[22m 04:59:55
650:  �[2m   Duration �[22m 4.43s�[2m (transform 2.06s, setup 2.36s, import 3.83s, tests 955ms, environment 6ms)�[22m
651:  ##[error]AssertionError: expected [] to have a length of 2 but got +0
652:  
653:  - Expected
654:  + Received
655:  
656:  - 2
657:  + 0
658:  
659:   ❯ tests/aiAgentAuthorizationSemantics.test.ts:119:20
660:  
661:  
662:  /home/runner/work/opencom/opencom/packages/convex:
663:  ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @opencom/convex@0.1.0 test: `vitest run`
664:  Exit status 1
665:  ELIFECYCLE  Command failed with exit code 1.
666:  ##[error]Process completed with exit code 1.
667:  ##[group]Run pnpm --filter @opencom/web build
...

750:  ├ ○ /widget-demo                         2.15 kB         134 kB
751:  └ ○ /widget-preview                      1.08 kB         133 kB
752:  + First Load JS shared by all             102 kB
753:  ├ chunks/186ceb5a-cd350ebda3c37bce.js  54.2 kB
754:  ├ chunks/5470-86efb4adbf0b2de3.js      45.8 kB
755:  └ other shared chunks (total)          1.93 kB
756:  ○  (Static)   prerendered as static content
757:  ƒ  (Dynamic)  server-rendered on demand
758:  ##[group]Run node scripts/ci-audit-gate.js
759:  �[36;1mnode scripts/ci-audit-gate.js�[0m
760:  shell: /usr/bin/bash -e {0}
761:  env:
762:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
763:  ##[endgroup]
764:  [audit-gate] No high/critical advisories found.
765:  ##[group]Run failures=0
766:  �[36;1mfailures=0�[0m
767:  �[36;1m�[0m
768:  �[36;1mreport_blocking() {�[0m
769:  �[36;1m  name="$1"�[0m
770:  �[36;1m  outcome="$2"�[0m
771:  �[36;1m  if [ "$outcome" = "success" ]; then�[0m
772:  �[36;1m    echo "::notice::$name passed"�[0m
773:  �[36;1m  elif [ "$outcome" = "skipped" ]; then�[0m
774:  �[36;1m    echo "::warning::$name skipped"�[0m
775:  �[36;1m  else�[0m
776:  �[36;1m    echo "::error::$name failed"�[0m
777:  �[36;1m    failures=1�[0m
778:  �[36;1m  fi�[0m
779:  �[36;1m}�[0m
780:  �[36;1m�[0m
781:  �[36;1mreport_warning() {�[0m
782:  �[36;1m  name="$1"�[0m
783:  �[36;1m  outcome="$2"�[0m
784:  �[36;1m  if [ "$outcome" = "success" ]; then�[0m
785:  �[36;1m    echo "::notice::$name passed"�[0m
786:  �[36;1m  elif [ "$outcome" = "skipped" ]; then�[0m
787:  �[36;1m    echo "::warning::$name skipped"�[0m
788:  �[36;1m  else�[0m
789:  �[36;1m    echo "::warning::$name failed (warning only)"�[0m
790:  �[36;1m  fi�[0m
...

798:  �[36;1mreport_blocking "Security headers policy check" "success"�[0m
799:  �[36;1mreport_blocking "Convex backend tests" "failure"�[0m
800:  �[36;1mreport_blocking "Web production build" "success"�[0m
801:  �[36;1mreport_blocking "Dependency audit gate" "success"�[0m
802:  �[36;1m�[0m
803:  �[36;1mif [ "$failures" -ne 0 ]; then�[0m
804:  �[36;1m  exit 1�[0m
805:  �[36;1mfi�[0m
806:  shell: /usr/bin/bash -e {0}
807:  env:
808:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
809:  ##[endgroup]
810:  ##[notice]Lint passed
811:  ##[notice]Typecheck passed
812:  ##[notice]Convex raw auth guard passed
813:  ##[error]Convex validator any guard failed
814:  ##[notice]Secret scan gate passed
815:  ##[notice]Security headers policy check passed
816:  ##[error]Convex backend tests failed
817:  ##[notice]Web production build passed
818:  ##[notice]Dependency audit gate passed
819:  ##[error]Process completed with exit code 1.
820:  Post job cleanup.

Comment thread packages/convex/convex/aiAgent.ts Outdated
Comment thread packages/convex/convex/aiAgent.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades AI model selection by discovering available generation models dynamically from the configured AI gateway, normalizing/validating model IDs, and centralizing embedding model defaults/resolution so backend modules and the UI handle models consistently.

Changes:

  • Replace static model list with authenticated, gateway-backed model discovery (with dedupe + fallback behavior).
  • Normalize/validate generation model IDs (supporting both provider/model and raw IDs) and align frontend settings UI with discovered models.
  • Centralize content embedding model defaults/dimensions and apply consistent fallback logic across embedding/suggestions/knowledge paths.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/convex/tests/embeddingModels.test.ts Adds coverage for embedding model compatibility + fallback behavior.
packages/convex/tests/aiAgentRuntimeSafety.test.ts Updates diagnostics expectations for new model-format rules and gateway env.
packages/convex/tests/aiAgent.test.ts Updates listAvailableModels callsite to action + adds fallback preservation test.
packages/convex/convex/suggestions.ts Uses centralized embedding model resolution/defaults.
packages/convex/convex/schema/operationsReportingTables.ts Makes messageId optional and adds attemptStatus to AI response reporting.
packages/convex/convex/schema/operationsAiTables.ts Replaces hard-coded vector dimensions with shared constant.
packages/convex/convex/schema/inboxConversationTables.ts Adds aiTurnSequence to conversation schema.
packages/convex/convex/lib/embeddingModels.ts Introduces shared embedding model constants + compatibility helpers.
packages/convex/convex/lib/aiGateway.ts Adds base URL/key normalization and provider-label inference.
packages/convex/convex/embeddings.ts Uses centralized embedding model resolution/defaults for generation.
packages/convex/convex/aiAgentActionsKnowledge.ts Uses centralized embedding model resolution/defaults for runtime knowledge.
packages/convex/convex/aiAgentActions.ts Updates model parsing/diagnostics to support raw model IDs + inferred provider label.
packages/convex/convex/aiAgent.ts Adds gateway model discovery and uses embedding model resolution across settings/queries.
packages/convex/convex/_generated/api.d.ts Regenerates API types to include new module exports.
apps/web/src/app/settings/hooks/useSettingsSectionsConvex.ts Switches available-models fetch from query to action and wires it into settings hook.
apps/web/src/app/settings/AIAgentSection.tsx Adds discovered-model dropdown + manual model ID entry and trims model on save.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +30
return isContentEmbeddingModelSupportedByCurrentIndex(model)
? model?.trim() || DEFAULT_CONTENT_EMBEDDING_MODEL
: DEFAULT_CONTENT_EMBEDDING_MODEL;
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveContentEmbeddingModel can return a non-canonical model ID (it trims but doesn’t normalize casing). Because isContentEmbeddingModelSupportedByCurrentIndex lowercases before comparing, inputs like "Text-Embedding-3-Small" will be treated as supported yet returned as-is, which can lead to downstream embedding calls using a model ID the provider doesn’t recognize. Consider returning the canonical constant (DEFAULT_CONTENT_EMBEDDING_MODEL / LEGACY_CONTENT_EMBEDDING_MODEL) based on the normalized value, or otherwise normalizing to the exact supported IDs before returning.

Suggested change
return isContentEmbeddingModelSupportedByCurrentIndex(model)
? model?.trim() || DEFAULT_CONTENT_EMBEDDING_MODEL
: DEFAULT_CONTENT_EMBEDDING_MODEL;
const normalized = normalizeModelName(model);
if (normalized === "" || normalized === DEFAULT_CONTENT_EMBEDDING_MODEL) {
return DEFAULT_CONTENT_EMBEDDING_MODEL;
}
if (normalized === LEGACY_CONTENT_EMBEDDING_MODEL) {
return LEGACY_CONTENT_EMBEDDING_MODEL;
}
return DEFAULT_CONTENT_EMBEDDING_MODEL;

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +132
function createAvailableAIModel(value: string | undefined): AvailableAIModel | null {
const normalizedId = normalizeAvailableModelId(value);
if (!normalizedId) {
return null;
}

const [provider, ...modelParts] = normalizedId.split("/");
const model = modelParts.join("/") || normalizedId;
return {
id: normalizedId,
name: model,
provider,
};
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createAvailableAIModel assumes the ID always contains a / and treats the first segment as the provider. When selectedModel is a raw model ID (which the UI now allows), this will set provider to the raw ID itself and name to the full ID, producing misleading entries (and potentially breaking provider-based UI grouping later). Consider detecting the no-slash case and assigning a sensible provider label (e.g., the gateway provider label / "gateway") while keeping name as the raw model ID.

Copilot uses AI. Check for mistakes.
Comment on lines 275 to 279
modelString: string,
environment: { aiGatewayApiKey?: string } = {
environment: { aiGatewayApiKey?: string; aiGatewayProviderLabel?: string } = {
aiGatewayApiKey: process.env.AI_GATEWAY_API_KEY,
aiGatewayProviderLabel: getAIGatewayProviderLabel(),
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAIConfigurationDiagnostic accepts environment.aiGatewayProviderLabel (and even sets a default), but the function never actually uses that value when deriving the provider for raw model IDs (it calls getAIGatewayProviderLabel() directly instead). This makes the parameter misleading and prevents callers/tests from controlling provider label resolution. Consider using environment.aiGatewayProviderLabel ?? getAIGatewayProviderLabel() when parts.length !== 2, or remove the parameter if it’s not needed.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 35 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +358 to +363
.catch((error) => {
console.error("Failed to load available AI models:", error);
if (!cancelled) {
setAvailableModels(undefined);
}
});
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

availableModels uses undefined for both the initial loading state and the error fallback (catch sets it back to undefined). This makes the UI treat a failed fetch as "Loading discovered models..." indefinitely, with no way to distinguish failure vs loading. Consider tracking an explicit error/loaded state (e.g., availableModels: null | AvailableAIAgentModel[] or a separate availableModelsStatus) and rendering an appropriate message/fallback when discovery fails.

Copilot uses AI. Check for mistakes.
@djanogly djanogly merged commit 189b4f3 into dev Apr 16, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[AI Agent] Model selector only shows gpt-5.1-mini and ignores the configured model

2 participants