feat: extend gateway session patch

This commit is contained in:
Peter Steinberger
2026-01-03 06:16:49 +01:00
parent b86619bcd0
commit 61b67f6301
5 changed files with 175 additions and 1 deletions

View File

@@ -297,6 +297,7 @@ export const SessionsPatchParamsSchema = Type.Object(
key: NonEmptyString,
thinkingLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
verboseLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
model: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
groupActivation: Type.Optional(
Type.Union([
Type.Literal("mention"),

View File

@@ -4074,6 +4074,16 @@ describe("gateway server", () => {
expect(main2?.thinkingLevel).toBe("medium");
expect(main2?.verboseLevel).toBeUndefined();
piSdkMock.enabled = true;
piSdkMock.models = [{ id: "gpt-test-a", name: "A", provider: "openai" }];
const modelPatched = await rpcReq<{
ok: true;
entry: { modelOverride?: string; providerOverride?: string };
}>(ws, "sessions.patch", { key: "main", model: "openai/gpt-test-a" });
expect(modelPatched.ok).toBe(true);
expect(modelPatched.payload?.entry.modelOverride).toBe("gpt-test-a");
expect(modelPatched.payload?.entry.providerOverride).toBe("openai");
const compacted = await rpcReq<{ ok: true; compacted: boolean }>(
ws,
"sessions.compact",

View File

@@ -20,7 +20,13 @@ import {
type ModelCatalogEntry,
resetModelCatalogCacheForTest,
} from "../agents/model-catalog.js";
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
import {
buildAllowedModelSet,
buildModelAliasIndex,
modelKey,
resolveConfiguredModelRef,
resolveModelRefFromString,
} from "../agents/model-selection.js";
import { installSkill } from "../agents/skills-install.js";
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
import { DEFAULT_AGENT_WORKSPACE_DIR } from "../agents/workspace.js";
@@ -2958,6 +2964,74 @@ export async function startGatewayServer(
}
}
if ("model" in p) {
const raw = p.model;
if (raw === null) {
delete next.providerOverride;
delete next.modelOverride;
} else if (raw !== undefined) {
const trimmed = String(raw).trim();
if (!trimmed) {
return {
ok: false,
error: {
code: ErrorCodes.INVALID_REQUEST,
message: "invalid model: empty",
},
};
}
const resolvedDefault = resolveConfiguredModelRef({
cfg,
defaultProvider: DEFAULT_PROVIDER,
defaultModel: DEFAULT_MODEL,
});
const aliasIndex = buildModelAliasIndex({
cfg,
defaultProvider: resolvedDefault.provider,
});
const resolved = resolveModelRefFromString({
raw: trimmed,
defaultProvider: resolvedDefault.provider,
aliasIndex,
});
if (!resolved) {
return {
ok: false,
error: {
code: ErrorCodes.INVALID_REQUEST,
message: `invalid model: ${trimmed}`,
},
};
}
const catalog = await loadGatewayModelCatalog();
const allowed = buildAllowedModelSet({
cfg,
catalog,
defaultProvider: resolvedDefault.provider,
});
const key = modelKey(resolved.ref.provider, resolved.ref.model);
if (!allowed.allowAny && !allowed.allowedKeys.has(key)) {
return {
ok: false,
error: {
code: ErrorCodes.INVALID_REQUEST,
message: `model not allowed: ${key}`,
},
};
}
if (
resolved.ref.provider === resolvedDefault.provider &&
resolved.ref.model === resolvedDefault.model
) {
delete next.providerOverride;
delete next.modelOverride;
} else {
next.providerOverride = resolved.ref.provider;
next.modelOverride = resolved.ref.model;
}
}
}
if ("groupActivation" in p) {
const raw = p.groupActivation;
if (raw === null) {