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.
- 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 && (
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) && (
{isDuplicate && (
-
Duplicate reset period — each budget line must use a different interval.
+
Duplicate reset period; each budget line must use a different interval.