Skip to content

feat(providers): add live-validated Microsoft 365 Copilot individual provider [wating autor] - #5287

Closed
skyzea1 wants to merge 2 commits into
cheaper-inference:release/v3.8.40from
skyzea1:feat/m365-copilot-bizchat-live
Closed

skyzea1 wants to merge 2 commits into
cheaper-inference:release/v3.8.40from
skyzea1:feat/m365-copilot-bizchat-live

Conversation

@skyzea1

@skyzea1 skyzea1 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a copilot-m365-web provider registry entry and specialized executor for Microsoft 365 Copilot BizChat.
  • Reuses the landed pure M365 SignalR framing/connection helpers and wires provider credential UI metadata.
  • Includes current non-secret BizChat variants / optionsSets defaults required for live text responses, while redacting all credentials and account-specific values.
  • Filters EarlyProgress/Progress messages so only assistant answer text is emitted as OpenAI-compatible SSE deltas.

Validation

  • Live browser validation against m365.cloud.microsoft/chat completed on 2026-06-29.
  • Credentials, user/tenant path, account identifiers, and location-specific fields: REDACTED.
  • Observed redacted flow: wss://substrate.office.com/m365Copilot/Chathub/REDACTED?...access_token=REDACTED.
  • SignalR handshake succeeded; type:4 chat invocation produced type:1 updates, type:2 final item, and type:3 completion.
  • Prompt: Reply with exactly one word: pong; response: pong.
  • Fresh non-browser replay with newly generated IDs succeeded only when current non-secret variants and optionsSets were included. Empty option flags completed without assistant text.

Tests

  • npm exec -- tsx --import ./open-sse/utils/setupPolyfill.ts --test tests/unit/m365-connection-4042.test.ts tests/unit/m365-bizchat-frames-4042.test.ts
  • Result: 23/23 passing.

Follow-up to #4400 / #4042.

@skyzea1
skyzea1 requested a review from diegosouzapw as a code owner June 29, 2026 02:58
Copilot AI review requested due to automatic review settings June 29, 2026 02:58
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Copilot AI 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.

Pull request overview

Adds a new Microsoft 365 Copilot (BizChat) “web-session” provider (copilot-m365-web) to OmniRoute’s provider registry and routing stack, including a dedicated WebSocket/SignalR executor plus defaults needed to stream assistant text back as OpenAI-compatible SSE.

Changes:

  • Introduces CopilotM365WebExecutor and registers it in the executor factory + provider registry.
  • Adds M365 BizChat connection/framing defaults (variants + optionSets) and filters Progress/EarlyProgress updates from streamed output.
  • Wires provider UI metadata + web-session credential requirements, and updates unit tests for the new defaults.

Review notes (custom):

  • Commands run: none (not available in this review environment)
  • Changed test files: tests/unit/m365-connection-4042.test.ts, tests/unit/m365-bizchat-frames-4042.test.ts
  • Coverage result (custom): not provided / not verified here (required gate: npm run test:coverage, min 60% for statements/lines/functions/branches)

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/m365-connection-4042.test.ts Updates WS URL expectations to include default variants.
tests/unit/m365-bizchat-frames-4042.test.ts Adds Progress text fixture + asserts default optionsSets include rich_responses.
src/shared/providers/webSessionCredentials.ts Adds copilot-m365-web web-session credential requirement + placeholder.
src/shared/constants/providers/web-cookie.ts Adds provider UI metadata entry for Microsoft 365 Copilot (BizChat).
open-sse/executors/index.ts Registers the new executor in the executor factory.
open-sse/executors/copilot-m365-web.ts New specialized WebSocket/SignalR transport executor emitting OpenAI-style SSE.
open-sse/executors/copilot-m365-frames.ts Adds default optionsSets and filters Progress/EarlyProgress messages.
open-sse/executors/copilot-m365-connection.ts Adds default variants and includes them in the generated WS URL.
open-sse/config/providers/registry/copilot-m365-web/index.ts New registry entry for the provider.
open-sse/config/providers/index.ts Adds the provider registry entry to the global REGISTRY map.

