Skip to content

feat: model select + paywall changes, authoritative frontend models, model router#4077

Open
ehayes2000 wants to merge 1 commit into
add-openai-modelsfrom
model-selector
Open

feat: model select + paywall changes, authoritative frontend models, model router#4077
ehayes2000 wants to merge 1 commit into
add-openai-modelsfrom
model-selector

Conversation

@ehayes2000

Copy link
Copy Markdown
Contributor

No description provided.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b2a1c9b0-4ecb-44ec-bde5-7524d550b522

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR removes client-side prechecks for paid chat access, adds frontend model availability fetching and locked-model selection UI, and introduces a backend GET /chats/models endpoint. Chat APIs, persistence models, and OpenAPI schemas now use provider API id strings for models. The agent runtime routes requests across Anthropic and OpenAI model ids and normalizes tool schemas for provider requests. Tool schema generation was refactored around validated input schemas and frontend typegen schemas, and related generators and tests were updated.

@ehayes2000 ehayes2000 changed the base branch from main to add-openai-models June 15, 2026 16:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
js/app/packages/service-clients/service-cognition/openapi.json (1)

1276-1321: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align the no-access model status with the backend.

This request now accepts provider IDs, so paywall branching needs a stable status. The backend access check returns StatusCode::FORBIDDEN when model_access.has_access(&request.model) fails, but this operation documents 402 Payment required for that same case. Either return 402 from the handler or document/handle 403 consistently.

Also applies to: 2787-2790

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app/packages/service-clients/service-cognition/openapi.json` around lines
1276 - 1321, The OpenAPI specification for the send_chat_message operation
documents a 402 status code with description "Payment required — user lacks
access to the requested model", but the backend handler returns 403 (FORBIDDEN)
when model_access.has_access check fails. Choose one approach: either modify the
handler to return 402 to match the documented behavior, or update the OpenAPI
documentation to reflect that 403 (Forbidden) is returned for access check
failures. Ensure this change is applied consistently at all affected locations
in the specification where this mismatch occurs.
rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs (1)

1-59: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This change set modifies a crate marked as non-modifiable by repository policy.

These edits are under rust/cloud-storage/ai_toolset/**, which is explicitly designated as off-limits in the provided project rules. Please move the implementation to an allowed location or document an explicit exception before merge.

As per coding guidelines, rust/cloud-storage/ai_toolset/**: “You are not allowed to EVER change this crate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs` around lines 1 -
59, The changes you've made to the ai_toolset crate (including the Validate
trait and validate_tool_schema function) violate repository policy, which
explicitly prohibits modifications to rust/cloud-storage/ai_toolset/**. Move the
implementation changes to an allowed location within the repository, or obtain
and document an explicit exception to the policy before this code can be merged.
If these are new additions rather than modifications to existing code, clarify
the business justification and get approval from the appropriate stakeholders.

Source: Coding guidelines

rust/cloud-storage/ai_toolset/src/schema/error.rs (1)

1-45: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not modify ai_toolset in this PR.

Line 1 is within rust/cloud-storage/ai_toolset/**, which the project guidelines explicitly mark as immutable. Please move this behavior to an allowed crate or revert these changes.

As per coding guidelines: rust/cloud-storage/ai_toolset/**: “You are not allowed to EVER change this crate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/error.rs` around lines 1 - 45, The
ValidationError enum and its modifications are located in the immutable
ai_toolset crate, which violates project guidelines that explicitly prohibit any
changes to rust/cloud-storage/ai_toolset/**. Revert all changes made to this
file, or if the functionality is necessary, move the ValidationError enum and
related code to an allowed crate outside the ai_toolset directory.

Source: Coding guidelines

rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs (1)

1-234: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Blocked: changes landed in a forbidden crate path.

This file is inside rust/cloud-storage/ai_toolset/**, which is marked as immutable by project policy. Please move this implementation out of ai_toolset and revert direct edits in this crate before merge.

As per coding guidelines: rust/cloud-storage/ai_toolset/**: You are not allowed to _EVER_ change this crate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs` around lines 1
- 234, The file rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs
containing the FrontendSchemas, FrontendSchemasBuilder, and ToolSchemaGenerator
implementations is located in the ai_toolset crate, which is marked as immutable
by project policy. Move all the code in this file (FrontendToolEntry,
ToolSchemaGenerator trait, AsyncToolCollection implementation, FrontendSchemas
struct with its methods like mangle_collisions, find_owning_tool,
value_contains_ref, rename_refs, and rename_refs_in_value, along with
FrontendSchemasBuilder) to a different, mutable crate location outside of
ai_toolset, then revert this file to its original state before your changes.

Source: Coding guidelines

🧹 Nitpick comments (1)
js/app/packages/core/component/AI/component/input/ChatInput.tsx (1)

63-69: ⚡ Quick win

Replace derived-state createEffect with a derived signal/setter flow.

This effect is deriving and mutating local app state from reactive inputs, which conflicts with the SolidJS guideline for this codebase. Prefer a derived signal/wrapped setter approach here.

As per coding guidelines: “Avoid createEffect in SolidJs… Do not use to derive state or trigger updates—use a derived signal or wrap the setter instead”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app/packages/core/component/AI/component/input/ChatInput.tsx` around lines
63 - 69, The createEffect in the ChatInput component is using a side-effect to
derive and mutate state by calling input.setModel() based on modelOptions() and
model() reactivity, which violates SolidJS coding guidelines. Replace this
createEffect with a derived signal approach: create a derived signal that
computes the appropriate model ID based on the available options and current
model selection logic, then wrap the input.setModel setter with this derived
signal to handle the state update in a declarative, non-effect-based manner. The
derived signal should encapsulate the logic that checks option availability and
determines the first available model ID.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@js/app/packages/core/component/AI/component/input/ChatInput.tsx`:
- Around line 54-69: The models query needs to be resolved before allowing
message send to prevent sending a locked model. The createEffect fallback exits
early when modelOptions is empty (before the query finishes loading), but
canSendMessage still allows sending with the current model selection without
validation. Block sending in canSendMessage by checking the models query loading
state (modelsQuery.isPending or isLoading) or ensuring a valid model is
confirmed available before the send is allowed. This prevents sending a model
that hasn't been validated as available to the user yet, avoiding 403 errors and
ensuring the correct locked-model or paywall UX is shown instead.

In `@js/app/packages/service-clients/service-cognition/openapi.json`:
- Around line 373-394: The OpenAPI specification for the `/chats/models`
endpoint is missing a 401 response definition, even though the endpoint is
protected by ensure_user_exists middleware at runtime. Add a 401 response object
to the responses section alongside the existing 200 and 500 responses, with an
appropriate description and schema (typically application/json with an error
message or empty description with text/plain) to accurately document that
unauthenticated requests will receive a 401 Unauthorized response.
- Around line 1445-1454: The enum array in the AgentModel definition contains
duplicate values that violate OpenAPI schema constraints. Remove the duplicate
instances so that `claude-opus-4-8` and `claude-haiku-4-5` each appear only once
in the enum list. Keep the first occurrence of each duplicated value and remove
the subsequent occurrences.

In `@rust/cloud-storage/agent/Cargo.toml`:
- Around line 17-19: The rig-core dependency declaration in Cargo.toml currently
uses branch = "feat/responses-api-non-strict-tools" which creates a hybrid
pinning strategy between the lockfile and manifest. Replace the branch attribute
with rev = "6deadfc6f9b432c849ea79733a549f46407530b7" to make the pinned commit
explicit and single-sourced in the manifest, improving auditability and ensuring
the manifest and lockfile stay consistent without requiring manual
synchronization.

In `@rust/cloud-storage/agent/src/agent_loop.rs`:
- Around line 263-289: The StreamBridge attached at line 263 already emits usage
information via the on_stream_completion_response_finish hook, so the subsequent
emission of StreamPart::Usage in the FinalResponse match arm (lines 284-289)
creates a duplicate. Remove the code block that yields Ok(StreamPart::Usage)
from the FinalResponse match case, as the usage tokens are already being
properly emitted through the StreamBridge hook mechanism.

In `@rust/cloud-storage/agent/src/model/router.rs`:
- Around line 96-106: The route_or_default method currently catches all errors
from the self.route(model) call and silently returns an Anthropic default, which
masks real provider unavailability and configuration issues. Modify the error
handling to distinguish between error types: only return the default Anthropic
model for UnknownModel errors, while allowing ProviderUnavailable errors to
propagate up so they are not hidden by the automatic fallback to the default
model.

In `@rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs`:
- Line 6: The std::fs::create_dir call for creating the "schemas" directory
fails when the directory already exists, breaking repeated runs of the schema
generation tool. Replace the create_dir call with create_dir_all, which is
idempotent and will succeed whether the directory exists or not, making the
schema generation process rerunnable without errors.

In `@rust/cloud-storage/ai_toolset/src/schema/generate.rs`:
- Around line 1-91: The changes made to
`rust/cloud-storage/ai_toolset/src/schema/generate.rs` violate the repository
guardrail that forbids modifications to the `ai_toolset` crate. Revert all
changes in the `ai_toolset` directory and refactor the modifications to be made
in an allowed external crate or interface that wraps or extends the
functionality, ensuring the core `ai_toolset` crate remains unmodified as
required by project policy.

In `@rust/cloud-storage/ai_toolset/src/schema/mod.rs`:
- Around line 9-20: The file rust/cloud-storage/ai_toolset/src/schema/mod.rs is
in a crate designated as immutable by project rules and cannot be modified.
Either revert all changes made to files under rust/cloud-storage/ai_toolset/**,
move the implementation to an allowed crate and update the corresponding imports
and exports, or obtain explicit written approval from the project maintainers to
document an exception for this PR with clear justification for why the
immutability rule must be broken.

In `@rust/cloud-storage/ai_toolset/src/schema/transform/nullify_optional.rs`:
- Around line 70-96: The transform adds "null" to the type for nullable
properties but doesn't handle the case where a property has both type and const
constraints. When const is present, adding "null" to type is insufficient
because const still restricts the value to the original constant. After the type
union logic (around lines 70-95), add a check similar to the enum handling
(lines 97-101) to detect if a const field exists. If it does, convert it to an
enum array containing the original constant value and null, then remove the
const field, ensuring the property truly becomes nullable.

In `@rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs`:
- Around line 1-107: The new implementation of the StripUnsupported struct and
its Transform trait implementation has been added to the ai_toolset crate, which
violates the repository rule that this crate must never be modified. Either
revert all changes in this file
(rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs)
entirely, or move the StripUnsupported struct and its Transform implementation
to an allowed crate outside of ai_toolset, keeping the ai_toolset directory
completely untouched.

In `@rust/cloud-storage/chat/src/outbound/postgres/mod.rs`:
- Line 131: The model field assignment is currently always using FALLBACK_MODEL,
which causes reopened chats to lose their originally selected provider and model
information. Instead of unconditionally setting model to
FALLBACK_MODEL.to_string(), check if a persisted chat model exists in the
fetched data and use that value when available, only falling back to
FALLBACK_MODEL for legacy database rows that do not have a stored model. This
preserves the original model selection for existing chats while providing a
fallback for historical data.

In
`@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message/mod.rs`:
- Around line 182-189: The status code returned in the ChatMessageError when
model access is denied does not match the endpoint's documented contract. In the
model access validation block where has_access is checked (around lines
182-189), the status field is set to StatusCode::FORBIDDEN, but the endpoint
documentation indicates that entitlement/access denial should return 402
(Payment Required) to align with the paywall UX contract. Update the status
field in the ChatMessageError returned from the model access check to use the
correct status code that matches the documented contract at Line 133.

In
`@rust/cloud-storage/document_cognition_service/src/api/structured_completion.rs`:
- Line 23: The structured completion handler accepts a model id in the request
struct but ignores it, always using model_access.model() instead. To fix this,
validate and access-check the request.model field to ensure it is a known and
authorized model id, resolve it to the corresponding runtime model, and use that
resolved model value for both agent phases in the handler. Return a 400 status
code if the model id is unknown or invalid. This ensures the handler honors the
caller's requested model rather than silently substituting a potentially
different model.

In
`@rust/cloud-storage/scheduled_action/src/outbound/inprocess_executor/agent_task.rs`:
- Line 107: The AgentTask.model is not being authorized before being persisted
or executed, allowing users to schedule unauthorized premium provider models.
Add ChatModelAccess authorization checks for action.owner at all affected
locations: (1) before storing messages at the line with model:
agent_task.model.clone() around line 107, (2) at the second affected site around
line 132-133, and (3) before AgentLoop::with_model at the third affected site
around line 193. Either implement access validation checks at each location
before using the model, or enforce authorization at task creation/update time
and validate again at dispatch time to prevent unauthorized model execution.

---

Outside diff comments:
In `@js/app/packages/service-clients/service-cognition/openapi.json`:
- Around line 1276-1321: The OpenAPI specification for the send_chat_message
operation documents a 402 status code with description "Payment required — user
lacks access to the requested model", but the backend handler returns 403
(FORBIDDEN) when model_access.has_access check fails. Choose one approach:
either modify the handler to return 402 to match the documented behavior, or
update the OpenAPI documentation to reflect that 403 (Forbidden) is returned for
access check failures. Ensure this change is applied consistently at all
affected locations in the specification where this mismatch occurs.

In `@rust/cloud-storage/ai_toolset/src/schema/error.rs`:
- Around line 1-45: The ValidationError enum and its modifications are located
in the immutable ai_toolset crate, which violates project guidelines that
explicitly prohibit any changes to rust/cloud-storage/ai_toolset/**. Revert all
changes made to this file, or if the functionality is necessary, move the
ValidationError enum and related code to an allowed crate outside the ai_toolset
directory.

In `@rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs`:
- Around line 1-234: The file
rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs containing the
FrontendSchemas, FrontendSchemasBuilder, and ToolSchemaGenerator implementations
is located in the ai_toolset crate, which is marked as immutable by project
policy. Move all the code in this file (FrontendToolEntry, ToolSchemaGenerator
trait, AsyncToolCollection implementation, FrontendSchemas struct with its
methods like mangle_collisions, find_owning_tool, value_contains_ref,
rename_refs, and rename_refs_in_value, along with FrontendSchemasBuilder) to a
different, mutable crate location outside of ai_toolset, then revert this file
to its original state before your changes.

In `@rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs`:
- Around line 1-59: The changes you've made to the ai_toolset crate (including
the Validate trait and validate_tool_schema function) violate repository policy,
which explicitly prohibits modifications to rust/cloud-storage/ai_toolset/**.
Move the implementation changes to an allowed location within the repository, or
obtain and document an explicit exception to the policy before this code can be
merged. If these are new additions rather than modifications to existing code,
clarify the business justification and get approval from the appropriate
stakeholders.

---

Nitpick comments:
In `@js/app/packages/core/component/AI/component/input/ChatInput.tsx`:
- Around line 63-69: The createEffect in the ChatInput component is using a
side-effect to derive and mutate state by calling input.setModel() based on
modelOptions() and model() reactivity, which violates SolidJS coding guidelines.
Replace this createEffect with a derived signal approach: create a derived
signal that computes the appropriate model ID based on the available options and
current model selection logic, then wrap the input.setModel setter with this
derived signal to handle the state update in a declarative, non-effect-based
manner. The derived signal should encapsulate the logic that checks option
availability and determines the first available model ID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ae0d84a-b9c7-4309-bbfc-066f200a1f51

📥 Commits

Reviewing files that changed from the base of the PR and between 85be656 and 1a43c4d.

⛔ Files ignored due to path filters (20)
  • js/app/packages/core/component/AI/assets/openai.svg is excluded by !**/*.svg
  • js/app/packages/service-clients/service-cognition/generated/client.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/agentModel.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/chatResponse.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/chatResponseModel.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/httpSendChatMessageRequest.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/index.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/model.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/modelAccess.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/modelsResponse.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/newChatMessage.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/sendChatMessagePayloadAllOf.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/streamErrorOneOf.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/structuredCompletionRequest.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/toolSchema.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/toolSchemas.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-scheduled-action/generated/schemas/agentModel.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-scheduled-action/generated/schemas/agentTask.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-scheduled-action/generated/schemas/index.ts is excluded by !**/generated/**
  • rust/cloud-storage/Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (100)
  • js/app/packages/app/component/SoupChatInput.tsx
  • js/app/packages/app/component/home/home.tsx
  • js/app/packages/core/component/AI/component/input/ChatInput.tsx
  • js/app/packages/core/component/AI/component/input/ModelSelector.tsx
  • js/app/packages/core/component/AI/component/input/buildRequest.ts
  • js/app/packages/core/component/AI/constant/model.ts
  • js/app/packages/core/util/create.ts
  • js/app/packages/queries/chat.ts
  • js/app/packages/service-clients/service-cognition/client.ts
  • js/app/packages/service-clients/service-cognition/openapi.json
  • js/app/packages/service-clients/service-scheduled-action/openapi.json
  • js/app/scripts/generate-dcs-tools.ts
  • rust/cloud-storage/agent/Cargo.toml
  • rust/cloud-storage/agent/src/agent_loop.rs
  • rust/cloud-storage/agent/src/anthropic_model.rs
  • rust/cloud-storage/agent/src/anthropic_model/test.rs
  • rust/cloud-storage/agent/src/completion.rs
  • rust/cloud-storage/agent/src/convert.rs
  • rust/cloud-storage/agent/src/convert/test.rs
  • rust/cloud-storage/agent/src/error.rs
  • rust/cloud-storage/agent/src/lib.rs
  • rust/cloud-storage/agent/src/model.rs
  • rust/cloud-storage/agent/src/model/anthropic.rs
  • rust/cloud-storage/agent/src/model/mod.rs
  • rust/cloud-storage/agent/src/model/openai.rs
  • rust/cloud-storage/agent/src/model/router.rs
  • rust/cloud-storage/agent/src/model/router/anthropic.rs
  • rust/cloud-storage/agent/src/model/router/openai.rs
  • rust/cloud-storage/agent/src/model/router/test.rs
  • rust/cloud-storage/agent/src/model/test.rs
  • rust/cloud-storage/agent/src/model/types.rs
  • rust/cloud-storage/agent/src/router.rs
  • rust/cloud-storage/agent/src/structured_output.rs
  • rust/cloud-storage/agent/src/tool_adapter.rs
  • rust/cloud-storage/agent/src/tool_adapter/test.rs
  • rust/cloud-storage/ai_tools/src/bin/gen_ai_request_schemas.rs
  • rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs
  • rust/cloud-storage/ai_tools/src/lib.rs
  • rust/cloud-storage/ai_tools/src/search/search_service/content.rs
  • rust/cloud-storage/ai_tools/src/search/search_service/name.rs
  • rust/cloud-storage/ai_toolset/src/schema/error.rs
  • rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs
  • rust/cloud-storage/ai_toolset/src/schema/generate.rs
  • rust/cloud-storage/ai_toolset/src/schema/generate/test.rs
  • rust/cloud-storage/ai_toolset/src/schema/mod.rs
  • rust/cloud-storage/ai_toolset/src/schema/phantom_tool.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/additional_properties.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/mod.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/nullify_optional.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/ref_siblings.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/required.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/rewrite_one_of.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/extract.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/one_of.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/recursive.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/recursive/test.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/strict.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/mod.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/object.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/tool_async.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/util.rs
  • rust/cloud-storage/ai_toolset/src/toolset/types.rs
  • rust/cloud-storage/call/src/inbound/toolset/test.rs
  • rust/cloud-storage/channels/src/inbound/toolset/test.rs
  • rust/cloud-storage/chat/src/domain/models/chat.rs
  • rust/cloud-storage/chat/src/domain/models/mod.rs
  • rust/cloud-storage/chat/src/domain/models/model_access.rs
  • rust/cloud-storage/chat/src/domain/ports/mod.rs
  • rust/cloud-storage/chat/src/domain/ports/model_access.rs
  • rust/cloud-storage/chat/src/domain/service/mod.rs
  • rust/cloud-storage/chat/src/domain/service/model_access.rs
  • rust/cloud-storage/chat/src/domain/service/model_access/test.rs
  • rust/cloud-storage/chat/src/inbound/http/extractors.rs
  • rust/cloud-storage/chat/src/inbound/http/mod.rs
  • rust/cloud-storage/chat/src/inbound/http/models.rs
  • rust/cloud-storage/chat/src/inbound/toolset/test.rs
  • rust/cloud-storage/chat/src/outbound/postgres/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/chats/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/chat_message/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/structured_completion.rs
  • rust/cloud-storage/document_cognition_service/src/api/swagger.rs
  • rust/cloud-storage/document_cognition_service/src/api/utils/log.rs
  • rust/cloud-storage/document_cognition_service/src/core/model.rs
  • rust/cloud-storage/document_cognition_service/src/model/chats.rs
  • rust/cloud-storage/document_cognition_service/src/model/stream.rs
  • rust/cloud-storage/document_cognition_service/src/models_bin.rs
  • rust/cloud-storage/document_cognition_service/src/service/get_chat.rs
  • rust/cloud-storage/documents/src/inbound/toolset/test.rs
  • rust/cloud-storage/email/src/inbound/toolset/test.rs
  • rust/cloud-storage/macro_db_client/src/dcs/create_chat.rs
  • rust/cloud-storage/model/src/chat/message.rs
  • rust/cloud-storage/notification/src/inbound/ai_tool/test.rs
  • rust/cloud-storage/properties/src/inbound/toolset/test.rs
  • rust/cloud-storage/scheduled_action/src/domain/models.rs
  • rust/cloud-storage/scheduled_action/src/outbound/inprocess_executor/agent_task.rs
  • rust/cloud-storage/soup/src/inbound/toolset/test.rs
  • rust/cloud-storage/teams/src/inbound/toolset/test.rs
💤 Files with no reviewable changes (7)
  • rust/cloud-storage/agent/src/anthropic_model/test.rs
  • rust/cloud-storage/agent/src/anthropic_model.rs
  • rust/cloud-storage/agent/src/model.rs
  • js/app/packages/app/component/home/home.tsx
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/object.rs
  • js/app/packages/app/component/SoupChatInput.tsx
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/util.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
js/app/packages/service-clients/service-cognition/openapi.json (1)

1276-1321: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align the no-access model status with the backend.

This request now accepts provider IDs, so paywall branching needs a stable status. The backend access check returns StatusCode::FORBIDDEN when model_access.has_access(&request.model) fails, but this operation documents 402 Payment required for that same case. Either return 402 from the handler or document/handle 403 consistently.

Also applies to: 2787-2790

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app/packages/service-clients/service-cognition/openapi.json` around lines
1276 - 1321, The OpenAPI specification for the send_chat_message operation
documents a 402 status code with description "Payment required — user lacks
access to the requested model", but the backend handler returns 403 (FORBIDDEN)
when model_access.has_access check fails. Choose one approach: either modify the
handler to return 402 to match the documented behavior, or update the OpenAPI
documentation to reflect that 403 (Forbidden) is returned for access check
failures. Ensure this change is applied consistently at all affected locations
in the specification where this mismatch occurs.
rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs (1)

1-59: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This change set modifies a crate marked as non-modifiable by repository policy.

These edits are under rust/cloud-storage/ai_toolset/**, which is explicitly designated as off-limits in the provided project rules. Please move the implementation to an allowed location or document an explicit exception before merge.

As per coding guidelines, rust/cloud-storage/ai_toolset/**: “You are not allowed to EVER change this crate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs` around lines 1 -
59, The changes you've made to the ai_toolset crate (including the Validate
trait and validate_tool_schema function) violate repository policy, which
explicitly prohibits modifications to rust/cloud-storage/ai_toolset/**. Move the
implementation changes to an allowed location within the repository, or obtain
and document an explicit exception to the policy before this code can be merged.
If these are new additions rather than modifications to existing code, clarify
the business justification and get approval from the appropriate stakeholders.

Source: Coding guidelines

rust/cloud-storage/ai_toolset/src/schema/error.rs (1)

1-45: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not modify ai_toolset in this PR.

Line 1 is within rust/cloud-storage/ai_toolset/**, which the project guidelines explicitly mark as immutable. Please move this behavior to an allowed crate or revert these changes.

As per coding guidelines: rust/cloud-storage/ai_toolset/**: “You are not allowed to EVER change this crate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/error.rs` around lines 1 - 45, The
ValidationError enum and its modifications are located in the immutable
ai_toolset crate, which violates project guidelines that explicitly prohibit any
changes to rust/cloud-storage/ai_toolset/**. Revert all changes made to this
file, or if the functionality is necessary, move the ValidationError enum and
related code to an allowed crate outside the ai_toolset directory.

Source: Coding guidelines

rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs (1)

1-234: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Blocked: changes landed in a forbidden crate path.

This file is inside rust/cloud-storage/ai_toolset/**, which is marked as immutable by project policy. Please move this implementation out of ai_toolset and revert direct edits in this crate before merge.

As per coding guidelines: rust/cloud-storage/ai_toolset/**: You are not allowed to _EVER_ change this crate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs` around lines 1
- 234, The file rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs
containing the FrontendSchemas, FrontendSchemasBuilder, and ToolSchemaGenerator
implementations is located in the ai_toolset crate, which is marked as immutable
by project policy. Move all the code in this file (FrontendToolEntry,
ToolSchemaGenerator trait, AsyncToolCollection implementation, FrontendSchemas
struct with its methods like mangle_collisions, find_owning_tool,
value_contains_ref, rename_refs, and rename_refs_in_value, along with
FrontendSchemasBuilder) to a different, mutable crate location outside of
ai_toolset, then revert this file to its original state before your changes.

Source: Coding guidelines

🧹 Nitpick comments (1)
js/app/packages/core/component/AI/component/input/ChatInput.tsx (1)

63-69: ⚡ Quick win

Replace derived-state createEffect with a derived signal/setter flow.

This effect is deriving and mutating local app state from reactive inputs, which conflicts with the SolidJS guideline for this codebase. Prefer a derived signal/wrapped setter approach here.

As per coding guidelines: “Avoid createEffect in SolidJs… Do not use to derive state or trigger updates—use a derived signal or wrap the setter instead”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app/packages/core/component/AI/component/input/ChatInput.tsx` around lines
63 - 69, The createEffect in the ChatInput component is using a side-effect to
derive and mutate state by calling input.setModel() based on modelOptions() and
model() reactivity, which violates SolidJS coding guidelines. Replace this
createEffect with a derived signal approach: create a derived signal that
computes the appropriate model ID based on the available options and current
model selection logic, then wrap the input.setModel setter with this derived
signal to handle the state update in a declarative, non-effect-based manner. The
derived signal should encapsulate the logic that checks option availability and
determines the first available model ID.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@js/app/packages/core/component/AI/component/input/ChatInput.tsx`:
- Around line 54-69: The models query needs to be resolved before allowing
message send to prevent sending a locked model. The createEffect fallback exits
early when modelOptions is empty (before the query finishes loading), but
canSendMessage still allows sending with the current model selection without
validation. Block sending in canSendMessage by checking the models query loading
state (modelsQuery.isPending or isLoading) or ensuring a valid model is
confirmed available before the send is allowed. This prevents sending a model
that hasn't been validated as available to the user yet, avoiding 403 errors and
ensuring the correct locked-model or paywall UX is shown instead.

In `@js/app/packages/service-clients/service-cognition/openapi.json`:
- Around line 373-394: The OpenAPI specification for the `/chats/models`
endpoint is missing a 401 response definition, even though the endpoint is
protected by ensure_user_exists middleware at runtime. Add a 401 response object
to the responses section alongside the existing 200 and 500 responses, with an
appropriate description and schema (typically application/json with an error
message or empty description with text/plain) to accurately document that
unauthenticated requests will receive a 401 Unauthorized response.
- Around line 1445-1454: The enum array in the AgentModel definition contains
duplicate values that violate OpenAPI schema constraints. Remove the duplicate
instances so that `claude-opus-4-8` and `claude-haiku-4-5` each appear only once
in the enum list. Keep the first occurrence of each duplicated value and remove
the subsequent occurrences.

In `@rust/cloud-storage/agent/Cargo.toml`:
- Around line 17-19: The rig-core dependency declaration in Cargo.toml currently
uses branch = "feat/responses-api-non-strict-tools" which creates a hybrid
pinning strategy between the lockfile and manifest. Replace the branch attribute
with rev = "6deadfc6f9b432c849ea79733a549f46407530b7" to make the pinned commit
explicit and single-sourced in the manifest, improving auditability and ensuring
the manifest and lockfile stay consistent without requiring manual
synchronization.

In `@rust/cloud-storage/agent/src/agent_loop.rs`:
- Around line 263-289: The StreamBridge attached at line 263 already emits usage
information via the on_stream_completion_response_finish hook, so the subsequent
emission of StreamPart::Usage in the FinalResponse match arm (lines 284-289)
creates a duplicate. Remove the code block that yields Ok(StreamPart::Usage)
from the FinalResponse match case, as the usage tokens are already being
properly emitted through the StreamBridge hook mechanism.

In `@rust/cloud-storage/agent/src/model/router.rs`:
- Around line 96-106: The route_or_default method currently catches all errors
from the self.route(model) call and silently returns an Anthropic default, which
masks real provider unavailability and configuration issues. Modify the error
handling to distinguish between error types: only return the default Anthropic
model for UnknownModel errors, while allowing ProviderUnavailable errors to
propagate up so they are not hidden by the automatic fallback to the default
model.

In `@rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs`:
- Line 6: The std::fs::create_dir call for creating the "schemas" directory
fails when the directory already exists, breaking repeated runs of the schema
generation tool. Replace the create_dir call with create_dir_all, which is
idempotent and will succeed whether the directory exists or not, making the
schema generation process rerunnable without errors.

In `@rust/cloud-storage/ai_toolset/src/schema/generate.rs`:
- Around line 1-91: The changes made to
`rust/cloud-storage/ai_toolset/src/schema/generate.rs` violate the repository
guardrail that forbids modifications to the `ai_toolset` crate. Revert all
changes in the `ai_toolset` directory and refactor the modifications to be made
in an allowed external crate or interface that wraps or extends the
functionality, ensuring the core `ai_toolset` crate remains unmodified as
required by project policy.

In `@rust/cloud-storage/ai_toolset/src/schema/mod.rs`:
- Around line 9-20: The file rust/cloud-storage/ai_toolset/src/schema/mod.rs is
in a crate designated as immutable by project rules and cannot be modified.
Either revert all changes made to files under rust/cloud-storage/ai_toolset/**,
move the implementation to an allowed crate and update the corresponding imports
and exports, or obtain explicit written approval from the project maintainers to
document an exception for this PR with clear justification for why the
immutability rule must be broken.

In `@rust/cloud-storage/ai_toolset/src/schema/transform/nullify_optional.rs`:
- Around line 70-96: The transform adds "null" to the type for nullable
properties but doesn't handle the case where a property has both type and const
constraints. When const is present, adding "null" to type is insufficient
because const still restricts the value to the original constant. After the type
union logic (around lines 70-95), add a check similar to the enum handling
(lines 97-101) to detect if a const field exists. If it does, convert it to an
enum array containing the original constant value and null, then remove the
const field, ensuring the property truly becomes nullable.

In `@rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs`:
- Around line 1-107: The new implementation of the StripUnsupported struct and
its Transform trait implementation has been added to the ai_toolset crate, which
violates the repository rule that this crate must never be modified. Either
revert all changes in this file
(rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs)
entirely, or move the StripUnsupported struct and its Transform implementation
to an allowed crate outside of ai_toolset, keeping the ai_toolset directory
completely untouched.

In `@rust/cloud-storage/chat/src/outbound/postgres/mod.rs`:
- Line 131: The model field assignment is currently always using FALLBACK_MODEL,
which causes reopened chats to lose their originally selected provider and model
information. Instead of unconditionally setting model to
FALLBACK_MODEL.to_string(), check if a persisted chat model exists in the
fetched data and use that value when available, only falling back to
FALLBACK_MODEL for legacy database rows that do not have a stored model. This
preserves the original model selection for existing chats while providing a
fallback for historical data.

In
`@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message/mod.rs`:
- Around line 182-189: The status code returned in the ChatMessageError when
model access is denied does not match the endpoint's documented contract. In the
model access validation block where has_access is checked (around lines
182-189), the status field is set to StatusCode::FORBIDDEN, but the endpoint
documentation indicates that entitlement/access denial should return 402
(Payment Required) to align with the paywall UX contract. Update the status
field in the ChatMessageError returned from the model access check to use the
correct status code that matches the documented contract at Line 133.

In
`@rust/cloud-storage/document_cognition_service/src/api/structured_completion.rs`:
- Line 23: The structured completion handler accepts a model id in the request
struct but ignores it, always using model_access.model() instead. To fix this,
validate and access-check the request.model field to ensure it is a known and
authorized model id, resolve it to the corresponding runtime model, and use that
resolved model value for both agent phases in the handler. Return a 400 status
code if the model id is unknown or invalid. This ensures the handler honors the
caller's requested model rather than silently substituting a potentially
different model.

In
`@rust/cloud-storage/scheduled_action/src/outbound/inprocess_executor/agent_task.rs`:
- Line 107: The AgentTask.model is not being authorized before being persisted
or executed, allowing users to schedule unauthorized premium provider models.
Add ChatModelAccess authorization checks for action.owner at all affected
locations: (1) before storing messages at the line with model:
agent_task.model.clone() around line 107, (2) at the second affected site around
line 132-133, and (3) before AgentLoop::with_model at the third affected site
around line 193. Either implement access validation checks at each location
before using the model, or enforce authorization at task creation/update time
and validate again at dispatch time to prevent unauthorized model execution.

---

Outside diff comments:
In `@js/app/packages/service-clients/service-cognition/openapi.json`:
- Around line 1276-1321: The OpenAPI specification for the send_chat_message
operation documents a 402 status code with description "Payment required — user
lacks access to the requested model", but the backend handler returns 403
(FORBIDDEN) when model_access.has_access check fails. Choose one approach:
either modify the handler to return 402 to match the documented behavior, or
update the OpenAPI documentation to reflect that 403 (Forbidden) is returned for
access check failures. Ensure this change is applied consistently at all
affected locations in the specification where this mismatch occurs.

In `@rust/cloud-storage/ai_toolset/src/schema/error.rs`:
- Around line 1-45: The ValidationError enum and its modifications are located
in the immutable ai_toolset crate, which violates project guidelines that
explicitly prohibit any changes to rust/cloud-storage/ai_toolset/**. Revert all
changes made to this file, or if the functionality is necessary, move the
ValidationError enum and related code to an allowed crate outside the ai_toolset
directory.

In `@rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs`:
- Around line 1-234: The file
rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs containing the
FrontendSchemas, FrontendSchemasBuilder, and ToolSchemaGenerator implementations
is located in the ai_toolset crate, which is marked as immutable by project
policy. Move all the code in this file (FrontendToolEntry, ToolSchemaGenerator
trait, AsyncToolCollection implementation, FrontendSchemas struct with its
methods like mangle_collisions, find_owning_tool, value_contains_ref,
rename_refs, and rename_refs_in_value, along with FrontendSchemasBuilder) to a
different, mutable crate location outside of ai_toolset, then revert this file
to its original state before your changes.

In `@rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs`:
- Around line 1-59: The changes you've made to the ai_toolset crate (including
the Validate trait and validate_tool_schema function) violate repository policy,
which explicitly prohibits modifications to rust/cloud-storage/ai_toolset/**.
Move the implementation changes to an allowed location within the repository, or
obtain and document an explicit exception to the policy before this code can be
merged. If these are new additions rather than modifications to existing code,
clarify the business justification and get approval from the appropriate
stakeholders.

---

Nitpick comments:
In `@js/app/packages/core/component/AI/component/input/ChatInput.tsx`:
- Around line 63-69: The createEffect in the ChatInput component is using a
side-effect to derive and mutate state by calling input.setModel() based on
modelOptions() and model() reactivity, which violates SolidJS coding guidelines.
Replace this createEffect with a derived signal approach: create a derived
signal that computes the appropriate model ID based on the available options and
current model selection logic, then wrap the input.setModel setter with this
derived signal to handle the state update in a declarative, non-effect-based
manner. The derived signal should encapsulate the logic that checks option
availability and determines the first available model ID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ae0d84a-b9c7-4309-bbfc-066f200a1f51

📥 Commits

Reviewing files that changed from the base of the PR and between 85be656 and 1a43c4d.

⛔ Files ignored due to path filters (20)
  • js/app/packages/core/component/AI/assets/openai.svg is excluded by !**/*.svg
  • js/app/packages/service-clients/service-cognition/generated/client.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/agentModel.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/chatResponse.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/chatResponseModel.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/httpSendChatMessageRequest.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/index.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/model.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/modelAccess.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/modelsResponse.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/newChatMessage.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/sendChatMessagePayloadAllOf.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/streamErrorOneOf.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/structuredCompletionRequest.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/toolSchema.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-cognition/generated/schemas/toolSchemas.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-scheduled-action/generated/schemas/agentModel.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-scheduled-action/generated/schemas/agentTask.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-scheduled-action/generated/schemas/index.ts is excluded by !**/generated/**
  • rust/cloud-storage/Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (100)
  • js/app/packages/app/component/SoupChatInput.tsx
  • js/app/packages/app/component/home/home.tsx
  • js/app/packages/core/component/AI/component/input/ChatInput.tsx
  • js/app/packages/core/component/AI/component/input/ModelSelector.tsx
  • js/app/packages/core/component/AI/component/input/buildRequest.ts
  • js/app/packages/core/component/AI/constant/model.ts
  • js/app/packages/core/util/create.ts
  • js/app/packages/queries/chat.ts
  • js/app/packages/service-clients/service-cognition/client.ts
  • js/app/packages/service-clients/service-cognition/openapi.json
  • js/app/packages/service-clients/service-scheduled-action/openapi.json
  • js/app/scripts/generate-dcs-tools.ts
  • rust/cloud-storage/agent/Cargo.toml
  • rust/cloud-storage/agent/src/agent_loop.rs
  • rust/cloud-storage/agent/src/anthropic_model.rs
  • rust/cloud-storage/agent/src/anthropic_model/test.rs
  • rust/cloud-storage/agent/src/completion.rs
  • rust/cloud-storage/agent/src/convert.rs
  • rust/cloud-storage/agent/src/convert/test.rs
  • rust/cloud-storage/agent/src/error.rs
  • rust/cloud-storage/agent/src/lib.rs
  • rust/cloud-storage/agent/src/model.rs
  • rust/cloud-storage/agent/src/model/anthropic.rs
  • rust/cloud-storage/agent/src/model/mod.rs
  • rust/cloud-storage/agent/src/model/openai.rs
  • rust/cloud-storage/agent/src/model/router.rs
  • rust/cloud-storage/agent/src/model/router/anthropic.rs
  • rust/cloud-storage/agent/src/model/router/openai.rs
  • rust/cloud-storage/agent/src/model/router/test.rs
  • rust/cloud-storage/agent/src/model/test.rs
  • rust/cloud-storage/agent/src/model/types.rs
  • rust/cloud-storage/agent/src/router.rs
  • rust/cloud-storage/agent/src/structured_output.rs
  • rust/cloud-storage/agent/src/tool_adapter.rs
  • rust/cloud-storage/agent/src/tool_adapter/test.rs
  • rust/cloud-storage/ai_tools/src/bin/gen_ai_request_schemas.rs
  • rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs
  • rust/cloud-storage/ai_tools/src/lib.rs
  • rust/cloud-storage/ai_tools/src/search/search_service/content.rs
  • rust/cloud-storage/ai_tools/src/search/search_service/name.rs
  • rust/cloud-storage/ai_toolset/src/schema/error.rs
  • rust/cloud-storage/ai_toolset/src/schema/frontend_typegen.rs
  • rust/cloud-storage/ai_toolset/src/schema/generate.rs
  • rust/cloud-storage/ai_toolset/src/schema/generate/test.rs
  • rust/cloud-storage/ai_toolset/src/schema/mod.rs
  • rust/cloud-storage/ai_toolset/src/schema/phantom_tool.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/additional_properties.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/mod.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/nullify_optional.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/ref_siblings.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/required.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/rewrite_one_of.rs
  • rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/extract.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/mod.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/one_of.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/recursive.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/recursive/test.rs
  • rust/cloud-storage/ai_toolset/src/schema/validate/strict.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/mod.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/object.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/tool_async.rs
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/util.rs
  • rust/cloud-storage/ai_toolset/src/toolset/types.rs
  • rust/cloud-storage/call/src/inbound/toolset/test.rs
  • rust/cloud-storage/channels/src/inbound/toolset/test.rs
  • rust/cloud-storage/chat/src/domain/models/chat.rs
  • rust/cloud-storage/chat/src/domain/models/mod.rs
  • rust/cloud-storage/chat/src/domain/models/model_access.rs
  • rust/cloud-storage/chat/src/domain/ports/mod.rs
  • rust/cloud-storage/chat/src/domain/ports/model_access.rs
  • rust/cloud-storage/chat/src/domain/service/mod.rs
  • rust/cloud-storage/chat/src/domain/service/model_access.rs
  • rust/cloud-storage/chat/src/domain/service/model_access/test.rs
  • rust/cloud-storage/chat/src/inbound/http/extractors.rs
  • rust/cloud-storage/chat/src/inbound/http/mod.rs
  • rust/cloud-storage/chat/src/inbound/http/models.rs
  • rust/cloud-storage/chat/src/inbound/toolset/test.rs
  • rust/cloud-storage/chat/src/outbound/postgres/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/chats/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/chat_message/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/stream/util/chat_message/mod.rs
  • rust/cloud-storage/document_cognition_service/src/api/structured_completion.rs
  • rust/cloud-storage/document_cognition_service/src/api/swagger.rs
  • rust/cloud-storage/document_cognition_service/src/api/utils/log.rs
  • rust/cloud-storage/document_cognition_service/src/core/model.rs
  • rust/cloud-storage/document_cognition_service/src/model/chats.rs
  • rust/cloud-storage/document_cognition_service/src/model/stream.rs
  • rust/cloud-storage/document_cognition_service/src/models_bin.rs
  • rust/cloud-storage/document_cognition_service/src/service/get_chat.rs
  • rust/cloud-storage/documents/src/inbound/toolset/test.rs
  • rust/cloud-storage/email/src/inbound/toolset/test.rs
  • rust/cloud-storage/macro_db_client/src/dcs/create_chat.rs
  • rust/cloud-storage/model/src/chat/message.rs
  • rust/cloud-storage/notification/src/inbound/ai_tool/test.rs
  • rust/cloud-storage/properties/src/inbound/toolset/test.rs
  • rust/cloud-storage/scheduled_action/src/domain/models.rs
  • rust/cloud-storage/scheduled_action/src/outbound/inprocess_executor/agent_task.rs
  • rust/cloud-storage/soup/src/inbound/toolset/test.rs
  • rust/cloud-storage/teams/src/inbound/toolset/test.rs
💤 Files with no reviewable changes (7)
  • rust/cloud-storage/agent/src/anthropic_model/test.rs
  • rust/cloud-storage/agent/src/anthropic_model.rs
  • rust/cloud-storage/agent/src/model.rs
  • js/app/packages/app/component/home/home.tsx
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/object.rs
  • js/app/packages/app/component/SoupChatInput.tsx
  • rust/cloud-storage/ai_toolset/src/toolset/tool_object/util.rs
🛑 Comments failed to post (15)
js/app/packages/core/component/AI/component/input/ChatInput.tsx (1)

54-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block sends until model availability is resolved and valid for the selected model.

While useModelsQuery() is still unresolved, modelOptions() is empty (Line 54-59) and the fallback effect exits early (Line 65), but canSendMessage() still allows send and sendInput.model uses current selection (Line 118). This can send a locked model before entitlement data arrives, causing avoidable 403/error UX instead of the locked-model/paywall path.

💡 Suggested fix
-  const modelOptions = createMemo(() =>
-    (modelsQuery.data?.models ?? []).map((m) => ({
+  const modelOptions = createMemo(() =>
+    (modelsQuery.data?.models ?? null)?.map((m) => ({
       id: m.id as Model,
       available: m.available,
     }))
   );
+
+  const selectedModelAvailable = () => {
+    const options = modelOptions();
+    if (!options) return false; // unresolved
+    return options.some((o) => o.id === model() && o.available);
+  };
@@
-  const canSendMessage = () =>
-    !isEmptyInput() && !generating() && !hasUploadingAttachments();
+  const canSendMessage = () =>
+    !!modelOptions() &&
+    selectedModelAvailable() &&
+    !isEmptyInput() &&
+    !generating() &&
+    !hasUploadingAttachments();

Also applies to: 94-95, 116-119

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app/packages/core/component/AI/component/input/ChatInput.tsx` around lines
54 - 69, The models query needs to be resolved before allowing message send to
prevent sending a locked model. The createEffect fallback exits early when
modelOptions is empty (before the query finishes loading), but canSendMessage
still allows sending with the current model selection without validation. Block
sending in canSendMessage by checking the models query loading state
(modelsQuery.isPending or isLoading) or ensuring a valid model is confirmed
available before the send is allowed. This prevents sending a model that hasn't
been validated as available to the user yet, avoiding 403 errors and ensuring
the correct locked-model or paywall UX is shown instead.
js/app/packages/service-clients/service-cognition/openapi.json (2)

373-394: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the authenticated error response for /chats/models.

The router wraps this endpoint with ensure_user_exists, so unauthenticated requests can return 401, but the spec only exposes 200/500. Add the 401 response so generated clients and docs match runtime behavior.

Proposed OpenAPI response fix
           "200": {
             "description": "",
             "content": {
               "application/json": {
                 "schema": {
                   "$ref": "`#/components/schemas/ModelsResponse`"
                 }
               }
             }
+          },
+          "401": {
+            "description": "",
+            "content": {
+              "text/plain": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            }
           },
           "500": {
             "description": "",
             "content": {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "`#/components/schemas/ModelsResponse`"
                }
              }
            }
          },
          "401": {
            "description": "",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app/packages/service-clients/service-cognition/openapi.json` around lines
373 - 394, The OpenAPI specification for the `/chats/models` endpoint is missing
a 401 response definition, even though the endpoint is protected by
ensure_user_exists middleware at runtime. Add a 401 response object to the
responses section alongside the existing 200 and 500 responses, with an
appropriate description and schema (typically application/json with an error
message or empty description with text/plain) to accurately document that
unauthenticated requests will receive a 401 Unauthorized response.

1445-1454: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import collections
import json

path = "js/app/packages/service-clients/service-cognition/openapi.json"
with open(path) as f:
    spec = json.load(f)

enum_values = spec["components"]["schemas"]["AgentModel"]["enum"]
dupes = {value: count for value, count in collections.Counter(enum_values).items() if count > 1}
print(dupes)
raise SystemExit(1 if dupes else 0)
PY

Repository: macro-inc/macro

Length of output: 105


Remove duplicate enum values from AgentModel in OpenAPI spec.

The enum contains duplicate provider ids that violate OpenAPI schema constraints. Deduplicate by keeping only one instance of claude-opus-4-8 and claude-haiku-4-5.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app/packages/service-clients/service-cognition/openapi.json` around lines
1445 - 1454, The enum array in the AgentModel definition contains duplicate
values that violate OpenAPI schema constraints. Remove the duplicate instances
so that `claude-opus-4-8` and `claude-haiku-4-5` each appear only once in the
enum list. Keep the first occurrence of each duplicated value and remove the
subsequent occurrences.
rust/cloud-storage/agent/Cargo.toml (1)

17-19: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Checking manifest declaration:"
rg -n '^\s*rig-core\s*=' rust/cloud-storage/agent/Cargo.toml

echo
echo "Checking lockfile source pin (if present):"
LOCKFILE="$(fd '^Cargo\.lock$' | head -n1 || true)"
if [ -n "${LOCKFILE}" ]; then
  rg -n 'name = "rig-core"|source = "git\+https://github.com/macro-inc/rig' "$LOCKFILE"
else
  echo "Cargo.lock not found in provided checkout."
fi

Repository: macro-inc/macro

Length of output: 241


🏁 Script executed:

# Check if Cargo.lock exists at workspace root
fd -t f '^Cargo\.lock$' rust/cloud-storage/

Repository: macro-inc/macro

Length of output: 89


🏁 Script executed:

# If Cargo.lock exists, extract rig-core details
if [ -f "rust/cloud-storage/Cargo.lock" ]; then
  echo "=== Lockfile rig-core entry ==="; 
  rg -A 5 'name = "rig-core"' rust/cloud-storage/Cargo.lock | head -20
else
  echo "No Cargo.lock at workspace root"
fi

Repository: macro-inc/macro

Length of output: 307


🏁 Script executed:

# Check agent crate Cargo.toml for other git dependencies or conventions
cat rust/cloud-storage/agent/Cargo.toml | head -30

Repository: macro-inc/macro

Length of output: 1000


Use rev instead of branch to make the pinned commit explicit in the manifest.

The workspace maintains Cargo.lock, which already resolves this dependency to commit 6deadfc6f9b432c849ea79733a549f46407530b7. However, declaring branch = "feat/responses-api-non-strict-tools" in the manifest creates a hybrid pinning strategy that requires lockfile and manifest to stay in sync. Using rev = "6deadfc6f9b432c849ea79733a549f46407530b7" in the manifest makes the pinned commit explicit and single-sourced, improving auditability.

Suggested manifest change
-rig-core = { git = "https://github.com/macro-inc/rig", branch = "feat/responses-api-non-strict-tools" }
+rig-core = { git = "https://github.com/macro-inc/rig", rev = "6deadfc6f9b432c849ea79733a549f46407530b7" }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/agent/Cargo.toml` around lines 17 - 19, The rig-core
dependency declaration in Cargo.toml currently uses branch =
"feat/responses-api-non-strict-tools" which creates a hybrid pinning strategy
between the lockfile and manifest. Replace the branch attribute with rev =
"6deadfc6f9b432c849ea79733a549f46407530b7" to make the pinned commit explicit
and single-sourced in the manifest, improving auditability and ensuring the
manifest and lockfile stay consistent without requiring manual synchronization.
rust/cloud-storage/agent/src/agent_loop.rs (1)

263-289: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Usage tokens are emitted twice in the streaming path.

Line 263 attaches StreamBridge, and Lines 284-289 emit StreamPart::Usage again from FinalResponse. This can duplicate usage events per request.

Minimal fix
-                        Ok(MultiTurnStreamItem::FinalResponse(final_resp)) => {
-                            let usage = final_resp.usage();
-                            yield Ok(StreamPart::Usage(crate::stream::Usage {
-                                input_tokens: usage.input_tokens,
-                                output_tokens: usage.output_tokens,
-                            }));
-                        }
+                        Ok(MultiTurnStreamItem::FinalResponse(_)) => {}

Based on learnings from the provided context snippet rust/cloud-storage/agent/src/hook.rs:44-121, usage is already emitted in on_stream_completion_response_finish.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/agent/src/agent_loop.rs` around lines 263 - 289, The
StreamBridge attached at line 263 already emits usage information via the
on_stream_completion_response_finish hook, so the subsequent emission of
StreamPart::Usage in the FinalResponse match arm (lines 284-289) creates a
duplicate. Remove the code block that yields Ok(StreamPart::Usage) from the
FinalResponse match case, as the usage tokens are already being properly emitted
through the StreamBridge hook mechanism.
rust/cloud-storage/agent/src/model/router.rs (1)

96-106: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Propagate routing failures instead of silently downgrading to default.

Line 100 currently catches both UnknownModel and ProviderUnavailable and always returns Anthropic default. That can serve a different model/provider than requested and hide real provider/config outages.

Suggested direction
- pub fn route_or_default(&self, model: &str) -> RoutedModel {
-     self.route(model).unwrap_or_else(|_| {
-         RoutedModel::Anthropic(AnthropicModel::new(
-             self.anthropic_client.clone(),
-             super::AgentModel::default().to_string(),
-         ))
-     })
- }
+ pub fn route_or_default(&self, model: &str) -> crate::error::Result<RoutedModel> {
+     match self.route(model) {
+         Ok(routed) => Ok(routed),
+         Err(AgentError::UnknownModel(_)) => Ok(RoutedModel::Anthropic(AnthropicModel::new(
+             self.anthropic_client.clone(),
+             super::AgentModel::default().to_string(),
+         ))),
+         Err(e) => Err(e),
+     }
+ }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/agent/src/model/router.rs` around lines 96 - 106, The
route_or_default method currently catches all errors from the self.route(model)
call and silently returns an Anthropic default, which masks real provider
unavailability and configuration issues. Modify the error handling to
distinguish between error types: only return the default Anthropic model for
UnknownModel errors, while allowing ProviderUnavailable errors to propagate up
so they are not hidden by the automatic fallback to the default model.
rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs (1)

6-6: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make schema directory creation idempotent.

create_dir("schemas") fails on reruns when the directory already exists, which breaks repeated schema-generation runs.

Proposed fix
-    std::fs::create_dir("schemas").expect("create schemas dir");
+    std::fs::create_dir_all("schemas").expect("create schemas dir");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs` at line 6, The
std::fs::create_dir call for creating the "schemas" directory fails when the
directory already exists, breaking repeated runs of the schema generation tool.
Replace the create_dir call with create_dir_all, which is idempotent and will
succeed whether the directory exists or not, making the schema generation
process rerunnable without errors.
rust/cloud-storage/ai_toolset/src/schema/generate.rs (1)

1-91: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

ai_toolset modifications violate a repository guardrail.

This PR introduces direct changes in rust/cloud-storage/ai_toolset/**, which is explicitly marked as non-modifiable by project policy. Please move these changes behind allowed crates/interfaces (or get an explicit exception before merge).

As per coding guidelines, rust/cloud-storage/ai_toolset/**: “You are not allowed to EVER change this crate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/generate.rs` around lines 1 - 91,
The changes made to `rust/cloud-storage/ai_toolset/src/schema/generate.rs`
violate the repository guardrail that forbids modifications to the `ai_toolset`
crate. Revert all changes in the `ai_toolset` directory and refactor the
modifications to be made in an allowed external crate or interface that wraps or
extends the functionality, ensuring the core `ai_toolset` crate remains
unmodified as required by project policy.

Source: Coding guidelines

rust/cloud-storage/ai_toolset/src/schema/mod.rs (1)

9-20: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This change touches a crate marked as immutable by project rules.

These edits are under rust/cloud-storage/ai_toolset/**, which the provided repository guidelines designate as do-not-change. Please move this implementation to an allowed crate, revert these edits, or document an explicit exception for this PR.

As per coding guidelines, rust/cloud-storage/ai_toolset/**: “You are not allowed to EVER change this crate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/mod.rs` around lines 9 - 20, The
file rust/cloud-storage/ai_toolset/src/schema/mod.rs is in a crate designated as
immutable by project rules and cannot be modified. Either revert all changes
made to files under rust/cloud-storage/ai_toolset/**, move the implementation to
an allowed crate and update the corresponding imports and exports, or obtain
explicit written approval from the project maintainers to document an exception
for this PR with clear justification for why the immutability rule must be
broken.

Source: Coding guidelines

rust/cloud-storage/ai_toolset/src/schema/transform/nullify_optional.rs (1)

70-96: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Optional fields with const can remain effectively non-nullable.

When a property has both type and const, this transform adds "null" to type but leaves const unchanged, so null is still invalid. After AddRequired, that field becomes required with a single constant value instead of nullable.

Suggested fix
 fn make_nullable(property: &mut Value) {
     let Some(obj) = property.as_object_mut() else {
         return;
     };

+    // `const` plus type-union does not actually admit null; wrap instead.
+    if obj.contains_key("const") {
+        let original = Value::Object(std::mem::take(obj));
+        *property = json!({"anyOf": [original, {"type": "null"}]});
+        return;
+    }
+
     // Union node: add a null variant unless one exists.
     if let Some(Value::Array(variants)) = obj.get_mut("anyOf") {
         let has_null = variants
             .iter()
             .any(|v| v.as_object().is_some_and(has_null_type));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/transform/nullify_optional.rs`
around lines 70 - 96, The transform adds "null" to the type for nullable
properties but doesn't handle the case where a property has both type and const
constraints. When const is present, adding "null" to type is insufficient
because const still restricts the value to the original constant. After the type
union logic (around lines 70-95), add a check similar to the enum handling
(lines 97-101) to detect if a const field exists. If it does, convert it to an
enum array containing the original constant value and null, then remove the
const field, ensuring the property truly becomes nullable.
rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs (1)

1-107: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not modify ai_toolset in this PR.

This file introduces new code under rust/cloud-storage/ai_toolset/**, which violates the repository rule that this crate must not be changed. Please revert these edits (or move the implementation to an allowed crate and keep ai_toolset untouched).

As per coding guidelines: rust/cloud-storage/ai_toolset/**: “You are not allowed to EVER change this crate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs`
around lines 1 - 107, The new implementation of the StripUnsupported struct and
its Transform trait implementation has been added to the ai_toolset crate, which
violates the repository rule that this crate must never be modified. Either
revert all changes in this file
(rust/cloud-storage/ai_toolset/src/schema/transform/strip_unsupported.rs)
entirely, or move the StripUnsupported struct and its Transform implementation
to an allowed crate outside of ai_toolset, keeping the ai_toolset directory
completely untouched.

Source: Coding guidelines

rust/cloud-storage/chat/src/outbound/postgres/mod.rs (1)

131-131: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return the stored chat model before falling back.

This still reports every fetched chat as FALLBACK_MODEL, so reopened chats lose the selected provider id and consumers can render/continue with the wrong model. Use the persisted chat model when present and fall back only for legacy rows.

Proposed fix
-            model: Some(FALLBACK_MODEL.to_string()),
+            model: chat.model.or_else(|| Some(FALLBACK_MODEL.to_string())),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            model: chat.model.or_else(|| Some(FALLBACK_MODEL.to_string())),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/cloud-storage/chat/src/outbound/postgres/mod.rs` at line 131, The model
field assignment is currently always using FALLBACK_MODEL, which causes reopened
chats to lose their originally selected provider and model information. Instead
of unconditionally setting model to FALLBACK_MODEL.to_string(), check if a
persisted chat model exists in the fetched data and use that value when
available, only falling back to FALLBACK_MODEL for legacy database rows that do
not have a stored model. This preserves the original model selection for
existing chats while providing a fallback for historical data.
rust/cloud-storage/document_cognition_service/src/api/stream/chat_message/mod.rs (1)

182-189: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align entitlement denial status with the endpoint’s documented/paywall contract.

Line 184 enforces model access, but Line 188 returns StatusCode::FORBIDDEN while this endpoint documents “user lacks access to requested model” as 402 (Line 133). This contract drift can break clients that map entitlement failures to paywall UX by status code.

Suggested fix
-            status: Some(StatusCode::FORBIDDEN),
+            status: Some(StatusCode::PAYMENT_REQUIRED),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@rust/cloud-storage/document_cognition_service/src/api/stream/chat_message/mod.rs`
around lines 182 - 189, The status code returned in the ChatMessageError when
model access is denied does not match the endpoint's documented contract. In the
model access validation block where has_access is checked (around lines
182-189), the status field is set to StatusCode::FORBIDDEN, but the endpoint
documentation indicates that entitlement/access denial should return 402
(Payment Required) to align with the paywall UX contract. Update the status
field in the ChatMessageError returned from the model access check to use the
correct status code that matches the documented contract at Line 133.
rust/cloud-storage/document_cognition_service/src/api/structured_completion.rs (1)

23-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor the requested model id in structured completion.

Line 23 accepts a model id, but the handler ignores it and always uses model_access.model() (Line 75). This silently drops caller intent and can run a different (potentially higher-cost) model than requested. Validate/access-check request.model, resolve it to the runtime model, and use that resolved value for both agent phases; return 400 for unknown ids.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@rust/cloud-storage/document_cognition_service/src/api/structured_completion.rs`
at line 23, The structured completion handler accepts a model id in the request
struct but ignores it, always using model_access.model() instead. To fix this,
validate and access-check the request.model field to ensure it is a known and
authorized model id, resolve it to the corresponding runtime model, and use that
resolved model value for both agent phases in the handler. Return a 400 status
code if the model id is unknown or invalid. This ensures the handler honors the
caller's requested model rather than silently substituting a potentially
different model.
rust/cloud-storage/scheduled_action/src/outbound/inprocess_executor/agent_task.rs (1)

107-107: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Authorize the scheduled task model before persisting or running it.

AgentTask.model comes from persisted task JSON, and these usages store/run it directly. Unlike the chat send path, there is no ChatModelAccess check here, so a user can schedule a premium provider id and have the worker execute it later. Re-check access for action.owner before storing messages and before AgentLoop::with_model, or reject unauthorized models at create/update and again at dispatch.

Also applies to: 132-132, 193-193

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@rust/cloud-storage/scheduled_action/src/outbound/inprocess_executor/agent_task.rs`
at line 107, The AgentTask.model is not being authorized before being persisted
or executed, allowing users to schedule unauthorized premium provider models.
Add ChatModelAccess authorization checks for action.owner at all affected
locations: (1) before storing messages at the line with model:
agent_task.model.clone() around line 107, (2) at the second affected site around
line 132-133, and (3) before AgentLoop::with_model at the third affected site
around line 193. Either implement access validation checks at each location
before using the model, or enforce authorization at task creation/update time
and validate again at dispatch time to prevent unauthorized model execution.

@github-actions

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant