From f5147d00f9b96aec3451b2b8d564a706529f63f8 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:30:56 -0300 Subject: [PATCH] feat(providers): copilot-m365-web enterprise (work) tier support (#6334) (#6392) copilot-m365-web enterprise (work) tier support (#6334) (net +1/-0, tests OK). Integrated into release/v3.8.46. --- CHANGELOG.md | 1 + open-sse/executors/copilot-m365-connection.ts | 37 +++-- .../components/modals/EditConnectionModal.tsx | 28 +++- .../modals/connectionProviderSpecificData.ts | 3 + .../[id]/components/modals/m365Tier.ts | 51 +++++++ src/i18n/messages/en.json | 5 + .../unit/copilot-m365-enterprise-6334.test.ts | 129 ++++++++++++++++++ tests/unit/m365-tier-selector-6334.test.ts | 61 +++++++++ 8 files changed, 306 insertions(+), 9 deletions(-) create mode 100644 src/app/(dashboard)/dashboard/providers/[id]/components/modals/m365Tier.ts create mode 100644 tests/unit/copilot-m365-enterprise-6334.test.ts create mode 100644 tests/unit/m365-tier-selector-6334.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b3bebbf9..2ccab29d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### ✨ New Features +- **feat(providers):** copilot-m365-web **enterprise / work tier** support ([#6334](https://github.com/diegosouzapw/OmniRoute/issues/6334)) — mirrors the EDU-tier pattern (#6210): `M365ConnectionParams` gains an `agent` field, a new opt-in `M365_ENTERPRISE_OVERRIDES` preset (`agent=work`, `scenario=officeweb`, `licenseType=Premium`) applies via `providerSpecificData.tier="enterprise"` (alias `"work"`), and `agent` is also overridable directly via `providerSpecificData.agent`. `buildWsUrl` was hardcoding `agent="web"` (the one enterprise-distinguishing param with no override path), so a Premium work account handshook then returned an empty stream. The individual and EDU paths are untouched. Kilo's dup flag vs #6210 (EDU tier) was a false positive — different tier. Regression guard: `tests/unit/copilot-m365-enterprise-6334.test.ts` (7). End-to-end confirmation on a real Premium work account is a live-VPS validation follow-up (Hard Rule #18). (thanks @Forcerecon) - **feat(api):** standardized, provider-agnostic **`effort` + `thinking` request params** ([#6241](https://github.com/diegosouzapw/OmniRoute/issues/6241)) — a thin standardization layer over the existing mature per-provider reasoning plumbing (no provider mapper touched). `providerChatCompletionSchema` gains a canonical `effort` (reusing the shared `none/low/medium/high/xhigh` vocabulary — the UI tiers `extra`/`max` collapse onto `xhigh`) and a boolean `thinking`. A pure `normalizeReasoningRequest` (wired once in `src/sse/handlers/chat.ts`, before any reasoning field is read) folds them onto the fields the translators already consume (`reasoning_effort` / `reasoning.effort` / `thinking`), so they fan out to Anthropic / Gemini / xAI / Responses — an explicit client `reasoning_effort` / object-shaped `thinking` always wins (backward-compatible). `/models` additively exposes `supportsThinking` + `effort_tiers` so the frontend can render the toggles (UI component is a follow-up). Regression guard: `tests/unit/effort-thinking-standardization-6241.test.ts` (12). (thanks @Iammilansoni, @shabeer) - **feat(combo):** new **`pipeline` (sequential) combo strategy** ([#6297](https://github.com/diegosouzapw/OmniRoute/issues/6297)) — the 18th routing strategy runs targets **in order**, threading each step's output into the next step's input, with an optional per-step `prompt` (system instruction); only the final step's response is returned. Distinct from `fusion` (parallel fan-out + judge). Implemented as a self-contained `open-sse/services/pipeline.ts` (sibling to `fusion.ts`), dispatched from `combo.ts`; the step list reuses `combo.models` order and reads an optional `prompt` off each target (backward-compatible — ignored by every other strategy). Intermediate steps run non-streaming with tools stripped (complete prose to thread forward); the final step keeps the client's `stream` flag + tools. A failing/empty/unparseable intermediate step fails the whole pipeline explicitly via a sanitized error (never silently swallowed). Kilo's dup flag vs #563 was a false positive (that's model→chain selection; this is a sequential chain). Regression guard: `tests/unit/combo-pipeline-strategy.test.ts` (5). (thanks @ofekbetzalel) - **feat(ci):** `check:test-masking` now flags **inline-reimplemented prod conditions** ([#6348](https://github.com/diegosouzapw/OmniRoute/issues/6348)) — a new **report-only** subcheck (v2, 6A.10 family) catches the wrong-shape contract test: a test that recomputes the condition under test inline instead of importing/exercising the real function (the #6216 class, where `=== 500` → `>= 500` stayed green because the test re-implemented the branch). For each added/modified test file it warns when the file textually duplicates a ≥3-token conditional from a production file touched in the same PR **and** does not import the symbol/module owning it, via a pure, fixture-tested `findReimplementedConditions()` with an allowlist mirroring `assertReductionAllowlist`. Report-only for now (does not fail the gate) — to be promoted to blocking after a triage cycle. Regression guard: `tests/unit/check-test-masking.test.ts` (45). diff --git a/open-sse/executors/copilot-m365-connection.ts b/open-sse/executors/copilot-m365-connection.ts index e2abea4010..80a06a75ec 100644 --- a/open-sse/executors/copilot-m365-connection.ts +++ b/open-sse/executors/copilot-m365-connection.ts @@ -35,6 +35,19 @@ export const M365_EDU_OVERRIDES = { licenseType: "Starter", } as const; +/** + * Enterprise / "work" (Microsoft 365 Copilot for work) tier overrides (#6334). Enterprise + * tenants ride the `agent="work"` BizChat surface with the `officeweb` scenario and a + * Premium license. Opt-in via `providerSpecificData.tier="enterprise"` (alias `"work"`) so + * the individual and EDU paths are unchanged. A raw `providerSpecificData.agent` override is + * also honored for tenants that need a different agent value. + */ +export const M365_ENTERPRISE_OVERRIDES = { + agent: "work", + scenario: "officeweb", + licenseType: "Premium", +} as const; + export const M365_DEFAULT_VARIANTS = [ "EnableMcpServerWidgets", "feature.EnableMcpServerWidgets", @@ -94,6 +107,7 @@ export interface M365ConnectionParams { scenario?: string; isEdu?: string; licenseType?: string; + agent?: string; } /** A new 32-hex chat session id (== XRoutingParameterSessionKey == clientrequestid). */ @@ -174,16 +188,18 @@ export function resolveConnectionParams( } /** - * Resolve tier overrides (opt-in). `tier="edu"|"included"` applies the EDU overrides; - * individual fields (`scenario`/`isEdu`/`licenseType`) can also be overridden directly - * via providerSpecificData. Unset fields fall back to the individual defaults in - * buildWsUrl. (#6210) + * Resolve tier overrides (opt-in). `tier="edu"|"included"` applies the EDU overrides and + * `tier="enterprise"|"work"` applies the enterprise/work overrides; individual fields + * (`scenario`/`isEdu`/`licenseType`/`agent`) can also be overridden directly via + * providerSpecificData. Unset fields fall back to the individual defaults in buildWsUrl. + * (#6210, #6334) */ function resolveTierOverrides( psd: JsonRecord -): Pick { +): Pick { const tier = typeof psd.tier === "string" ? psd.tier.toLowerCase() : ""; const isEduTier = tier === "edu" || tier === "included"; + const isEnterpriseTier = tier === "enterprise" || tier === "work"; const psdIsEdu = (typeof psd.isEdu === "string" && psd.isEdu) || (typeof psd.isEdu === "boolean" && String(psd.isEdu)) || @@ -191,11 +207,16 @@ function resolveTierOverrides( return { scenario: (typeof psd.scenario === "string" && psd.scenario) || - (isEduTier ? M365_EDU_OVERRIDES.scenario : undefined), + (isEduTier ? M365_EDU_OVERRIDES.scenario : undefined) || + (isEnterpriseTier ? M365_ENTERPRISE_OVERRIDES.scenario : undefined), isEdu: psdIsEdu || (isEduTier ? M365_EDU_OVERRIDES.isEdu : undefined), licenseType: (typeof psd.licenseType === "string" && psd.licenseType) || - (isEduTier ? M365_EDU_OVERRIDES.licenseType : undefined), + (isEduTier ? M365_EDU_OVERRIDES.licenseType : undefined) || + (isEnterpriseTier ? M365_ENTERPRISE_OVERRIDES.licenseType : undefined), + agent: + (typeof psd.agent === "string" && psd.agent) || + (isEnterpriseTier ? M365_ENTERPRISE_OVERRIDES.agent : undefined), }; } @@ -219,7 +240,7 @@ export function buildWsUrl(params: M365ConnectionParams): string { agentHost: M365_INDIVIDUAL_DEFAULTS.agentHost, licenseType: params.licenseType ?? M365_INDIVIDUAL_DEFAULTS.licenseType, isEdu: params.isEdu ?? "false", - agent: M365_INDIVIDUAL_DEFAULTS.agent, + agent: params.agent ?? M365_INDIVIDUAL_DEFAULTS.agent, scenario: params.scenario ?? M365_INDIVIDUAL_DEFAULTS.scenario, }); return `wss://${params.host}/m365Copilot/Chathub/${params.chathubPath}?${query.toString()}`; diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx index 94bc105b05..0c96f5b4b3 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx @@ -51,6 +51,11 @@ import { useOpenRouterPresetControl } from "../OpenRouterPresetInput"; import WebSessionCredentialGuide from "../WebSessionCredentialGuide"; import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields"; import { assignEditApiKeyProviderSpecificData } from "./connectionProviderSpecificData"; +import { + isM365TierCapableProvider, + normalizeM365TierValue, + type M365TierValue, +} from "./m365Tier"; import QuotaScrapingFields, { EMPTY_QUOTA_SCRAPING_FIELDS } from "./QuotaScrapingFields"; export interface EditConnectionModalConnection { @@ -132,6 +137,7 @@ export default function EditConnectionModal({ passthroughModels: connection?.providerSpecificData?.passthroughModels === true, disableCooling: connection?.providerSpecificData?.disableCooling === true, importFreeModelsOnly: connection?.providerSpecificData?.importFreeModelsOnly === true, + m365Tier: normalizeM365TierValue(connection?.providerSpecificData?.tier) as M365TierValue, }); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState(null); @@ -183,6 +189,7 @@ export default function EditConnectionModal({ const localProviderMetadata = getLocalProviderMetadata(provider); const isLocalSelfHostedProvider = !!localProviderMetadata; const isGooglePse = provider === "google-pse-search"; + const isM365TierCapable = isM365TierCapableProvider(provider); const webSessionCredential = getWebSessionCredentialRequirement(provider); const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none"; const isWebSessionCredential = !!webSessionCredential && webSessionCredential.kind !== "none"; @@ -307,6 +314,7 @@ export default function EditConnectionModal({ passthroughModels: connection?.providerSpecificData?.passthroughModels === true, disableCooling: connection?.providerSpecificData?.disableCooling === true, importFreeModelsOnly: connection?.providerSpecificData?.importFreeModelsOnly === true, + m365Tier: normalizeM365TierValue(connection.providerSpecificData?.tier) as M365TierValue, }); const existing = connection.providerSpecificData?.extraApiKeys; setExtraApiKeys(Array.isArray(existing) ? existing : []); @@ -325,7 +333,10 @@ export default function EditConnectionModal({ setApiKeyHealth(health || {}); setNewExtraKey(""); setOpenRouterPreset(existingOpenRouterPreset); - setShowAdvanced(!!existingCustomUserAgent); + setShowAdvanced( + !!existingCustomUserAgent || + normalizeM365TierValue(connection.providerSpecificData?.tier) !== "" + ); setTestResult(null); setValidationResult(null); setSaveError(null); @@ -876,6 +887,21 @@ export default function EditConnectionModal({ placeholder="my-app/1.0" hint={t("customUserAgentHint")} /> + {isM365TierCapable && ( +