Comment on lines 114 to +118
};
}
const host = (typeof psd.host === "string" && psd.host) || M365_INDIVIDUAL_DEFAULTS.host;
return { host, chathubPath, accessToken };
const variants = typeof psd.variants === "string" && psd.variants ? psd.variants : undefined;
return { host, chathubPath, accessToken, variants };
Comment on lines +84 to +90
const abort = (reason: string) => {
if (settled) return;
settled = true;
cleanup();
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: { message: reason } })}\n\n`));
controller.close();
};
Comment on lines +236 to +254
const reader = wsStream.getReader();
const decoder = new TextDecoder();
let fullText = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (const line of decoder.decode(value, { stream: true }).split("\n")) {
if (!line.startsWith("data: ")) continue;
const data = line.slice(6).trim();
if (!data || data === "[DONE]") continue;
try {
const parsed = JSON.parse(data);
const content = parsed.choices?.[0]?.delta?.content;
if (typeof content === "string") fullText += content;
} catch {
/* skip malformed SSE lines */
}
}
}
Comment on lines +119 to +123
"copilot-m365-web": {
id: "copilot-m365-web",
alias: "m365copilot",
name: "Microsoft 365 Copilot (BizChat)",
icon: "business_center",
@skyzea1 skyzea1 changed the title feat(providers): add live-validated Microsoft 365 Copilot BizChat provider feat(providers): add live-validated Microsoft 365 Copilot individual provider Jun 29, 2026
@skyzea1

skyzea1 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Sanitized validation log for individual M365 Copilot

All credentials, user/tenant identifiers, account identifiers, session identifiers, request identifiers, and location-specific values are marked as redacted.

Browser round-trip

[redacted] account/session: redacted
[redacted] credential/access_token: redacted
[redacted] chathub user/tenant path: redacted
[redacted] location/account fields observed in service payload: redacted

[2026-06-29] Opened signed-in individual M365 Copilot page:
  page: https://m365.cloud.microsoft/chat
  account: redacted

[2026-06-29] WebSocket created:
  url: wss://substrate.office.com/m365Copilot/Chathub/redacted?chatsessionid=redacted&XRoutingParameterSessionKey=redacted&clientrequestid=redacted&X-SessionId=redacted&ConversationId=redacted&access_token=redacted&variants=<non-secret feature flags>&source="officeweb"&product=Office&agentHost=Bizchat.FullScreen&licenseType=Starter&isEdu=false&agent=web&scenario=OfficeWebPaidConsumerCopilot

[2026-06-29] SignalR handshake:
  sent: {"protocol":"json","version":1}\x1e
  received: {}\x1e
  result: ok

[2026-06-29] Keepalive:
  sent: {"type":6}\x1e

[2026-06-29] Chat invocation:
  sent frame type: 4
  target: chat
  invocationId: 0
  source: officeweb
  clientCorrelationId: redacted
  sessionId: redacted
  traceId: redacted
  optionsSets: <non-secret feature flags included>
  sliceIds: []
  message.author: user
  message.inputMethod: Keyboard
  message.messageType: Chat
  prompt: "Reply with exactly one word: pong"

[2026-06-29] Streaming updates:
  received: type=1 target=update messageType=Progress text=<ignored progress text>
  received: type=1 target=update throttling/request metadata with identifiers redacted
  received: type=1 target=update assistant text="pong"
  received: type=1 target=update assistant text="pong" isLastUpdate=true
  received: type=1 target=update messageType=ReferencesListComplete identifiers redacted
  received: type=2 invocationId=0 final item identifiers/account/location fields redacted
  received: type=3 invocationId=0 completion

[2026-06-29] Browser result:
  expected: pong
  actual: pong
  status: pass

Fresh non-browser replay

[redacted] replay credential/access_token: redacted
[redacted] replay chathub user/tenant path: redacted
[redacted] replay request/session identifiers: redacted

Replay attempt A: fresh socket + fresh IDs + empty option flags
  handshake: ok
  completion: type=3
  assistant text: <empty>
  status: completed without assistant text
  conclusion: empty option flags are insufficient for this individual M365 path

Replay attempt B: fresh socket + fresh IDs + current non-secret variants/optionsSets
  handshake: ok
  received: type=1 update assistant text="pong"
  received: type=1 update assistant text="pong" isLastUpdate=true
  received: type=2 final item
  received: type=3 completion
  assistant text: pong
  status: pass
  elapsed: approximately 5.7s

Implementation notes from the sanitized log

- The provider is for the individual M365 Copilot path at m365.cloud.microsoft/chat.
- BizChat/Chathub is the observed underlying SignalR WebSocket protocol.
- The access token remains a credential and is always redacted.
- The <user-oid>@<tenant-id> Chathub path is account-specific and is always redacted.
- EarlyProgress/Progress frames can contain bot-authored status text and should not be emitted as assistant content.
- The final assistant text is accumulated in type=1 update frames; deltas are produced by comparing the previous accumulated assistant text with the next one.
- Current non-secret variants/optionsSets are required for live text responses on the validated individual M365 path.

Focused tests

Command:
  npm exec -- tsx --import ./open-sse/utils/setupPolyfill.ts --test tests/unit/m365-connection-4042.test.ts tests/unit/m365-bizchat-frames-4042.test.ts

Result:
  23/23 passing

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.37 to release/v3.8.40 June 29, 2026 03:11
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for this, @skyzea1 — and welcome! 🎉 The M365 Copilot provider integration is well-scoped and follows the provider-adding pattern (registry + executor map + format:"openai" + UI metadata), with no hardcoded credentials (the access token flows from operator-pasted session creds; the M365_DEFAULT_VARIANTS/OPTION_SETS arrays are Microsoft feature-flag names, not secrets) and no eval/new Function. Three things before it can merge:

  1. Rule fix(ui): fix Select dropdown dark theme inconsistency #12 — sanitize error messages in the SSE abort paths. open-sse/executors/copilot-m365-web.ts feeds raw err.message into the abort/data:{error:...} SSE frame at two sites (the ws.on("error", …) handler and the start-block catch). Wrap both in sanitizeErrorMessage(...) (already imported in the file), the same way the top-level execute() catch does.
  2. Rule fix(ci): add environment for npm token access #18 — unit test for the executor. CopilotM365WebExecutor (~290 lines) has no unit test — the wsChat ReadableStream orchestration, SSE chunking, incremental-delta dedup, and handshake/abort/finish lifecycle are unverified. "Live browser validation" doesn't satisfy Rule fix(ci): add environment for npm token access #18 for reproducible logic. Please add a test that drives the framing/SSE logic with a mock ws (assert the OpenAI-shaped SSE chunks for a scripted server frame sequence, plus the abort path). The frames/connection helpers are already covered — this is just the executor class.
  3. Rebase onto release/v3.8.40. The branch was cut around v3.8.37, so the PR currently shows ~+23k/100 files of already-merged release churn (CHANGELOG + i18n + baselines) on top of your real 10-file change. Please git rebase onto the current release/v3.8.40 so the diff is just your provider — otherwise it triggers the CHANGELOG-eat conflict on merge.

Once those three are in, this is merge-ready — ping me and I'll review again. Happy to help if anything's unclear on the test mock.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for this, @skyzea1 — and welcome! 🎉 A live-validated Microsoft 365 Copilot provider is genuinely wanted, and the M365 connection/frames groundwork you contributed earlier is already in the release, so you're building on solid ground.

The blocker is purely scope, not the idea. As it stands this branch is 80 commits / ~242 files against a base from June 25, and the 3-dot diff (i.e. excluding base drift) still changes ~99 source files (+5,587/-408) — far beyond the new provider. It bundles several unrelated things into one PR, e.g.:

  • the new copilot-m365-web provider (open-sse/executors/copilot-m365-web.ts + registry + wiring + tests) — the part we want;
  • an unrelated X-Stainless header-stripping change to open-sse/executors/base.ts + chatCore.ts (a separate feature touching the hot path);
  • changes across many other core executors/handlers.

I can't merge or cleanly salvage it as one unit — it conflicts with the current release and the hot-path changes each need their own review. Could you open a fresh, focused PR onto release/v3.8.40 with only the M365 Copilot web provider? Concretely:

  1. Branch from the current release/v3.8.40 tip.
  2. Add only: open-sse/executors/copilot-m365-web.ts, open-sse/config/providers/registry/copilot-m365-web/index.ts, the registration lines in open-sse/config/providers/index.ts + open-sse/executors/index.ts + open-sse/config/providerRegistry.ts, the provider tests, and the doc/provider-count updates.
  3. If you'd still like the X-Stainless stripping, that's welcome as its own separate PR so it can be reviewed on its merits.

Keeping this PR open for reference — just link the new scoped one here when it's up and I'll review it promptly. Really appreciate the contribution! 🙏

…seam

- Rule cheaper-inference#12: route the three wsChat abort() sinks (handshake error, ws error,
  connect failure) through sanitizeErrorMessage() so a raw err.message/stack never
  reaches the SSE data:{error:{message}} frame.
- Add an injectable WebSocket test seam (__setWebSocketImplForTests) and a regression
  test that drives the wsChat error path with a fake ws and asserts the emitted SSE
  error frame is sanitized (Rule cheaper-inference#18).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
@diegosouzapw
diegosouzapw force-pushed the feat/m365-copilot-bizchat-live branch from a58d60e to 4607ae0 Compare June 29, 2026 04:51
@diegosouzapw diegosouzapw changed the title feat(providers): add live-validated Microsoft 365 Copilot individual provider feat(providers): add live-validated Microsoft 365 Copilot individual provider [wating autor] Jun 29, 2026
@skyzea1

skyzea1 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Opened the fresh focused PR as requested: #5302

It is based on release/v3.8.40 and keeps the scope to the Microsoft 365 Copilot individual provider, wiring, tests, and doc/count updates. Credentials and account/session/request/location-specific values remain marked as redacted.

This PR can stay as reference or be closed, whichever you prefer.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Superseded by your own #5302, which you opened as a focused replacement for this PR.

Thanks for reworking it — #5302 is the better landing vehicle: it's branched directly on release/v3.8.40 (no cherry-pick), carries the full provider wiring + registry + reference docs, includes the Rule #12 SSE sanitization, and ships 25 passing executor/connection/frames tests (vs. the executor-only scope here). I validated #5302 locally — 25/25 green, check:provider-consistency OK, check:docs-counts OK — and it's queued to merge once CI is green.

Closing this one as superseded so we don't land the M365 Copilot provider twice. Your contribution credit lands on #5302. 🙏

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants