mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
committed by
GitHub
parent
effddc6a0e
commit
91f4c35e9d
1
changelog.d/features/7872-m365-tone-model-variants.md
Normal file
1
changelog.d/features/7872-m365-tone-model-variants.md
Normal file
@@ -0,0 +1 @@
|
||||
- feat(providers): copilot-m365-web tone-selected model variants (#7872)
|
||||
@@ -8,5 +8,14 @@ export const copilot_m365_webProvider: RegistryEntry = {
|
||||
baseUrl: "wss://substrate.office.com/m365Copilot/Chathub",
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [{ id: "copilot-m365", name: "Microsoft 365 Copilot (BizChat)", toolCalling: false }],
|
||||
models: [
|
||||
{ id: "copilot-m365", name: "Microsoft 365 Copilot (BizChat)", toolCalling: false },
|
||||
{ id: "copilot-m365-claude-opus", name: "Microsoft 365 Copilot — Claude Opus", toolCalling: false },
|
||||
{
|
||||
id: "copilot-m365-gpt-5-6-reasoning",
|
||||
name: "Microsoft 365 Copilot — GPT 5.6 Reasoning",
|
||||
toolCalling: false,
|
||||
},
|
||||
{ id: "copilot-m365-gpt-5-5-chat", name: "Microsoft 365 Copilot — GPT 5.5 Chat", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -193,6 +193,29 @@ export function resolveChatInvocationOverrides(tier: string | undefined): {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* BizChat exposes several models selected by the `tone` field of the `type:4` chat
|
||||
* invocation (#7872, values confirmed against a real enterprise tenant in #7850). Each
|
||||
* tone-selected variant is registered as its own model id; the bare `copilot-m365` id is
|
||||
* intentionally absent here so it keeps the tier default tone (`Magic` on enterprise, `""`
|
||||
* otherwise) resolved by {@link resolveChatInvocationOverrides}.
|
||||
*/
|
||||
export const M365_MODEL_TONE_MAP: Readonly<Record<string, string>> = {
|
||||
"copilot-m365-claude-opus": "Claude_Opus",
|
||||
"copilot-m365-gpt-5-6-reasoning": "Gpt_5_6_Reasoning",
|
||||
"copilot-m365-gpt-5-5-chat": "Gpt_5_5_Chat",
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the `tone` for a requested model id, or `undefined` when the id is the bare
|
||||
* `copilot-m365` / unknown — callers then fall back to the tier default tone. Model-driven
|
||||
* tone takes precedence over the tier default (see the executor wiring).
|
||||
*/
|
||||
export function resolveToneForModel(model: string | undefined): string | undefined {
|
||||
if (!model) return undefined;
|
||||
return M365_MODEL_TONE_MAP[model];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `type:4` chat invocation frame body (not yet `\x1e`-terminated).
|
||||
* Mirrors the argument shape captured on the individual M365 path in #4042.
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
keepaliveFrame,
|
||||
parseFrame,
|
||||
resolveChatInvocationOverrides,
|
||||
resolveToneForModel,
|
||||
splitFrames,
|
||||
} from "./copilot-m365-frames.ts";
|
||||
|
||||
@@ -167,6 +168,9 @@ export class CopilotM365WebExecutor extends BaseExecutor {
|
||||
const sendChat = () => {
|
||||
ws?.send(keepaliveFrame());
|
||||
const overrides = resolveChatInvocationOverrides(input.tier);
|
||||
// Model-driven tone (#7872) wins over the tier default; a bare/unknown id
|
||||
// keeps the tier tone resolved above.
|
||||
const tone = resolveToneForModel(input.model) ?? overrides.tone;
|
||||
ws?.send(
|
||||
encodeFrame(
|
||||
buildChatInvocation({
|
||||
@@ -175,6 +179,7 @@ export class CopilotM365WebExecutor extends BaseExecutor {
|
||||
sessionId,
|
||||
isStartOfSession: true,
|
||||
...overrides,
|
||||
tone,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
58
tests/unit/m365-tone-model-variants.test.ts
Normal file
58
tests/unit/m365-tone-model-variants.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
M365_MODEL_TONE_MAP,
|
||||
resolveToneForModel,
|
||||
resolveChatInvocationOverrides,
|
||||
} from "../../open-sse/executors/copilot-m365-frames.ts";
|
||||
import { copilot_m365_webProvider } from "../../open-sse/config/providers/registry/copilot-m365-web/index.ts";
|
||||
|
||||
// #7872 — tone-selected model variants for copilot-m365-web.
|
||||
// The wiring (model id → tone → invocation payload) is unit-tested here; whether a tone
|
||||
// actually selects that model upstream is a live enterprise-tenant check (release-drain).
|
||||
|
||||
test("resolveToneForModel maps each variant id to its confirmed tone", () => {
|
||||
assert.equal(resolveToneForModel("copilot-m365-claude-opus"), "Claude_Opus");
|
||||
assert.equal(resolveToneForModel("copilot-m365-gpt-5-6-reasoning"), "Gpt_5_6_Reasoning");
|
||||
assert.equal(resolveToneForModel("copilot-m365-gpt-5-5-chat"), "Gpt_5_5_Chat");
|
||||
});
|
||||
|
||||
test("resolveToneForModel returns undefined for the bare id and unknown ids", () => {
|
||||
// bare id must fall back to the tier default, not a hard-coded tone
|
||||
assert.equal(resolveToneForModel("copilot-m365"), undefined);
|
||||
assert.equal(resolveToneForModel("totally-unknown"), undefined);
|
||||
assert.equal(resolveToneForModel(undefined), undefined);
|
||||
assert.equal(resolveToneForModel(""), undefined);
|
||||
});
|
||||
|
||||
test("model-driven tone overrides the tier default; bare id keeps the tier tone", () => {
|
||||
const enterprise = resolveChatInvocationOverrides("enterprise");
|
||||
const individual = resolveChatInvocationOverrides(undefined);
|
||||
|
||||
// enterprise tier default tone is Magic
|
||||
assert.equal(enterprise.tone, "Magic");
|
||||
assert.equal(individual.tone, "");
|
||||
|
||||
// precedence: resolveToneForModel(model) ?? overrides.tone (mirrors the executor wiring)
|
||||
const toneFor = (model: string | undefined, tierTone: string) =>
|
||||
resolveToneForModel(model) ?? tierTone;
|
||||
|
||||
// a variant id wins over BOTH tier defaults
|
||||
assert.equal(toneFor("copilot-m365-claude-opus", enterprise.tone), "Claude_Opus");
|
||||
assert.equal(toneFor("copilot-m365-claude-opus", individual.tone), "Claude_Opus");
|
||||
|
||||
// the bare id keeps whatever the tier resolved
|
||||
assert.equal(toneFor("copilot-m365", enterprise.tone), "Magic");
|
||||
assert.equal(toneFor("copilot-m365", individual.tone), "");
|
||||
});
|
||||
|
||||
test("registry exposes the bare id (first) plus every tone variant", () => {
|
||||
const ids = copilot_m365_webProvider.models.map((m) => m.id);
|
||||
assert.equal(ids[0], "copilot-m365", "bare Auto/default id must be first");
|
||||
for (const variantId of Object.keys(M365_MODEL_TONE_MAP)) {
|
||||
assert.ok(ids.includes(variantId), `registry missing variant ${variantId}`);
|
||||
}
|
||||
// no duplicate ids
|
||||
assert.equal(ids.length, new Set(ids).size);
|
||||
});
|
||||
Reference in New Issue
Block a user