From d16b3ef07d0a543793d08a2871db770ffa016569 Mon Sep 17 00:00:00 2001 From: roroghost17 Date: Thu, 23 Jul 2026 14:02:16 +0530 Subject: [PATCH 1/4] fix: fixes whitespace trimming on connectors form fields --- ui/lib/utils/secretVarForm.ts | 6 ++++++ ui/lib/utils/strings.test.ts | 31 +++++++++++++++++++++++++++++-- ui/lib/utils/strings.ts | 16 ++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/ui/lib/utils/secretVarForm.ts b/ui/lib/utils/secretVarForm.ts index 296bf535969..abab8616ced 100644 --- a/ui/lib/utils/secretVarForm.ts +++ b/ui/lib/utils/secretVarForm.ts @@ -9,6 +9,12 @@ function inferType(ref: string | undefined): SecretVar["type"] | undefined { export const emptySecretVar = (): SecretVar => ({ value: "", ref: "" }); +// trimSecretVar strips stray whitespace from a SecretVar form value's literal value and reference. +export const trimSecretVar = (field: T): T => { + if (!field) return field; + return { ...field, value: field.value?.trim(), ref: field.ref?.trim() }; +}; + export const toSecretVarFormValue = (field?: SecretVar | string): SecretVar => { if (!field) return emptySecretVar(); if (typeof field === "string") { diff --git a/ui/lib/utils/strings.test.ts b/ui/lib/utils/strings.test.ts index 22413078c44..5fa80b23531 100644 --- a/ui/lib/utils/strings.test.ts +++ b/ui/lib/utils/strings.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from "vitest"; -import { cleanNumericInput } from "./strings"; +import { cleanNumericInput, trimFields } from "./strings"; // Simulate what onChange does: clean → Number() function simulateOnChange(raw: string): { display: string; value: number | undefined } { @@ -201,4 +201,31 @@ describe("simulateOnBlur (normalize display)", () => { test("1000 → 1000", () => { expect(simulateOnBlur("1000")).toEqual({ display: "1000", value: 1000 }); }); -}); \ No newline at end of file +}); +describe("trimFields", () => { + test("trims string fields in place", () => { + const obj = { topic: " my-topic ", project: "\tproj\n" }; + trimFields(obj, "topic", "project"); + expect(obj).toEqual({ topic: "my-topic", project: "proj" }); + }); + test("trims string array fields", () => { + const obj = { brokers: [" a:9092", "b:9092 ", " c:9092 "] }; + trimFields(obj, "brokers"); + expect(obj.brokers).toEqual(["a:9092", "b:9092", "c:9092"]); + }); + test("leaves undefined fields untouched", () => { + const obj: { name: string; ml_app?: string } = { name: " x " }; + trimFields(obj, "name", "ml_app"); + expect(obj).toEqual({ name: "x" }); + expect("ml_app" in obj).toBe(false); + }); + test("only touches the named fields", () => { + const obj = { a: " x ", b: " y " }; + trimFields(obj, "a"); + expect(obj).toEqual({ a: "x", b: " y " }); + }); + test("returns the same object", () => { + const obj = { a: " x " }; + expect(trimFields(obj, "a")).toBe(obj); + }); +}); diff --git a/ui/lib/utils/strings.ts b/ui/lib/utils/strings.ts index 0679bb1d694..61e780bfe66 100644 --- a/ui/lib/utils/strings.ts +++ b/ui/lib/utils/strings.ts @@ -2,6 +2,22 @@ export function capitalize(name: string) { return name.charAt(0).toUpperCase() + name.slice(1); } +export type TrimmableKeys = { [K in keyof T]: T[K] extends string | string[] | undefined ? K : never }[keyof T]; + +// trimFields trims whitespace from the named string (or string[]) fields of obj, in place. +// Undefined fields are left untouched. +export function trimFields(obj: T, ...keys: TrimmableKeys[]): T { + for (const key of keys) { + const value = obj[key]; + if (typeof value === "string") { + obj[key] = value.trim() as T[typeof key]; + } else if (Array.isArray(value)) { + obj[key] = value.map((item) => (typeof item === "string" ? item.trim() : item)) as T[typeof key]; + } + } + return obj; +} + // Cleans raw input into a valid numeric string: // - Single non-alphabetic separator between digits (commas, spaces, underscores) → stripped // - Alphabetic characters → stop processing From e36ff50a5086627a104c77a080b3c0591451d5ec Mon Sep 17 00:00:00 2001 From: roroghost17 Date: Thu, 23 Jul 2026 15:59:02 +0530 Subject: [PATCH 2/4] fix: fixes error forwarding to connectors --- core/bifrost.go | 4 ++++ core/providers/utils/utils.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/core/bifrost.go b/core/bifrost.go index 259b0cabea9..6dbac1b508a 100644 --- a/core/bifrost.go +++ b/core/bifrost.go @@ -6176,6 +6176,10 @@ func executeRequestWithRetries[T any]( } resp.PopulateExtraFields(requestType, providerKey, model, resolvedModelUsed) tracer.PopulateLLMResponseAttributes(ctx, handle, resp, bifrostError) + } else if bifrostError != nil { + // Failed stream requests carry a chan result and miss the cast above; + // stamp error attributes explicitly so spans don't report unknown. + tracer.PopulateLLMResponseAttributes(ctx, handle, nil, bifrostError) } // End span with appropriate status diff --git a/core/providers/utils/utils.go b/core/providers/utils/utils.go index c999cdf61dc..1ada81440f8 100644 --- a/core/providers/utils/utils.go +++ b/core/providers/utils/utils.go @@ -3398,6 +3398,9 @@ func completeDeferredSpan(ctx *schemas.BifrostContext, result *schemas.BifrostRe } else if result != nil { // Fall back to final chunk if no accumulated data (shouldn't happen normally) tracer.PopulateLLMResponseAttributes(ctx, handle, result, err) + } else if err != nil { + // Stream failed before the first chunk — still stamp error attributes. + tracer.PopulateLLMResponseAttributes(ctx, handle, nil, err) } // Finalize aggregated post-hook spans before ending the LLM span From c019b24ccc1060aaca1267fd3f4931527bbc9898 Mon Sep 17 00:00:00 2001 From: roroghost17 Date: Thu, 23 Jul 2026 16:15:20 +0530 Subject: [PATCH 3/4] chore: update OTEL test harness with error scenarios --- .../api/runners/run-observability-local.mjs | 94 ++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/tests/e2e/api/runners/run-observability-local.mjs b/tests/e2e/api/runners/run-observability-local.mjs index b31be458f5a..173cb9af472 100644 --- a/tests/e2e/api/runners/run-observability-local.mjs +++ b/tests/e2e/api/runners/run-observability-local.mjs @@ -8,6 +8,11 @@ const providerName = `otel-e2e-${process.pid}-${Date.now()}`; const modelName = "hello-world"; const requestedModel = `${providerName}/${modelName}`; const requestID = `otel-e2e-request-${process.pid}-${Date.now()}`; +const errorRequestID = `otel-e2e-error-${process.pid}-${Date.now()}`; +const streamErrorRequestID = `otel-e2e-stream-error-${process.pid}-${Date.now()}`; + +// Message marker that makes the mock provider return a 404 error body. +const ERROR_TRIGGER = "trigger-error"; const state = { otelTraceRequests: [], @@ -74,6 +79,19 @@ function createOpenAIMock() { headers: req.headers, body: body.toString("utf8"), }); + // Error scenarios: the trigger marker gets a provider-style 404, streaming + // or not — this is the pre-first-chunk failure path. + if (body.toString("utf8").includes(ERROR_TRIGGER)) { + res.writeHead(404, { "content-type": "application/json" }); + res.end(JSON.stringify({ + error: { + message: "The model does not exist", + type: "invalid_request_error", + code: "model_not_found", + }, + })); + return; + } const now = Math.floor(Date.now() / 1000); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ @@ -171,6 +189,7 @@ async function addLocalProvider(mockPort) { is_key_less: true, allowed_requests: { chat_completion: true, + chat_completion_stream: true, }, }, network_config: { @@ -204,6 +223,24 @@ async function chatHelloWorld() { } } +// chatError fires a request the mock fails with a 404 error body; stream: true +// exercises the pre-first-chunk stream failure path. +async function chatError(id, stream) { + const res = await request("POST", "/v1/chat/completions", { + model: requestedModel, + messages: [{ role: "user", content: ERROR_TRIGGER }], + ...(stream ? { stream: true } : {}), + }, { + "x-request-id": id, + }); + if (res.ok) { + throw new Error(`error request (stream=${stream}) unexpectedly succeeded: ${res.text}`); + } + if (res.status !== 404) { + throw new Error(`error request (stream=${stream}) status=${res.status}, want 404: ${res.text}`); + } +} + async function poll(name, timeoutMs, fn) { const started = Date.now(); let lastError; @@ -270,6 +307,46 @@ async function assertOtelMetricsReceived() { ]); } +// assertOtelErrorTrace checks the error span export carries the provider error +// attributes. The stream case pins core's deferred-span error stamping, which +// regressed silently before (spans exported with no gen_ai.error.* when a +// stream failed before its first chunk). +async function assertOtelErrorTrace(id, label) { + const entry = await poll(`OTEL ${label} trace receiver`, 20000, + () => state.otelTraceRequests.find((item) => item.body.includes(Buffer.from(id)))); + assertBufferContainsAll(`OTEL ${label} trace export`, entry.body, [ + id, + "gen_ai.error.type", + "invalid_request_error", + "gen_ai.error.code", + "model_not_found", + "http.response.status_code", + ]); +} + +// assertPrometheusErrorScrape checks bifrost_error_requests_total carries the +// status_code label for both the non-stream and stream error calls. +async function assertPrometheusErrorScrape() { + await poll("Prometheus error scrape", 20000, async () => { + const res = await request("GET", "/metrics"); + if (!res.ok) { + throw new Error(`GET /metrics failed with ${res.status}: ${res.text}`); + } + const failures = []; + for (const method of ["chat_completion", "chat_completion_stream"]) { + const line = findPrometheusSample(res.text, "bifrost_error_requests_total", + { provider: providerName, method, status_code: "404" }); + if (!line || parsePrometheusValue(line) < 1) { + failures.push(method); + } + } + if (failures.length > 0) { + throw new Error(`bifrost_error_requests_total{status_code="404"} missing for: ${failures.join(", ")}`); + } + return true; + }); +} + function assertBufferContainsAll(name, body, values) { for (const value of values) { if (!body.includes(Buffer.from(value))) { @@ -414,9 +491,9 @@ function assertMetricsMatchLogs(metrics, log) { } } -function assertMockProviderRequest() { - if (state.mockRequests.length !== 1) { - throw new Error(`expected exactly one mock provider request, got ${state.mockRequests.length}`); +function assertMockProviderRequest(wantCount = 1) { + if (state.mockRequests.length !== wantCount) { + throw new Error(`expected ${wantCount} mock provider request(s), got ${state.mockRequests.length}`); } let body; try { @@ -482,11 +559,22 @@ async function main() { const log = await assertLoggingTrace(); assertMetricsMatchLogs(metrics, log); + // Error scenarios: non-stream provider 404, then a stream failing before + // its first chunk. Both must export gen_ai.error.* span attributes and a + // status_code-labeled error counter. + await chatError(errorRequestID, false); + await assertOtelErrorTrace(errorRequestID, "error"); + await chatError(streamErrorRequestID, true); + await assertOtelErrorTrace(streamErrorRequestID, "stream-error"); + await assertPrometheusErrorScrape(); + assertMockProviderRequest(3); + console.log(` OTEL trace exports received: ${state.otelTraceRequests.length}`); console.log(` OTEL metric exports received: ${state.otelMetricRequests.length}`); console.log(` Prometheus scrape includes provider="${providerName}" model="${requestedModel}"`); console.log(` Metrics/logs token usage reconciled (scrape == logs)`); console.log(` Logging trace API returned id="${requestID}"`); + console.log(` Error spans carry gen_ai.error.* and error counter has status_code (non-stream and stream).`); console.log("Local observability API check passed."); } finally { try { From 03a0b735d927210cf9ce79d54e00cdfeb6b0d74a Mon Sep 17 00:00:00 2001 From: roroghost17 Date: Thu, 23 Jul 2026 16:31:16 +0530 Subject: [PATCH 4/4] chore: ui descriptions cleanup --- ui/app/pprof/page.tsx | 6 +++--- ui/app/workspace/config/views/loggingView.tsx | 14 +++++++------- ui/app/workspace/config/views/mcpView.tsx | 2 +- ui/app/workspace/governance/views/teamSheet.tsx | 2 +- ui/app/workspace/logs/sheets/logDetailView.tsx | 4 ++-- .../library/views/mcpLibraryAddServerSheet.tsx | 4 ++-- .../mcp-registry/views/oauth2Authorizer.tsx | 2 +- .../workspace/mcp-sessions/views/sessionsTable.tsx | 4 ++-- .../model-catalog/views/attributeSheet.tsx | 2 +- .../observability/fragments/maximFormFragment.tsx | 2 +- .../observability/fragments/otelFormFragment.tsx | 2 +- .../fragments/betaHeadersFormFragment.tsx | 2 +- .../providers/fragments/deploymentsTable.tsx | 2 +- .../providers/views/modelProviderKeysTableView.tsx | 2 +- .../routing-rules/tree/views/node/rfRuleNode.tsx | 2 +- .../routing-rules/tree/views/routingTreeView.tsx | 4 ++-- .../routing-rules/views/routingRuleSheet.tsx | 4 ++-- .../virtual-keys/views/virtualKeySheet.tsx | 8 ++++---- ui/components/prompts/context.tsx | 2 +- ui/components/ui/multibudgets.tsx | 2 +- 20 files changed, 36 insertions(+), 36 deletions(-) diff --git a/ui/app/pprof/page.tsx b/ui/app/pprof/page.tsx index b46519623d1..06b4b5d0d21 100644 --- a/ui/app/pprof/page.tsx +++ b/ui/app/pprof/page.tsx @@ -517,7 +517,7 @@ function LeakTable({ {candidates.length === 0 && ( - No obvious leak signatures — all live allocations have normal retention ratios. + No obvious leak signatures; all live allocations have normal retention ratios. )} @@ -1076,7 +1076,7 @@ export default function PprofPage() { ({sortedInuseAllocations.length} sites)

- Call stacks currently holding memory on the heap right now — expand a row to see the full stack. + Call stacks currently holding memory on the heap right now. Expand a row to see the full stack.

({sortedAllocations.length} sites)

- Total bytes allocated since process start (includes memory already freed) — expand a row to see the full stack. + Total bytes allocated since process start (includes memory already freed). Expand a row to see the full stack.

When enabled, only usage metadata (latency, cost, token count, status, routing IDs, etc.) is logged. Request/response - content — messages, params, tool calls, and any raw provider bytes — is dropped from log records, even when{" "} + content (messages, params, tool calls, and any raw provider bytes) is dropped from log records, even when{" "} store_raw_request_response is on. Raw-byte send-back to callers via{" "} send_back_raw_* is unaffected.

@@ -144,8 +144,8 @@ export default function LoggingView() { Retain Content in Object Storage

- When enabled, requests with content logging disabled — via the global setting above or the{" "} - x-bf-disable-content-logging header — still have their full content offloaded to object + When enabled, requests with content logging disabled (via the global setting above or the{" "} + x-bf-disable-content-logging header) still have their full content offloaded to object storage, but the content is never shown in logs: the database row stays metadata-only and the UI/API never fetch the payload back. Content is then only readable with direct access to the storage bucket. When disabled, content for such requests is dropped entirely (current behavior). @@ -183,10 +183,10 @@ export default function LoggingView() { When enabled, individual requests can override the global content logging setting using the{" "} x-bf-disable-content-logging header or context key, and can opt-in to persisting raw provider bytes in logs using the x-bf-store-raw-request-response header. Raw-byte storage - requires content logging to be on — either globally, or via{" "} + requires content logging to be on, either globally, or via{" "} x-bf-disable-content-logging: false on the same request. If content logging is off, raw bytes are dropped from the log record even when x-bf-store-raw-request-response: true. Does - not control sending raw bytes back to callers — see Allow Per-Request Raw Override. + not control sending raw bytes back to callers; see Allow Per-Request Raw Override.

When enabled, individual requests can send raw provider request/response bytes back to the caller using the{" "} x-bf-send-back-raw-request and x-bf-send-back-raw-response{" "} - headers. Does not affect log storage — raw-byte persistence in logs is controlled by Allow Per-Request Content Storage + headers. Does not affect log storage; raw-byte persistence in logs is controlled by Allow Per-Request Content Storage Override.

@@ -273,7 +273,7 @@ export default function LoggingView() {

Comma-separated list of request headers to capture in log metadata. Supports exact names and wildcard patterns (e.g.{" "} x-custom-* captures all headers with that prefix, * logs all - headers — note that * will capture sensitive headers like Authorization). Values are + headers; note that * will capture sensitive headers like Authorization). Values are extracted from incoming requests and stored in the metadata field of log entries. Headers with the{" "} x-bf-lh- prefix are always captured automatically.

diff --git a/ui/app/workspace/config/views/mcpView.tsx b/ui/app/workspace/config/views/mcpView.tsx index 492d33a8ee8..1281907effc 100644 --- a/ui/app/workspace/config/views/mcpView.tsx +++ b/ui/app/workspace/config/views/mcpView.tsx @@ -531,7 +531,7 @@ export default function MCPView() { OAuth discovery will be disabled All MCP clients that authenticated via the OAuth consent - flow will lose access — their JWTs will be rejected and + flow will lose access; their JWTs will be rejected and their refresh tokens will become unusable. They will need to reconfigure using a virtual key or api-key header. diff --git a/ui/app/workspace/governance/views/teamSheet.tsx b/ui/app/workspace/governance/views/teamSheet.tsx index aa8f97ddebb..e03486f2904 100644 --- a/ui/app/workspace/governance/views/teamSheet.tsx +++ b/ui/app/workspace/governance/views/teamSheet.tsx @@ -393,7 +393,7 @@ export default function TeamSheet({ team, customers, onSave, onCancel }: TeamShe
updateBudgetRow(idx, { maxLimit: value })} diff --git a/ui/app/workspace/logs/sheets/logDetailView.tsx b/ui/app/workspace/logs/sheets/logDetailView.tsx index befe1a850b9..baa5e8682ca 100644 --- a/ui/app/workspace/logs/sheets/logDetailView.tsx +++ b/ui/app/workspace/logs/sheets/logDetailView.tsx @@ -2196,13 +2196,13 @@ export function LogDetailView({ {log.is_large_payload_request && !log.input_history?.length && !log.responses_input_history?.length && (
- Large payload request — input content was streamed directly to the provider and is not available for display. + Large payload request: input content was streamed directly to the provider and is not available for display. {log.raw_request && " A truncated preview is available in the Raw JSON tab."}
)} {log.is_large_payload_response && !log.output_message && !log.responses_output?.length && log.status !== "processing" && (
- Large payload response — response content was streamed directly to the client and is not available for display. + Large payload response: response content was streamed directly to the client and is not available for display. {log.raw_response && " A truncated preview is available in the Raw JSON tab."}
)} diff --git a/ui/app/workspace/mcp-registry/library/views/mcpLibraryAddServerSheet.tsx b/ui/app/workspace/mcp-registry/library/views/mcpLibraryAddServerSheet.tsx index 16fbf219522..9050112bd0b 100644 --- a/ui/app/workspace/mcp-registry/library/views/mcpLibraryAddServerSheet.tsx +++ b/ui/app/workspace/mcp-registry/library/views/mcpLibraryAddServerSheet.tsx @@ -213,7 +213,7 @@ export function MCPLibraryAddServerSheet({ open, onClose }: MCPLibraryAddServerS data-testid="mcp-add-envs-input" {...register("envs")} /> -

Only names — users supply values at install time.

+

Only names; users supply values at install time.

)} @@ -250,7 +250,7 @@ export function MCPLibraryAddServerSheet({ open, onClose }: MCPLibraryAddServerS data-testid="mcp-add-header-keys-input" {...register("required_header_keys")} /> -

Only names — users supply values at install time.

+

Only names; users supply values at install time.

)} diff --git a/ui/app/workspace/mcp-registry/views/oauth2Authorizer.tsx b/ui/app/workspace/mcp-registry/views/oauth2Authorizer.tsx index f64b1363915..13d3510e430 100644 --- a/ui/app/workspace/mcp-registry/views/oauth2Authorizer.tsx +++ b/ui/app/workspace/mcp-registry/views/oauth2Authorizer.tsx @@ -394,7 +394,7 @@ export const OAuth2Authorizer: React.FC = ({ {status === "success" && ( }>

Finishing setup and syncing available tools.

-

You can close this dialog — setup will complete in the background.

+

You can close this dialog; setup will complete in the background.

)} diff --git a/ui/app/workspace/mcp-sessions/views/sessionsTable.tsx b/ui/app/workspace/mcp-sessions/views/sessionsTable.tsx index 0e2b2ee9706..5ad86399193 100644 --- a/ui/app/workspace/mcp-sessions/views/sessionsTable.tsx +++ b/ui/app/workspace/mcp-sessions/views/sessionsTable.tsx @@ -133,7 +133,7 @@ export default function SessionsTable({ <> Revoke this MCP session? - Bifrost will remove the stored credential for this binding. The upstream OAuth token is not revoked at the provider — it + Bifrost will remove the stored credential for this binding. The upstream OAuth token is not revoked at the provider; it stays detached and expires naturally. Anyone using this binding will need to re-authenticate to obtain a fresh token. @@ -181,7 +181,7 @@ export default function SessionsTable({ diff --git a/ui/app/workspace/model-catalog/views/attributeSheet.tsx b/ui/app/workspace/model-catalog/views/attributeSheet.tsx index 8bc259e95fe..0165edf49a1 100644 --- a/ui/app/workspace/model-catalog/views/attributeSheet.tsx +++ b/ui/app/workspace/model-catalog/views/attributeSheet.tsx @@ -232,7 +232,7 @@ export default function AttributeSheet({ model, onClose }: AttributeSheetProps) value={description} onChange={(e) => setDescription(e.target.value)} rows={4} - placeholder="A short description of this model — shown anywhere additional_attributes.description is consumed." + placeholder="A short description of this model, shown anywhere additional_attributes.description is consumed." data-testid="model-catalog-description-textarea" /> diff --git a/ui/app/workspace/observability/fragments/maximFormFragment.tsx b/ui/app/workspace/observability/fragments/maximFormFragment.tsx index bbc4595816b..bbb0526b82a 100644 --- a/ui/app/workspace/observability/fragments/maximFormFragment.tsx +++ b/ui/app/workspace/observability/fragments/maximFormFragment.tsx @@ -122,7 +122,7 @@ export function MaximFormFragment({ initialConfig, onSave, onDelete, isDeleting Comma-separated list of request headers to capture and attach as trace tags. Supports exact names and wildcard patterns (e.g. x-custom-* captures all headers with that prefix,{" "} - * captures all headers — note that * will capture + * captures all headers; note that * will capture sensitive headers like Authorization). diff --git a/ui/app/workspace/observability/fragments/otelFormFragment.tsx b/ui/app/workspace/observability/fragments/otelFormFragment.tsx index ef5bc68051c..ea2c89152e0 100644 --- a/ui/app/workspace/observability/fragments/otelFormFragment.tsx +++ b/ui/app/workspace/observability/fragments/otelFormFragment.tsx @@ -438,7 +438,7 @@ function OtelProfileSection({ form, control, index, hasOtelAccess, canRemove, op Comma-separated list of request headers to capture and emit as span attributes. Supports exact names and wildcard patterns (e.g. x-custom-* captures all headers with that prefix,{" "} - * captures all headers — note that * will capture + * captures all headers; note that * will capture sensitive headers like Authorization). diff --git a/ui/app/workspace/providers/fragments/betaHeadersFormFragment.tsx b/ui/app/workspace/providers/fragments/betaHeadersFormFragment.tsx index 2088f74fddb..9e868f916bc 100644 --- a/ui/app/workspace/providers/fragments/betaHeadersFormFragment.tsx +++ b/ui/app/workspace/providers/fragments/betaHeadersFormFragment.tsx @@ -231,7 +231,7 @@ export function BetaHeadersFormFragment({ provider }: BetaHeadersFormFragmentPro // Validate if (KNOWN_PREFIXES.has(prefix)) { - setNewPrefixError("This is a known header — use the override dropdown above instead"); + setNewPrefixError("This is a known header; use the override dropdown above instead"); return; } if (overrides[prefix] !== undefined) { diff --git a/ui/app/workspace/providers/fragments/deploymentsTable.tsx b/ui/app/workspace/providers/fragments/deploymentsTable.tsx index f1b99dac23d..98b58db1b82 100644 --- a/ui/app/workspace/providers/fragments/deploymentsTable.tsx +++ b/ui/app/workspace/providers/fragments/deploymentsTable.tsx @@ -672,7 +672,7 @@ export function DeploymentsTable({ value, onChange, providerName, disabled = fal {(draftRow.name.trim() !== "" || draftRow.config.model_id.trim() !== "") && !(draftRow.name.trim() && draftRow.config.model_id.trim()) && (

- Both deployment name and model ID are required — this row will not be saved until both are filled. + Both deployment name and model ID are required; this row will not be saved until both are filled.

)} diff --git a/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx b/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx index f0e8ba51934..47088bee0f2 100644 --- a/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx +++ b/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx @@ -255,7 +255,7 @@ export default function ModelProviderKeysTableView({ provider, className, header - {key.description} — verify the secret reference is configured on the server + {key.description}; verify the secret reference is configured on the server ) : ( diff --git a/ui/app/workspace/routing-rules/tree/views/node/rfRuleNode.tsx b/ui/app/workspace/routing-rules/tree/views/node/rfRuleNode.tsx index 063a49e834e..45f0f6c3e91 100644 --- a/ui/app/workspace/routing-rules/tree/views/node/rfRuleNode.tsx +++ b/ui/app/workspace/routing-rules/tree/views/node/rfRuleNode.tsx @@ -105,7 +105,7 @@ export function RFRuleNode({ data }: { data: any }) {

- Chain rule — resolved provider/model feeds back as the new input and the full scope chain re-evaluates. + Chain rule: resolved provider/model feeds back as the new input and the full scope chain re-evaluates.

)} diff --git a/ui/app/workspace/routing-rules/tree/views/routingTreeView.tsx b/ui/app/workspace/routing-rules/tree/views/routingTreeView.tsx index 442cf8dec58..61115651eda 100644 --- a/ui/app/workspace/routing-rules/tree/views/routingTreeView.tsx +++ b/ui/app/workspace/routing-rules/tree/views/routingTreeView.tsx @@ -489,7 +489,7 @@ export function RoutingTreeView() { /> - Re-entry point is fully proven by static analysis — every condition on the path evaluated to a known value. + Re-entry point is fully proven by static analysis; every condition on the path evaluated to a known value. @@ -524,7 +524,7 @@ export function RoutingTreeView() { /> - Re-entry point is a conditional — one or more conditions on the path are not fully evaluated at build time. + Re-entry point is a conditional; one or more conditions on the path are not fully evaluated at build time. diff --git a/ui/app/workspace/routing-rules/views/routingRuleSheet.tsx b/ui/app/workspace/routing-rules/views/routingRuleSheet.tsx index c064ec18331..83cf0f6c1f4 100644 --- a/ui/app/workspace/routing-rules/views/routingRuleSheet.tsx +++ b/ui/app/workspace/routing-rules/views/routingRuleSheet.tsx @@ -363,7 +363,7 @@ export function RoutingRuleSheet({ open, onOpenChange, editingRule, onSuccess }:

After this rule matches, re-evaluate routing rules using the resolved provider/model as the new context. Useful for - composing rules — e.g. normalize a model alias first, then route based on the canonical name. + composing rules, e.g. normalize a model alias first, then route based on the canonical name.

0 || target.key_id) && (