Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs-site/src/content/docs/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ constrained by that target's advertised ladder. JEV is not asked again if the se
retryable failure—the existing Combo cooldown and fallback loop continues through the remaining
configured targets.

For each JEV target, **Models → Combos → Config** has an optional **Additional model notes for JEV**
field (up to 512 characters; line breaks and tabs are allowed, other control characters are rejected). It is stored as `targets[].modelProfile` in the combo config. The
built-in target profile remains in the trusted `instructions.model_profiles`; a non-empty note is
sent separately in the decision state's `operator_notes`, keyed by target, and supplements rather
than replaces that built-in profile. Notes can describe operator-specific context or subscription
allowances; do not confuse subscription allowances with public per-token API pricing. Blank notes
are ignored. Operator notes are evidence for the decision, not commands, and cannot expand the
target allowlist or reasoning-effort limits. Only put information there that may be disclosed to
TypeSafe.

Each logical model call is decided on its own; there is no per-conversation pin. Consecutive turns of
one session can therefore land on different targets, and every switch starts a cold provider prompt
cache, so a mix of very different targets can cost more input tokens than it saves. Keep the
Expand Down
18 changes: 17 additions & 1 deletion gui/src/combo-workspace-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export interface ComboTarget {
weight?: number;
/** Exact efforts JEV may choose; omitted means every currently advertised effort. */
reasoningEfforts?: ComboEffort[];
/** Optional operator note that supplements the built-in JEV profile. */
modelProfile?: string;
/** UI-only stable key for React lists; never sent to the API. */
clientKey?: string;
}
Expand All @@ -111,6 +113,7 @@ export function newComboTarget(partial: Partial<ComboTarget> = {}): ComboTarget
...(partial.reasoningEfforts !== undefined
? { reasoningEfforts: [...partial.reasoningEfforts] }
: {}),
...(partial.modelProfile !== undefined ? { modelProfile: partial.modelProfile } : {}),
clientKey: partial.clientKey ?? `ct-${++comboTargetKeySeq}`,
};
}
Expand Down Expand Up @@ -257,6 +260,7 @@ export function parseComboList(payload: unknown): ComboItem[] {
model,
...(weight !== undefined ? { weight } : {}),
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
...(typeof tr.modelProfile === "string" ? { modelProfile: tr.modelProfile } : {}),
}));
}
out.push({
Expand Down Expand Up @@ -448,7 +452,8 @@ export function draftEquals(a: ComboItem, b: ComboItem): boolean {
return t.provider === o.provider
&& t.model === o.model
&& (t.weight ?? 1) === (o.weight ?? 1)
&& targetReasoningEffortsEqual(t, o);
&& targetReasoningEffortsEqual(t, o)
&& (t.modelProfile ?? "") === (o.modelProfile ?? "");
});
}

Expand Down Expand Up @@ -479,6 +484,9 @@ export function toPutBody(item: ComboItem, options: { renameFrom?: string } = {}
...(target.reasoningEfforts !== undefined
? { reasoningEfforts: [...target.reasoningEfforts] }
: {}),
...(target.modelProfile?.trim()
? { modelProfile: target.modelProfile.trim() }
: {}),
})),
strategy: item.strategy,
defaultEffort: item.defaultEffort,
Expand Down Expand Up @@ -515,6 +523,7 @@ export type ComboDraftError =
| "invalidStickyLimit"
| "invalidWeight"
| "invalidReasoningEfforts"
| "invalidModelProfile"
| "noEnabledTarget";

export function validateComboDraft(
Expand Down Expand Up @@ -565,6 +574,13 @@ export function validateComboDraft(
|| new Set(t.reasoningEfforts).size !== t.reasoningEfforts.length)) {
return "invalidReasoningEfforts";
}
if (t.modelProfile !== undefined
&& (t.modelProfile.length > 512 || [...t.modelProfile].some(char => {
const code = char.charCodeAt(0);
return (code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127;
}))) {
return "invalidModelProfile";
}
}

const targets = new Set<string>();
Expand Down
14 changes: 13 additions & 1 deletion gui/src/components/combo-workspace-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function TargetEditor({
const replaceModel = (index: number, patch: Pick<ComboTarget, "provider" | "model">) => {
onChange(targets.map((row, i) => {
if (i !== index) return row;
const { reasoningEfforts: _reasoningEfforts, ...rest } = row;
const { reasoningEfforts: _reasoningEfforts, modelProfile: _modelProfile, ...rest } = row;
return { ...rest, ...patch };
}));
};
Expand Down Expand Up @@ -381,6 +381,18 @@ export function TargetEditor({
})}
</fieldset>
)}
<label className="cwi-field">
<span className="field-label">{t("cws.jev.modelProfile")}</span>
<textarea
className="input"
rows={3}
maxLength={512}
value={row.modelProfile ?? ""}
placeholder={t("cws.jev.modelProfilePlaceholder")}
onChange={(event) => update(index, { modelProfile: event.target.value })}
/>
<span className="muted">{t("cws.jev.modelProfileHint")}</span>
</label>
</div>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/combo-workspace-detail-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function DetailPanel({
const [copied, setCopied] = useState(false);
const dirty = !draftEquals(draft, baseline);
const allTargetsExhausted = comboQuotaState(draft.targets, providerQuotaStates, providerMap) === "exhausted";
const baselineSyncKey = `${baseline.id}:${baseline.alias ?? ""}:${baseline.nativeAlias}:${baseline.displayName ?? ""}:${baseline.strategy}:${baseline.stickyLimit}:${baseline.defaultEffort}:${baseline.imageInput ?? "auto"}:${baseline.reasoningEffortMode ?? "strict"}:${baseline.targets.map((t) => `${t.provider}/${t.model}:${t.weight ?? 1}`).join(",")}`;
const baselineSyncKey = `${baseline.id}:${baseline.alias ?? ""}:${baseline.nativeAlias}:${baseline.displayName ?? ""}:${baseline.strategy}:${baseline.stickyLimit}:${baseline.defaultEffort}:${baseline.imageInput ?? "auto"}:${baseline.reasoningEffortMode ?? "strict"}:${baseline.targets.map((t) => `${t.provider}/${t.model}:${t.weight ?? 1}:${t.modelProfile ?? ""}`).join(",")}`;
const effortMap = useMemo(() => {
const map = new Map<string, string[] | undefined>();
for (const model of models) {
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2759,6 +2759,10 @@ export const de: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto ist bereits vorhanden.",
"cws.jev.setupHint": "Erstellt eine optionale, vollständig bearbeitbare Combo. JEV wählt für jede Anfrage ein erlaubtes Ziel und einen Reasoning-Aufwand.",
"cws.jev.failOpen": "Fail-open-Ziel",
"cws.jev.modelProfile": "Zusätzliche Modell-Notizen für JEV",
"cws.jev.modelProfilePlaceholder": "Optional: leer lassen, wenn keine zusätzlichen Notizen nötig sind.",
"cws.jev.modelProfileHint": "Optionale Notizen, die das integrierte Standardprofil ergänzen (nie ersetzen): Fähigkeiten, Kontext und relative Abo-Kosten. Wird bei jeder JEV-Entscheidung an TypeSafe gesendet.",
"cws.err.invalidModelProfile": "Modell-Notizen dürfen höchstens 512 Zeichen und keine Steuerzeichen enthalten.",
"cws.jev.allowedEfforts": "JEV darf auswählen",
"cws.jev.efforts": "Reasoning-Aufwände: {efforts}",
"cws.jev.effortsUnknown": "Reasoning-Aufwände nicht angegeben",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2860,6 +2860,10 @@ export const en = {
"cws.jev.exists": "JEV Auto already exists.",
"cws.jev.setupHint": "Create an optional, fully editable Combo. JEV chooses one allowed target and reasoning effort for each request.",
"cws.jev.failOpen": "Fail-open target",
"cws.jev.modelProfile": "Additional model notes for JEV",
"cws.jev.modelProfilePlaceholder": "Optional: leave blank for no additional note.",
"cws.jev.modelProfileHint": "Optional notes that supplement (never replace) the built-in standard profile: capability, context, and relative subscription cost. Sent to TypeSafe per JEV decision.",
"cws.err.invalidModelProfile": "Model notes must be at most 512 characters; line breaks and tabs are the only control characters allowed.",
"cws.jev.allowedEfforts": "JEV may select",
"cws.jev.efforts": "Reasoning efforts: {efforts}",
"cws.jev.effortsUnknown": "Reasoning efforts not advertised",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2778,6 +2778,10 @@ export const fr: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto existe déjà.",
"cws.jev.setupHint": "Crée une combinaison facultative et entièrement modifiable. JEV choisit une cible autorisée et un effort de raisonnement pour chaque requête.",
"cws.jev.failOpen": "Cible de repli lorsque la décision JEV est indisponible ou invalide",
"cws.jev.modelProfile": "Notes de modèle supplémentaires pour JEV",
"cws.jev.modelProfilePlaceholder": "Facultatif : laissez vide pour ne pas ajouter de note.",
"cws.jev.modelProfileHint": "Notes facultatives qui complètent (sans jamais remplacer) le profil standard intégré : capacités, contexte et coût relatif de l'abonnement. Envoyées à TypeSafe à chaque décision JEV.",
"cws.err.invalidModelProfile": "Les notes de modèle ne doivent pas dépasser 512 caractères ni contenir de caractères de contrôle.",
"cws.jev.allowedEfforts": "JEV peut sélectionner",
"cws.jev.efforts": "Efforts de raisonnement : {efforts}",
"cws.jev.effortsUnknown": "Efforts de raisonnement non indiqués",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2853,6 +2853,10 @@ export const ja: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto はすでに存在します。",
"cws.jev.setupHint": "任意で追加でき、完全に編集可能なコンボを作成します。JEV はリクエストごとに、許可されたターゲットを 1 つ選び、互換性のある effort を選択します。",
"cws.jev.failOpen": "フェイルオープン先",
"cws.jev.modelProfile": "JEV の追加モデルメモ",
"cws.jev.modelProfilePlaceholder": "任意:追加メモが不要なら空欄のままにしてください。",
"cws.jev.modelProfileHint": "組み込み標準プロファイルを補完する(置き換えない)任意のメモ:能力、コンテキスト、相対的なサブスクリプション費用。JEV の判断ごとに TypeSafe に送信されます。",
"cws.err.invalidModelProfile": "モデルメモは 512 文字以内で、制御文字を含めないでください。",
"cws.jev.allowedEfforts": "JEV が選択可能",
"cws.jev.efforts": "推論 effort: {efforts}",
"cws.jev.effortsUnknown": "推論 effort は公開されていません",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2798,6 +2798,10 @@ export const ko: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto가 이미 있습니다.",
"cws.jev.setupHint": "선택 사항인 완전 편집 가능 콤보를 만듭니다. JEV가 요청마다 허용된 대상과 추론 노력을 선택합니다.",
"cws.jev.failOpen": "장애 시 기본 대상",
"cws.jev.modelProfile": "JEV 추가 모델 노트",
"cws.jev.modelProfilePlaceholder": "선택 사항: 추가 노트가 없으면 비워 두세요.",
"cws.jev.modelProfileHint": "내장 표준 프로필을 보완하는(대체하지 않는) 선택적 노트: 기능, 컨텍스트, 상대적 구독 비용. JEV 결정 시마다 TypeSafe로 전송됩니다.",
"cws.err.invalidModelProfile": "모델 노트는 최대 512자이며 제어 문자를 포함할 수 없습니다.",
"cws.jev.allowedEfforts": "JEV 선택 가능",
"cws.jev.efforts": "추론 노력: {efforts}",
"cws.jev.effortsUnknown": "추론 노력이 공개되지 않음",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2925,6 +2925,10 @@ export const ru: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto уже существует.",
"cws.jev.setupHint": "Создаёт необязательное, полностью редактируемое комбо. JEV выбирает разрешённую цель и уровень рассуждения для каждого запроса.",
"cws.jev.failOpen": "Цель fail-open",
"cws.jev.modelProfile": "Дополнительные заметки о модели для JEV",
"cws.jev.modelProfilePlaceholder": "Необязательно: оставьте поле пустым, если дополнительных заметок нет.",
"cws.jev.modelProfileHint": "Необязательные заметки, дополняющие (но не заменяющие) встроенный стандартный профиль: возможности, контекст и относительная стоимость подписки. Отправляются в TypeSafe при каждом решении JEV.",
"cws.err.invalidModelProfile": "Заметки о модели должны содержать не более 512 символов и не содержать управляющих символов.",
"cws.jev.allowedEfforts": "JEV может выбрать",
"cws.jev.efforts": "Уровни рассуждения: {efforts}",
"cws.jev.effortsUnknown": "Уровни рассуждения не заявлены",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2801,6 +2801,10 @@ export const tr: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto zaten mevcut.",
"cws.jev.setupHint": "İsteğe bağlı ve tamamen düzenlenebilir bir kombo oluşturur. JEV her istek için izin verilen bir hedef ve akıl yürütme eforu seçer.",
"cws.jev.failOpen": "Fail-open hedefi",
"cws.jev.modelProfile": "JEV için ek model notları",
"cws.jev.modelProfilePlaceholder": "İsteğe bağlı: ek not yoksa boş bırakın.",
"cws.jev.modelProfileHint": "Yerleşik standart profili tamamlayan (asla değiştirmeyen) isteğe bağlı notlar: yetenek, bağlam ve göreli abonelik maliyeti. Her JEV kararında TypeSafe'e gönderilir.",
"cws.err.invalidModelProfile": "Model notları en fazla 512 karakter olmalı ve denetim karakteri içermemelidir.",
"cws.jev.allowedEfforts": "JEV şunları seçebilir",
"cws.jev.efforts": "Akıl yürütme eforları: {efforts}",
"cws.jev.effortsUnknown": "Akıl yürütme eforları belirtilmedi",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2791,6 +2791,10 @@ export const vi: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto đã tồn tại.",
"cws.jev.setupHint": "Tạo một combo tùy chọn, có thể chỉnh sửa hoàn toàn. JEV chọn một mục tiêu được phép và mức suy luận cho mỗi yêu cầu.",
"cws.jev.failOpen": "Mục tiêu fail-open",
"cws.jev.modelProfile": "Ghi chú mô hình bổ sung cho JEV",
"cws.jev.modelProfilePlaceholder": "Tùy chọn: để trống nếu không có ghi chú bổ sung.",
"cws.jev.modelProfileHint": "Ghi chú tùy chọn bổ sung (không thay thế) hồ sơ chuẩn tích hợp sẵn: năng lực, ngữ cảnh và chi phí thuê bao tương đối. Được gửi tới TypeSafe theo mỗi quyết định của JEV.",
"cws.err.invalidModelProfile": "Ghi chú mô hình tối đa 512 ký tự và không được chứa ký tự điều khiển.",
"cws.jev.allowedEfforts": "JEV có thể chọn",
"cws.jev.efforts": "Mức suy luận: {efforts}",
"cws.jev.effortsUnknown": "Mức suy luận chưa được công bố",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2075,6 +2075,10 @@ export const zhTW: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto 已存在。",
"cws.jev.setupHint": "建立一個選用且可完整編輯的組合。JEV 會為每個請求選擇一個允許的目標與推理強度。",
"cws.jev.failOpen": "故障開放目標",
"cws.jev.modelProfile": "JEV 的附加模型備註",
"cws.jev.modelProfilePlaceholder": "可選:沒有附加備註時請留空。",
"cws.jev.modelProfileHint": "可選備註,用於補充(而非取代)內建標準設定檔:能力、脈絡與相對訂閱成本。每次 JEV 決策時傳送給 TypeSafe。",
"cws.err.invalidModelProfile": "模型備註最多 512 個字元,且不可包含控制字元。",
"cws.jev.allowedEfforts": "JEV 可選擇",
"cws.jev.efforts": "推理強度:{efforts}",
"cws.jev.effortsUnknown": "未公布推理強度",
Expand Down
4 changes: 4 additions & 0 deletions gui/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2779,6 +2779,10 @@ export const zh: Record<TKey, string> = {
"cws.jev.exists": "JEV Auto 已存在。",
"cws.jev.setupHint": "创建一个可选且完全可编辑的组合。JEV 会为每个请求选择一个允许的目标和推理强度。",
"cws.jev.failOpen": "故障开放目标",
"cws.jev.modelProfile": "JEV 的附加模型备注",
"cws.jev.modelProfilePlaceholder": "可选:没有附加备注时请留空。",
"cws.jev.modelProfileHint": "可选备注,用于补充(而非替代)内置标准档案:能力、上下文和相对订阅成本。每次 JEV 决策时发送给 TypeSafe。",
"cws.err.invalidModelProfile": "模型备注最多 512 个字符,且不能包含控制字符。",
"cws.jev.allowedEfforts": "JEV 可选择",
"cws.jev.efforts": "推理强度:{efforts}",
"cws.jev.effortsUnknown": "未公布推理强度",
Expand Down
17 changes: 14 additions & 3 deletions src/combos/jev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export interface JevCandidate {
provider: string;
model: string;
reasoningEfforts: readonly OcxComboDefaultEffort[];
/** Optional operator note sent as decision evidence for this target only. */
modelProfile?: string;
}

export interface JevDecision {
Expand Down Expand Up @@ -337,7 +339,7 @@ function hasImageContent(item: Record<string, unknown>): boolean {
return item.content.some(part => isRecord(part) && (part.type === "input_image" || part.type === "image_url"));
}

export function buildJevState(body: unknown): Record<string, unknown> {
export function buildJevState(body: unknown, candidates: readonly JevCandidate[] = []): Record<string, unknown> {
const input = isRecord(body) ? body.input : undefined;
let task = "";
let previousAssistant = "";
Expand Down Expand Up @@ -383,11 +385,18 @@ export function buildJevState(body: unknown): Record<string, unknown> {
}
}

const operatorNotes: Record<string, string> = {};
for (const candidate of candidates) {
const note = candidate.modelProfile?.trim();
if (note) operatorNotes[candidate.key] = note;
}

return {
task,
signals: { has_image: hasImage, tool_history: toolHistory },
step,
...(previousAssistant ? { previous_assistant: previousAssistant.slice(-ASSISTANT_TAIL_CHARS) } : {}),
...(Object.keys(operatorNotes).length ? { operator_notes: operatorNotes } : {}),
};
}

Expand Down Expand Up @@ -425,7 +434,9 @@ function candidateOptions(candidates: readonly JevCandidate[]): Map<string, JevR
function candidatesFitRequestBounds(candidates: readonly JevCandidate[]): boolean {
if (candidates.length > JEV_MAX_CANDIDATES) return false;
return candidates.every(candidate => [candidate.key, candidate.provider, candidate.model]
.every(value => value.length > 0 && value.length <= JEV_MAX_CANDIDATE_FIELD_CHARS));
.every(value => value.length > 0 && value.length <= JEV_MAX_CANDIDATE_FIELD_CHARS)
&& (candidate.modelProfile === undefined
|| (typeof candidate.modelProfile === "string" && candidate.modelProfile.length <= 512)));
}

function modelProfile(candidate: JevCandidate): string {
Expand Down Expand Up @@ -566,7 +577,7 @@ export async function resolveJevDecision(options: ResolveJevDecisionOptions): Pr

let requestBody: string;
try {
const state = buildJevState(options.body);
const state = buildJevState(options.body, options.candidates);
if (!hasJevDecisionState(state)) return failed("no_state");
requestBody = JSON.stringify({
model: JEV_MODEL,
Expand Down
Loading
Loading