Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
201 lines
5.5 KiB
TypeScript
201 lines
5.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { composerSettingsSupportFromOptions } from "./composerSettingsSupport";
|
|
import {
|
|
AGENT_CAPABILITY_KEYS,
|
|
type AgentActivityComposerOptions,
|
|
type AgentActivitySessionCapabilities
|
|
} from "@tutti-os/agent-activity-core";
|
|
|
|
function optionsFixture(input: {
|
|
model: boolean;
|
|
reasoning: boolean;
|
|
permission: boolean;
|
|
capabilities?: string[];
|
|
}): AgentActivityComposerOptions {
|
|
return {
|
|
provider: "test",
|
|
capabilities: Object.fromEntries(
|
|
AGENT_CAPABILITY_KEYS.map((key) => [
|
|
key,
|
|
(input.capabilities ?? []).includes(key)
|
|
])
|
|
) as unknown as AgentActivitySessionCapabilities,
|
|
models: [],
|
|
reasoningEfforts: [],
|
|
speeds: [],
|
|
modelConfigurable: input.model,
|
|
reasoningConfigurable: input.reasoning,
|
|
permissionConfig: {
|
|
configurable: input.permission,
|
|
defaultValue: null,
|
|
modes: []
|
|
},
|
|
capabilityCatalog: [],
|
|
skills: [],
|
|
behavior: {
|
|
collapseModelOptionsToLatest: false,
|
|
modelOptionsAuthoritative: false,
|
|
refreshModelOptionsAfterSettings: false,
|
|
prewarmDraftSession: false,
|
|
planModeExclusiveWithPermissionMode: false
|
|
},
|
|
loadedAtUnixMs: 0
|
|
};
|
|
}
|
|
|
|
describe("composerSettingsSupportFromOptions", () => {
|
|
it("keeps reasoning available when another model profile supports it", () => {
|
|
const options = optionsFixture({
|
|
model: true,
|
|
reasoning: false,
|
|
permission: false
|
|
});
|
|
options.reasoningOptionsByModel = {
|
|
"opencode/big-pickle": { defaultValue: null, options: [] },
|
|
"openai/gpt-5": {
|
|
defaultValue: "medium",
|
|
options: [{ value: "medium", label: "Medium" }]
|
|
}
|
|
};
|
|
|
|
expect(composerSettingsSupportFromOptions(options, null).reasoning).toBe(
|
|
true
|
|
);
|
|
});
|
|
|
|
// Equivalence truth table: must match the deleted composerSupportForProvider
|
|
// hardcoded table column by column (except plan, which intentionally moves
|
|
// from a hardcoded false to capability negotiation). The backend flag values
|
|
// per provider are pinned by Go's TestComposerConfigConfigurableTruthTable.
|
|
const providerFlags: Record<
|
|
string,
|
|
{
|
|
model: boolean;
|
|
reasoning: boolean;
|
|
permission: boolean;
|
|
capabilities: string[];
|
|
}
|
|
> = {
|
|
"claude-code": {
|
|
model: true,
|
|
reasoning: true,
|
|
permission: true,
|
|
capabilities: [
|
|
"imageInput",
|
|
"skills",
|
|
"compact",
|
|
"tokenUsage",
|
|
"rateLimits",
|
|
"planMode",
|
|
"interrupt",
|
|
"browserUse"
|
|
]
|
|
},
|
|
codex: {
|
|
model: true,
|
|
reasoning: true,
|
|
permission: true,
|
|
capabilities: [
|
|
"imageInput",
|
|
"skills",
|
|
"compact",
|
|
"tokenUsage",
|
|
"rateLimits",
|
|
"planMode",
|
|
"interrupt",
|
|
"browserUse"
|
|
]
|
|
},
|
|
opencode: {
|
|
model: true,
|
|
reasoning: false,
|
|
permission: false,
|
|
capabilities: ["interrupt", "planMode", "browserUse"]
|
|
},
|
|
cursor: {
|
|
model: true,
|
|
reasoning: false,
|
|
permission: true,
|
|
capabilities: ["interrupt", "planMode", "browserUse"]
|
|
},
|
|
hermes: {
|
|
model: false,
|
|
reasoning: false,
|
|
permission: false,
|
|
capabilities: ["interrupt"]
|
|
},
|
|
nexight: {
|
|
model: false,
|
|
reasoning: false,
|
|
permission: true,
|
|
capabilities: ["interrupt"]
|
|
},
|
|
openclaw: {
|
|
model: false,
|
|
reasoning: false,
|
|
permission: false,
|
|
capabilities: []
|
|
}
|
|
};
|
|
const legacyTable: Record<
|
|
string,
|
|
{ model: boolean; reasoning: boolean; permission: boolean }
|
|
> = {
|
|
"claude-code": { model: true, reasoning: true, permission: true },
|
|
codex: { model: true, reasoning: true, permission: true },
|
|
opencode: { model: true, reasoning: false, permission: false },
|
|
cursor: { model: true, reasoning: false, permission: true },
|
|
hermes: { model: false, reasoning: false, permission: false },
|
|
nexight: { model: false, reasoning: false, permission: true },
|
|
openclaw: { model: false, reasoning: false, permission: false }
|
|
};
|
|
|
|
for (const [provider, flags] of Object.entries(providerFlags)) {
|
|
it(`matches the legacy table for ${provider}`, () => {
|
|
const support = composerSettingsSupportFromOptions(
|
|
optionsFixture(flags),
|
|
null
|
|
);
|
|
expect(support.model).toBe(legacyTable[provider]!.model);
|
|
expect(support.reasoning).toBe(legacyTable[provider]!.reasoning);
|
|
expect(support.permission).toBe(legacyTable[provider]!.permission);
|
|
expect(support.plan).toBe(
|
|
provider === "claude-code" ||
|
|
provider === "codex" ||
|
|
provider === "cursor" ||
|
|
provider === "opencode"
|
|
);
|
|
expect(support.browser).toBe(flags.capabilities.includes("browserUse"));
|
|
});
|
|
}
|
|
|
|
it("returns all-false when composer options are absent", () => {
|
|
expect(composerSettingsSupportFromOptions(null, null)).toEqual({
|
|
model: false,
|
|
reasoning: false,
|
|
speed: false,
|
|
permission: false,
|
|
plan: false,
|
|
browser: false,
|
|
computer: false,
|
|
planImplementation: false,
|
|
permissionModeChangeDuringTurn: false,
|
|
permissionModeChangeDeferred: false,
|
|
modelSwitch: false
|
|
});
|
|
});
|
|
|
|
it("prefers session runtime capabilities for plan", () => {
|
|
const support = composerSettingsSupportFromOptions(
|
|
optionsFixture({
|
|
model: true,
|
|
reasoning: true,
|
|
permission: true,
|
|
capabilities: []
|
|
}),
|
|
{ planMode: true }
|
|
);
|
|
expect(support.plan).toBe(true);
|
|
});
|
|
});
|