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.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-06 19:30:56 -03:00
committed by GitHub
parent 62b1bc9d05
commit f5147d00f9
8 changed files with 306 additions and 9 deletions

View File

@@ -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).

View File

@@ -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<M365ConnectionParams, "scenario" | "isEdu" | "licenseType"> {
): Pick<M365ConnectionParams, "scenario" | "isEdu" | "licenseType" | "agent"> {
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()}`;

View File

@@ -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 && (
<Select
label={t("m365TierLabel")}
value={formData.m365Tier ?? ""}
options={[
{ value: "", label: t("m365TierIndividualOption") },
{ value: "edu", label: t("m365TierEduOption") },
{ value: "enterprise", label: t("m365TierEnterpriseOption") },
]}
onChange={(e) =>
setFormData({ ...formData, m365Tier: e.target.value as M365TierValue })
}
hint={t("m365TierHint")}
/>
)}
<Toggle
size="sm"
checked={formData.passthroughModels}

View File

@@ -3,6 +3,7 @@ import {
assignCcCompatibleRequestDefaults,
mergeCcCompatibleRequestDefaults,
} from "./ccCompatibleRequestDefaults";
import { applyM365Tier, isM365TierCapableProvider, type M365TierValue } from "./m365Tier";
import {
assignQuotaScrapingProviderData,
type QuotaScrapingFieldValues,
@@ -19,6 +20,7 @@ type FormData = QuotaScrapingFieldValues & {
cx: string;
excludedModels: string;
importFreeModelsOnly: boolean;
m365Tier?: M365TierValue;
passthroughModels: boolean;
region: string;
routingTags: string;
@@ -117,6 +119,7 @@ export function assignEditApiKeyProviderSpecificData(options: {
o.target.accountId = o.formData.accountId.trim();
}
if (o.isAntigravityFamily) o.target.projectId = o.trimmedCloudCodeProjectId || null;
if (isM365TierCapableProvider(o.provider)) applyM365Tier(o.target, o.formData.m365Tier ?? "");
if (o.isCcCompatible) {
o.target.requestDefaults = mergeCcCompatibleRequestDefaults(
o.target.requestDefaults,

View File

@@ -0,0 +1,51 @@
/**
* M365 Copilot (BizChat) tier selector helpers (#6334).
*
* The `copilot-m365-web` executor (`open-sse/executors/copilot-m365-connection.ts`)
* reads `providerSpecificData.tier` to pick the BizChat surface: unset/`individual`
* uses the consumer defaults, `edu` (alias `included`) applies the education overrides,
* and `enterprise` (alias `work`) applies the Microsoft 365 Copilot for-work overrides.
*
* These pure helpers gate the Advanced-Settings tier dropdown to tier-capable
* providers and normalize the stored value <-> the dropdown value so the UI can be
* unit-tested without a DOM.
*/
/** Providers that expose the tier selector in connection Advanced Settings. */
export const M365_TIER_CAPABLE_PROVIDERS = new Set<string>(["copilot-m365-web"]);
/** Dropdown value: "" is Individual (default / unset tier). */
export type M365TierValue = "" | "edu" | "enterprise";
export function isM365TierCapableProvider(provider?: string | null): boolean {
return !!provider && M365_TIER_CAPABLE_PROVIDERS.has(provider);
}
/**
* Normalize a stored `providerSpecificData.tier` into the dropdown value.
* Mirrors the executor's alias handling (`work`->enterprise, `included`->edu) so a
* connection saved with an alias round-trips to the right option. Anything else
* (unset, null, "individual", unknown) maps to "" (Individual).
*/
export function normalizeM365TierValue(raw: unknown): M365TierValue {
const tier = typeof raw === "string" ? raw.trim().toLowerCase() : "";
if (tier === "edu" || tier === "included") return "edu";
if (tier === "enterprise" || tier === "work") return "enterprise";
return "";
}
/**
* Apply the selected tier onto a `providerSpecificData` target.
*
* `edu`/`enterprise` write the canonical tier string. Individual ("") writes
* `null` — the PUT route merges `{ ...existing, ...incoming }`, so an omitted /
* `undefined` key would keep a previously-saved tier; an explicit `null` overrides
* it and the executor treats a non-string tier as Individual.
*/
export function applyM365Tier(target: Record<string, unknown>, tier: M365TierValue): void {
if (tier === "edu" || tier === "enterprise") {
target.tier = tier;
} else {
target.tier = null;
}
}

View File

@@ -4638,6 +4638,11 @@
"localProviderBaseUrlHint": "Local Provider Base Url Hint",
"localProviders": "Local Providers",
"maxConcurrentWholeNumberError": "Max Concurrent Whole Number Error",
"m365TierLabel": "Copilot tier",
"m365TierHint": "Choose which Microsoft 365 Copilot surface this connection uses. Individual is the default consumer BizChat; Education and Enterprise (work) opt into their tenant surfaces.",
"m365TierIndividualOption": "Individual (default)",
"m365TierEduOption": "Education",
"m365TierEnterpriseOption": "Enterprise / Work",
"museSparkWebCookieHint": "Muse Spark Web Cookie Hint",
"museSparkWebCookiePlaceholder": "Muse Spark Web Cookie Placeholder",
"oauth": "Oauth",

View File

@@ -0,0 +1,129 @@
/**
* Feature test for #6334 — copilot-m365-web enterprise ("work") tier support.
*
* The individual path hardcoded `agent="web"`. Microsoft 365 Copilot for work rides the
* `agent="work"` BizChat surface with `scenario="officeweb"` + a `Premium` license. This is
* opt-in via `providerSpecificData.tier="enterprise"` (alias `"work"`), mirroring the EDU
* tier (#6210). A raw `providerSpecificData.agent` override is also honored. The individual
* and EDU tuples must remain unchanged.
*
* The live round-trip against a real M365 enterprise tenant is the separate Rule #18
* validation and is NOT possible in this environment — documented as a live-validation
* follow-up.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
buildWsUrl,
resolveConnectionParams,
M365_INDIVIDUAL_DEFAULTS,
M365_ENTERPRISE_OVERRIDES,
} from "../../open-sse/executors/copilot-m365-connection.ts";
const BASE_PARAMS = {
host: "substrate.office.com",
chathubPath: "user-oid@tenant-id",
accessToken: "tok",
};
// ── buildWsUrl: enterprise tuple ────────────────────────────────────────────
test("buildWsUrl: enterprise params emit agent=work + scenario=officeweb + Premium license [#6334]", () => {
const url = new URL(
buildWsUrl({
...BASE_PARAMS,
agent: M365_ENTERPRISE_OVERRIDES.agent,
scenario: M365_ENTERPRISE_OVERRIDES.scenario,
licenseType: M365_ENTERPRISE_OVERRIDES.licenseType,
})
);
assert.equal(url.searchParams.get("agent"), "work");
assert.equal(url.searchParams.get("scenario"), "officeweb");
assert.equal(url.searchParams.get("licenseType"), "Premium");
});
test("buildWsUrl: individual (default) tier is unchanged — agent=web [#6334]", () => {
const url = new URL(buildWsUrl(BASE_PARAMS));
assert.equal(url.searchParams.get("agent"), M365_INDIVIDUAL_DEFAULTS.agent);
assert.equal(url.searchParams.get("agent"), "web");
assert.equal(url.searchParams.get("scenario"), M365_INDIVIDUAL_DEFAULTS.scenario);
assert.equal(url.searchParams.get("isEdu"), "false");
});
// ── resolveConnectionParams: opt-in tier resolution ─────────────────────────
test("resolveConnectionParams: tier='enterprise' selects the enterprise/work tuple [#6334]", () => {
const params = resolveConnectionParams({
apiKey: "access_token=tok",
providerSpecificData: { chathubPath: "user@tenant", tier: "enterprise" },
} as never);
assert.ok(!("error" in params), "should resolve without error");
if (!("error" in params)) {
assert.equal(params.agent, "work");
assert.equal(params.scenario, "officeweb");
assert.equal(params.licenseType, "Premium");
const url = new URL(buildWsUrl(params));
assert.equal(url.searchParams.get("agent"), "work");
assert.equal(url.searchParams.get("scenario"), "officeweb");
assert.equal(url.searchParams.get("licenseType"), "Premium");
}
});
test("resolveConnectionParams: tier='work' is an alias for the enterprise tuple [#6334]", () => {
const params = resolveConnectionParams({
apiKey: "access_token=tok",
providerSpecificData: { chathubPath: "user@tenant", tier: "work" },
} as never);
assert.ok(!("error" in params));
if (!("error" in params)) {
assert.equal(params.agent, "work");
assert.equal(params.scenario, "officeweb");
}
});
test("resolveConnectionParams: raw providerSpecificData.agent override flows into the URL [#6334]", () => {
const params = resolveConnectionParams({
apiKey: "access_token=tok",
providerSpecificData: { chathubPath: "user@tenant", agent: "work" },
} as never);
assert.ok(!("error" in params));
if (!("error" in params)) {
assert.equal(params.agent, "work");
const url = new URL(buildWsUrl(params));
assert.equal(url.searchParams.get("agent"), "work");
// No tier → scenario/licenseType fall back to individual defaults.
assert.equal(url.searchParams.get("scenario"), M365_INDIVIDUAL_DEFAULTS.scenario);
}
});
// ── Regressions: individual + EDU paths untouched ───────────────────────────
test("resolveConnectionParams: no tier keeps the individual default agent (web) [#6334]", () => {
const params = resolveConnectionParams({
apiKey: "access_token=tok",
providerSpecificData: { chathubPath: "user@tenant" },
} as never);
assert.ok(!("error" in params));
if (!("error" in params)) {
// agent unset → buildWsUrl falls back to the individual default.
assert.equal(params.agent, undefined);
const url = new URL(buildWsUrl(params));
assert.equal(url.searchParams.get("agent"), "web");
}
});
test("resolveConnectionParams: tier='edu' tuple is unchanged and stays on agent=web [#6334]", () => {
const params = resolveConnectionParams({
apiKey: "access_token=tok",
providerSpecificData: { chathubPath: "user@tenant", tier: "edu" },
} as never);
assert.ok(!("error" in params));
if (!("error" in params)) {
assert.equal(params.scenario, "OfficeWebIncludedCopilot");
assert.equal(params.isEdu, "true");
// EDU tier does not touch agent → individual default in the URL.
assert.equal(params.agent, undefined);
const url = new URL(buildWsUrl(params));
assert.equal(url.searchParams.get("agent"), "web");
}
});

View File

@@ -0,0 +1,61 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
applyM365Tier,
isM365TierCapableProvider,
normalizeM365TierValue,
} from "../../src/app/(dashboard)/dashboard/providers/[id]/components/modals/m365Tier.ts";
// #6334 — the connection Advanced-Settings tier dropdown must map its value to
// providerSpecificData.tier the same way the copilot-m365-web executor reads it,
// and selecting Individual must clear a previously-saved tier.
test("isM365TierCapableProvider only matches copilot-m365-web", () => {
assert.equal(isM365TierCapableProvider("copilot-m365-web"), true);
assert.equal(isM365TierCapableProvider("copilot-web"), false);
assert.equal(isM365TierCapableProvider("openai"), false);
assert.equal(isM365TierCapableProvider(""), false);
assert.equal(isM365TierCapableProvider(undefined), false);
assert.equal(isM365TierCapableProvider(null), false);
});
test("normalizeM365TierValue maps stored tier (and aliases) to the dropdown value", () => {
// Individual / unset
assert.equal(normalizeM365TierValue(undefined), "");
assert.equal(normalizeM365TierValue(null), "");
assert.equal(normalizeM365TierValue(""), "");
assert.equal(normalizeM365TierValue("individual"), "");
assert.equal(normalizeM365TierValue("unknown-tier"), "");
// Education (+ "included" alias, case-insensitive)
assert.equal(normalizeM365TierValue("edu"), "edu");
assert.equal(normalizeM365TierValue("EDU"), "edu");
assert.equal(normalizeM365TierValue("included"), "edu");
// Enterprise (+ "work" alias)
assert.equal(normalizeM365TierValue("enterprise"), "enterprise");
assert.equal(normalizeM365TierValue("work"), "enterprise");
assert.equal(normalizeM365TierValue(" Work "), "enterprise");
});
test("applyM365Tier writes the canonical tier for edu/enterprise", () => {
const eduTarget: Record<string, unknown> = {};
applyM365Tier(eduTarget, "edu");
assert.equal(eduTarget.tier, "edu");
const entTarget: Record<string, unknown> = {};
applyM365Tier(entTarget, "enterprise");
assert.equal(entTarget.tier, "enterprise");
});
test("applyM365Tier clears a previously-saved tier when Individual is selected", () => {
// Simulates the edit flow: target starts as a copy of existing providerSpecificData
// that already carries tier="enterprise". Individual must OVERRIDE it (null), not
// merely omit the key — the PUT route merges { ...existing, ...incoming }, so an
// omitted/undefined key would keep the stale value.
const target: Record<string, unknown> = { tier: "enterprise", customUserAgent: "x" };
applyM365Tier(target, "");
assert.equal(target.tier, null);
assert.equal(target.customUserAgent, "x");
// And it round-trips back to Individual on reload.
assert.equal(normalizeM365TierValue(target.tier), "");
});