@vellumai/assistant 0.12.4-staging.1 → 0.12.4-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openapi.yaml +3 -1
- package/package.json +1 -1
- package/src/__tests__/config-loader-backfill.test.ts +1 -0
- package/src/__tests__/default-profile-catalog-fallback.test.ts +1 -0
- package/src/__tests__/inference-profile-validation-jev.test.ts +17 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +43 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
- package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +197 -0
- package/src/api/requests/dictation.ts +2 -2
- package/src/calls/__tests__/typesafe-noul.test.ts +66 -0
- package/src/calls/typesafe-noul.ts +8 -2
- package/src/calls/voice-escalation-judge.ts +5 -4
- package/src/config/__tests__/default-profile-catalog.test.ts +33 -1
- package/src/config/__tests__/jev-managed-profile.test.ts +82 -0
- package/src/config/call-site-defaults.ts +11 -11
- package/src/config/default-profile-catalog.ts +88 -18
- package/src/config/default-profile-names.ts +35 -0
- package/src/config/inference-profile-validation.ts +7 -6
- package/src/config/llm-resolver.ts +16 -0
- package/src/config/profile-text-generation.test.ts +9 -0
- package/src/config/schemas/call-site-catalog.ts +1 -1
- package/src/config/schemas/llm.ts +54 -9
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/dictation-text-processing.ts +4 -4
- package/src/plugin-api/model-profiles.ts +5 -3
- package/src/providers/model-catalog.ts +23 -4
- package/src/runtime/routes/__tests__/dictation-processing.test.ts +174 -0
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +16 -1
- package/src/runtime/routes/conversation-routes.ts +4 -2
- package/src/runtime/routes/diagnostics-routes.ts +66 -229
- package/src/runtime/routes/llm-call-sites-routes.ts +19 -10
- package/src/tools/workflows/manage-workflows.ts +4 -2
- package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +344 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
package/openapi.yaml
CHANGED
|
@@ -12060,7 +12060,9 @@ paths:
|
|
|
12060
12060
|
post:
|
|
12061
12061
|
operationId: dictation_post
|
|
12062
12062
|
summary: Process dictation
|
|
12063
|
-
description:
|
|
12063
|
+
description:
|
|
12064
|
+
Preserve dictated words with explicit dictionary and snippet replacements, classify actions, or edit
|
|
12065
|
+
selected text when instructed.
|
|
12064
12066
|
tags:
|
|
12065
12067
|
- diagnostics
|
|
12066
12068
|
requestBody:
|
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { validateInferenceProfileKey } from "../config/inference-profile-validation.js";
|
|
4
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
5
|
+
|
|
6
|
+
describe("validateInferenceProfileKey and the managed Jev profile", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
setConfig("llm", { profiles: {} });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("a schedule or subagent turn cannot run on the verdict model", () => {
|
|
12
|
+
expect(validateInferenceProfileKey("balanced")).toBeNull();
|
|
13
|
+
expect(validateInferenceProfileKey("jev-managed")).toContain(
|
|
14
|
+
"is not defined in llm.profiles",
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -235,6 +235,10 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
235
235
|
|
|
236
236
|
test("jev-latest opts out of chat text generation", () => {
|
|
237
237
|
expect(catalogModelSupportsText("typesafe", "jev-latest")).toBe(false);
|
|
238
|
+
// A managed Jev profile is configured as `vellum`; the routing identity
|
|
239
|
+
// owns no catalog models, so the lookup must judge by the catalog owner.
|
|
240
|
+
expect(catalogModelSupportsText("vellum", "jev-latest")).toBe(false);
|
|
241
|
+
expect(catalogModelSupportsText("vellum", "claude-opus-5")).toBe(true);
|
|
238
242
|
expect(catalogModelSupportsText("anthropic", "claude-opus-4-8")).toBe(true);
|
|
239
243
|
expect(catalogModelSupportsText("openai-compatible", "local-model")).toBe(
|
|
240
244
|
true,
|
|
@@ -225,6 +225,49 @@ describe("composeCallSiteTweak", () => {
|
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
describe("shipped call-site tuning", () => {
|
|
228
|
+
test("the escalation judge uses managed Jev by default", () => {
|
|
229
|
+
const resolved = resolveCallSiteConfig(
|
|
230
|
+
"voiceEscalationJudge",
|
|
231
|
+
LLMSchema.parse({}),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(resolved.provider).toBe("vellum");
|
|
235
|
+
expect(resolved.provider_connection).toBeUndefined();
|
|
236
|
+
expect(resolved.model).toBe("jev-latest");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("the escalation judge falls through on a BYOK default provider", () => {
|
|
240
|
+
const resolved = resolveCallSiteConfig(
|
|
241
|
+
"voiceEscalationJudge",
|
|
242
|
+
LLMSchema.parse(anthropicDp),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
expect(resolved.provider).toBe("anthropic");
|
|
246
|
+
expect(resolved.model).not.toBe("jev-latest");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("a BYOK Jev call-site override replaces the managed default", () => {
|
|
250
|
+
const llm = LLMSchema.parse({
|
|
251
|
+
...anthropicDp,
|
|
252
|
+
profiles: {
|
|
253
|
+
jev: {
|
|
254
|
+
...completeCustom,
|
|
255
|
+
provider: "typesafe",
|
|
256
|
+
provider_connection: "typesafe-personal",
|
|
257
|
+
model: "jev-latest",
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
callSites: {
|
|
261
|
+
voiceEscalationJudge: { profile: "jev" },
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
const resolved = resolveCallSiteConfig("voiceEscalationJudge", llm);
|
|
265
|
+
|
|
266
|
+
expect(resolved.provider).toBe("typesafe");
|
|
267
|
+
expect(resolved.provider_connection).toBe("typesafe-personal");
|
|
268
|
+
expect(resolved.model).toBe("jev-latest");
|
|
269
|
+
});
|
|
270
|
+
|
|
228
271
|
// A workspace entry that only repoints the profile must not cost the call
|
|
229
272
|
// site the tuning it ships with. `recall` carries a full set: a token
|
|
230
273
|
// budget, effort, sampling, thinking, and cache posture.
|
|
@@ -40,6 +40,13 @@ describe("getModelProfiles", () => {
|
|
|
40
40
|
writeFixtureConfig({});
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
test("omits the managed Jev profile, whose model returns verdicts rather than text", () => {
|
|
44
|
+
writeFixtureConfig({});
|
|
45
|
+
const keys = getModelProfiles().map((p) => p.key);
|
|
46
|
+
expect(keys).toContain("balanced");
|
|
47
|
+
expect(keys).not.toContain("jev-managed");
|
|
48
|
+
});
|
|
49
|
+
|
|
43
50
|
test("orders profiles by profileOrder then the remaining keys alphabetically", () => {
|
|
44
51
|
// GIVEN a workspace whose profileOrder names some keys, with a duplicate
|
|
45
52
|
// ("balanced") and a key that resolves to no profile ("ghost").
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { Database } from "bun:sqlite";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
|
|
14
|
+
import { assertNotLiveDb } from "./assert-not-live-db.js";
|
|
15
|
+
|
|
16
|
+
const migration = WORKSPACE_MIGRATIONS.find(
|
|
17
|
+
(entry) => entry.id === "159-rename-colliding-jev-profile-name",
|
|
18
|
+
)!;
|
|
19
|
+
|
|
20
|
+
let workspaceDir: string;
|
|
21
|
+
|
|
22
|
+
function writeConfig(data: Record<string, unknown>): void {
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(workspaceDir, "config.json"),
|
|
25
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readConfig(): Record<string, any> {
|
|
30
|
+
return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function seedProfilePinTables(rows: {
|
|
34
|
+
conversations: string[];
|
|
35
|
+
cronJobs: string[];
|
|
36
|
+
}): void {
|
|
37
|
+
mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
|
|
38
|
+
const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
|
|
39
|
+
db.run(
|
|
40
|
+
`CREATE TABLE conversations (id TEXT PRIMARY KEY, inference_profile TEXT)`,
|
|
41
|
+
);
|
|
42
|
+
db.run(
|
|
43
|
+
`CREATE TABLE cron_jobs (id TEXT PRIMARY KEY, inference_profile TEXT)`,
|
|
44
|
+
);
|
|
45
|
+
rows.conversations.forEach((profile, index) => {
|
|
46
|
+
db.query(
|
|
47
|
+
`INSERT INTO conversations (id, inference_profile) VALUES (?, ?)`,
|
|
48
|
+
).run(`conv-${index}`, profile);
|
|
49
|
+
});
|
|
50
|
+
rows.cronJobs.forEach((profile, index) => {
|
|
51
|
+
db.query(`INSERT INTO cron_jobs (id, inference_profile) VALUES (?, ?)`).run(
|
|
52
|
+
`job-${index}`,
|
|
53
|
+
profile,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
db.close();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readPins(table: string): string[] {
|
|
60
|
+
const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
|
|
61
|
+
try {
|
|
62
|
+
return (
|
|
63
|
+
db
|
|
64
|
+
.query(`SELECT inference_profile AS p FROM ${table} ORDER BY id`)
|
|
65
|
+
.all() as Array<{ p: string }>
|
|
66
|
+
).map((row) => row.p);
|
|
67
|
+
} finally {
|
|
68
|
+
db.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
workspaceDir = join(
|
|
74
|
+
tmpdir(),
|
|
75
|
+
`vellum-migration-159-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
76
|
+
);
|
|
77
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
if (existsSync(workspaceDir)) {
|
|
82
|
+
assertNotLiveDb(workspaceDir);
|
|
83
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("159-rename-colliding-jev-profile-name migration", () => {
|
|
88
|
+
test("is registered under its id with checkpoint retry", () => {
|
|
89
|
+
expect(migration).toBeDefined();
|
|
90
|
+
expect(migration.retryFailedCheckpoint).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("renames a colliding user profile and rewrites every reference", async () => {
|
|
94
|
+
writeConfig({
|
|
95
|
+
llm: {
|
|
96
|
+
defaultProvider: { provider: "vellum" },
|
|
97
|
+
profiles: {
|
|
98
|
+
"jev-managed": {
|
|
99
|
+
source: "user",
|
|
100
|
+
provider: "anthropic",
|
|
101
|
+
model: "claude-opus-4-7",
|
|
102
|
+
label: "Mine",
|
|
103
|
+
},
|
|
104
|
+
scratch: { source: "user", fallbackProfile: "jev-managed" },
|
|
105
|
+
armA: { source: "user", speed: "fast" },
|
|
106
|
+
blend: {
|
|
107
|
+
source: "user",
|
|
108
|
+
mix: [
|
|
109
|
+
{ profile: "jev-managed", weight: 1 },
|
|
110
|
+
{ profile: "armA", weight: 1 },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
profileOrder: ["balanced", "jev-managed", "blend"],
|
|
115
|
+
activeProfile: "jev-managed",
|
|
116
|
+
advisorProfile: "jev-managed",
|
|
117
|
+
callSites: { recall: { profile: "jev-managed", maxTokens: 4096 } },
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
seedProfilePinTables({
|
|
121
|
+
conversations: ["jev-managed", "balanced"],
|
|
122
|
+
cronJobs: ["jev-managed"],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await migration.run(workspaceDir);
|
|
126
|
+
|
|
127
|
+
const llm = readConfig().llm;
|
|
128
|
+
expect(llm.profiles["jev-managed"]).toBeUndefined();
|
|
129
|
+
expect(llm.profiles["jev-managed-custom"]).toEqual({
|
|
130
|
+
source: "user",
|
|
131
|
+
provider: "anthropic",
|
|
132
|
+
model: "claude-opus-4-7",
|
|
133
|
+
label: "Mine",
|
|
134
|
+
});
|
|
135
|
+
expect(Object.keys(llm.profiles)).toEqual([
|
|
136
|
+
"jev-managed-custom",
|
|
137
|
+
"scratch",
|
|
138
|
+
"armA",
|
|
139
|
+
"blend",
|
|
140
|
+
]);
|
|
141
|
+
expect(llm.activeProfile).toBe("jev-managed-custom");
|
|
142
|
+
expect(llm.advisorProfile).toBe("jev-managed-custom");
|
|
143
|
+
expect(llm.profileOrder).toEqual([
|
|
144
|
+
"balanced",
|
|
145
|
+
"jev-managed-custom",
|
|
146
|
+
"blend",
|
|
147
|
+
]);
|
|
148
|
+
expect(llm.callSites.recall.profile).toBe("jev-managed-custom");
|
|
149
|
+
expect(llm.profiles.scratch.fallbackProfile).toBe("jev-managed-custom");
|
|
150
|
+
expect(llm.profiles.blend.mix[0].profile).toBe("jev-managed-custom");
|
|
151
|
+
expect(readPins("conversations")).toEqual([
|
|
152
|
+
"jev-managed-custom",
|
|
153
|
+
"balanced",
|
|
154
|
+
]);
|
|
155
|
+
expect(readPins("cron_jobs")).toEqual(["jev-managed-custom"]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("keeps suffixing past taken and reserved names", async () => {
|
|
159
|
+
writeConfig({
|
|
160
|
+
llm: {
|
|
161
|
+
profiles: {
|
|
162
|
+
"jev-managed": {
|
|
163
|
+
source: "user",
|
|
164
|
+
provider: "openai",
|
|
165
|
+
model: "gpt-5.5",
|
|
166
|
+
},
|
|
167
|
+
"jev-managed-custom": { source: "user", provider: "openai" },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await migration.run(workspaceDir);
|
|
173
|
+
|
|
174
|
+
const keys = Object.keys(readConfig().llm.profiles);
|
|
175
|
+
expect(keys).toEqual(["jev-managed-custom-2", "jev-managed-custom"]);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("leaves a managed stub alone and is idempotent", async () => {
|
|
179
|
+
const original = {
|
|
180
|
+
llm: {
|
|
181
|
+
profiles: {
|
|
182
|
+
"jev-managed": { source: "managed", status: "disabled" },
|
|
183
|
+
balanced: { source: "managed" },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
writeConfig(original);
|
|
188
|
+
|
|
189
|
+
await migration.run(workspaceDir);
|
|
190
|
+
await migration.run(workspaceDir);
|
|
191
|
+
|
|
192
|
+
expect(readConfig()).toEqual(original);
|
|
193
|
+
expect(existsSync(join(workspaceDir, "data", "db", "assistant.db"))).toBe(
|
|
194
|
+
false,
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wire contract for the dictation REST endpoint (`POST /dictation`).
|
|
3
3
|
*
|
|
4
|
-
* The body web/macOS sends
|
|
5
|
-
*
|
|
4
|
+
* The body web/macOS sends to apply explicit dictation replacements, classify
|
|
5
|
+
* voice input, or edit selected text. `context` is its own
|
|
6
6
|
* named schema/type so consumers (web client) can import `DictationContext`
|
|
7
7
|
* directly instead of inferring it from the generated client.
|
|
8
8
|
*
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { afterAll, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { Provider } from "../../providers/types.js";
|
|
4
|
+
|
|
5
|
+
let configured: {
|
|
6
|
+
provider: Provider;
|
|
7
|
+
configuredProviderName: string;
|
|
8
|
+
entryRouted: boolean;
|
|
9
|
+
} | null = null;
|
|
10
|
+
|
|
11
|
+
const actual = await import("../../providers/provider-send-message.js");
|
|
12
|
+
mock.module("../../providers/provider-send-message.js", () => ({
|
|
13
|
+
...actual,
|
|
14
|
+
resolveConfiguredProvider: async () => configured,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const { resolveTypesafeProvider } = await import("../typesafe-noul.js");
|
|
18
|
+
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
mock.restore();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function stub(name: string): Provider {
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
defaultModel: "jev-latest",
|
|
27
|
+
sendMessage: async () => {
|
|
28
|
+
throw new Error("not dispatched in this test");
|
|
29
|
+
},
|
|
30
|
+
} as unknown as Provider;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("resolveTypesafeProvider", () => {
|
|
34
|
+
test("a BYOK TypeSafe profile resolves", async () => {
|
|
35
|
+
configured = {
|
|
36
|
+
provider: stub("typesafe"),
|
|
37
|
+
configuredProviderName: "typesafe",
|
|
38
|
+
entryRouted: false,
|
|
39
|
+
};
|
|
40
|
+
expect((await resolveTypesafeProvider("voiceEscalationJudge"))?.name).toBe(
|
|
41
|
+
"typesafe",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("a managed Jev profile resolves although it is configured as vellum", async () => {
|
|
46
|
+
configured = {
|
|
47
|
+
provider: stub("typesafe"),
|
|
48
|
+
configuredProviderName: "vellum",
|
|
49
|
+
entryRouted: false,
|
|
50
|
+
};
|
|
51
|
+
expect((await resolveTypesafeProvider("voiceEscalationJudge"))?.name).toBe(
|
|
52
|
+
"typesafe",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("any other dispatched provider is unavailable", async () => {
|
|
57
|
+
configured = {
|
|
58
|
+
provider: stub("anthropic"),
|
|
59
|
+
configuredProviderName: "vellum",
|
|
60
|
+
entryRouted: false,
|
|
61
|
+
};
|
|
62
|
+
expect(await resolveTypesafeProvider("voiceEscalationJudge")).toBeNull();
|
|
63
|
+
configured = null;
|
|
64
|
+
expect(await resolveTypesafeProvider("voiceEscalationJudge")).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -28,12 +28,18 @@ export interface TypesafeNoulResult {
|
|
|
28
28
|
latencyMs: number;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The call site's provider when it resolves to TypeSafe, else null. Judged
|
|
33
|
+
* on the dispatched adapter's name rather than the configured provider: a
|
|
34
|
+
* managed profile is configured as `vellum` and only substitutes the
|
|
35
|
+
* TypeSafe upstream at dispatch, so the configured name alone would report
|
|
36
|
+
* the managed Jev profile as unavailable.
|
|
37
|
+
*/
|
|
32
38
|
export async function resolveTypesafeProvider(
|
|
33
39
|
callSite: LLMCallSite,
|
|
34
40
|
): Promise<Provider | null> {
|
|
35
41
|
const resolved = await resolveConfiguredProvider(callSite);
|
|
36
|
-
return resolved?.
|
|
42
|
+
return resolved?.provider.name === TYPESAFE_PROVIDER_NAME
|
|
37
43
|
? resolved.provider
|
|
38
44
|
: null;
|
|
39
45
|
}
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* correct reply need something the front door cannot do from here? A
|
|
10
10
|
* confident yes overrules a front-door answer and hands the turn off.
|
|
11
11
|
*
|
|
12
|
-
* The judge
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* The judge uses managed Jev by default when Vellum-managed inference is
|
|
13
|
+
* available. An explicit TypeSafe call-site override can use a user-owned
|
|
14
|
+
* connection. Every other outcome (unavailable route, provider error,
|
|
15
|
+
* timeout, unparseable answer) is a "clear" verdict, so the front door's own
|
|
16
|
+
* decision stands exactly as it would without the judge.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import type { Message, Provider } from "../providers/types.js";
|
|
@@ -5,6 +5,7 @@ import { resolveModelIntent } from "../../providers/model-intents.js";
|
|
|
5
5
|
import { CALL_SITE_DEFAULTS } from "../call-site-defaults.js";
|
|
6
6
|
import {
|
|
7
7
|
CODE_DEFAULT_PROFILE_ENTRIES,
|
|
8
|
+
getConversationProfilesForProvider,
|
|
8
9
|
getEffectiveProfile,
|
|
9
10
|
getEffectiveProfiles,
|
|
10
11
|
getEffectiveProfilesForProvider,
|
|
@@ -59,6 +60,37 @@ describe("getEffectiveProfiles", () => {
|
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
62
|
|
|
63
|
+
test("the managed Jev profile routes jev-latest through TypeSafe and is managed-only", () => {
|
|
64
|
+
const jev = CODE_DEFAULT_PROFILE_ENTRIES["jev-managed"];
|
|
65
|
+
expect(jev.model).toBe("jev-latest");
|
|
66
|
+
expect(jev.provider_connection).toBeUndefined();
|
|
67
|
+
expect(resolveRoutingIdentity(jev.provider, jev.model)).toEqual({
|
|
68
|
+
connectionName: "vellum",
|
|
69
|
+
expectedProvider: "typesafe",
|
|
70
|
+
});
|
|
71
|
+
expect(
|
|
72
|
+
resolveDefaultProfileForProvider(undefined, "jev-managed", null),
|
|
73
|
+
).toBeDefined();
|
|
74
|
+
expect(
|
|
75
|
+
resolveDefaultProfileForProvider(undefined, "jev-managed", {
|
|
76
|
+
provider: "anthropic",
|
|
77
|
+
connectionName: "anthropic-personal",
|
|
78
|
+
}),
|
|
79
|
+
).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("the conversation view drops the managed Jev profile but keeps it selectable for call sites", () => {
|
|
83
|
+
expect(
|
|
84
|
+
Object.keys(getConversationProfilesForProvider(undefined, null)),
|
|
85
|
+
).not.toContain("jev-managed");
|
|
86
|
+
expect(
|
|
87
|
+
Object.keys(getUserSelectableProfilesForProvider(undefined, null)),
|
|
88
|
+
).toContain("jev-managed");
|
|
89
|
+
expect(
|
|
90
|
+
Object.keys(getConversationProfilesForProvider(undefined, null)),
|
|
91
|
+
).toContain("balanced");
|
|
92
|
+
});
|
|
93
|
+
|
|
62
94
|
test("the managed Balanced profile routes GLM 5.3 Flash through Fireworks", () => {
|
|
63
95
|
const balanced = CODE_DEFAULT_PROFILE_ENTRIES.balanced;
|
|
64
96
|
expect(balanced.model).toBe("accounts/fireworks/models/glm-5p3-flash");
|
|
@@ -80,7 +112,7 @@ describe("getEffectiveProfiles", () => {
|
|
|
80
112
|
test("defaults absent from the workspace resolve from the catalog; os-beta stays flag-gated", () => {
|
|
81
113
|
const effective = getEffectiveProfiles(undefined);
|
|
82
114
|
expect(Object.keys(effective).sort()).toEqual(
|
|
83
|
-
[...DEFAULT_PROFILE_KEYS, ...BACKUP_PROFILE_KEYS].sort(),
|
|
115
|
+
[...DEFAULT_PROFILE_KEYS, ...BACKUP_PROFILE_KEYS, "jev-managed"].sort(),
|
|
84
116
|
);
|
|
85
117
|
expect(getEffectiveProfile({}, "balanced")?.model).toBe(
|
|
86
118
|
CODE_DEFAULT_PROFILE_ENTRIES.balanced.model as string,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { LLMSchema } from "../schemas/llm.js";
|
|
4
|
+
|
|
5
|
+
const PIN = { callSites: { voiceEscalationJudge: { profile: "jev-managed" } } };
|
|
6
|
+
|
|
7
|
+
describe("jev-managed as a call-site pin target", () => {
|
|
8
|
+
test("resolves under the managed default provider", () => {
|
|
9
|
+
expect(LLMSchema.safeParse(PIN).success).toBe(true);
|
|
10
|
+
expect(
|
|
11
|
+
LLMSchema.safeParse({
|
|
12
|
+
...PIN,
|
|
13
|
+
defaultProvider: { provider: "vellum" },
|
|
14
|
+
}).success,
|
|
15
|
+
).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("can be pinned only to the verdict-consuming call sites", () => {
|
|
19
|
+
for (const site of [
|
|
20
|
+
"voiceEscalationJudge",
|
|
21
|
+
"voiceContinuationJudge",
|
|
22
|
+
"memoryV3SelectL2",
|
|
23
|
+
]) {
|
|
24
|
+
expect(
|
|
25
|
+
LLMSchema.safeParse({
|
|
26
|
+
callSites: { [site]: { profile: "jev-managed" } },
|
|
27
|
+
}).success,
|
|
28
|
+
).toBe(true);
|
|
29
|
+
}
|
|
30
|
+
const result = LLMSchema.safeParse({
|
|
31
|
+
callSites: { conversationTitle: { profile: "jev-managed" } },
|
|
32
|
+
});
|
|
33
|
+
expect(result.success).toBe(false);
|
|
34
|
+
expect(JSON.stringify(result.error?.issues)).toContain(
|
|
35
|
+
"can only be pinned to",
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("cannot be a mix constituent", () => {
|
|
40
|
+
const result = LLMSchema.safeParse({
|
|
41
|
+
profiles: {
|
|
42
|
+
blend: {
|
|
43
|
+
mix: [
|
|
44
|
+
{ profile: "jev-managed", weight: 1 },
|
|
45
|
+
{ profile: "balanced", weight: 1 },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
expect(result.success).toBe(false);
|
|
51
|
+
expect(JSON.stringify(result.error?.issues)).toContain(
|
|
52
|
+
"cannot be a mix constituent",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("is rejected as the active or advisor profile even on the managed column", () => {
|
|
57
|
+
for (const field of ["activeProfile", "advisorProfile"] as const) {
|
|
58
|
+
const result = LLMSchema.safeParse({ [field]: "jev-managed" });
|
|
59
|
+
expect(result.success).toBe(false);
|
|
60
|
+
expect(JSON.stringify(result.error?.issues)).toContain(
|
|
61
|
+
"structured answers rather than chat text",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
expect(LLMSchema.safeParse({ activeProfile: "balanced" }).success).toBe(
|
|
65
|
+
true,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("is rejected under a BYOK default provider, where it has no body", () => {
|
|
70
|
+
const result = LLMSchema.safeParse({
|
|
71
|
+
...PIN,
|
|
72
|
+
defaultProvider: {
|
|
73
|
+
provider: "anthropic",
|
|
74
|
+
connectionName: "anthropic-personal",
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
expect(result.success).toBe(false);
|
|
78
|
+
expect(JSON.stringify(result.error?.issues)).toContain(
|
|
79
|
+
"managed Jev profile",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { JEV_MANAGED_PROFILE_KEY } from "./default-profile-names.js";
|
|
1
2
|
import { type LLMCallSite } from "./schemas/llm.js";
|
|
2
3
|
|
|
3
4
|
type CallSiteDefaultConfig = {
|
|
@@ -123,9 +124,8 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
|
|
|
123
124
|
effort: "low",
|
|
124
125
|
thinking: { enabled: false },
|
|
125
126
|
},
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// answers, so it is priced as a latency call.
|
|
127
|
+
// Voice intent classification and explicit selected-text edits are
|
|
128
|
+
// latency-sensitive: the caller is waiting for insertion or dispatch.
|
|
129
129
|
interactionClassifier: {
|
|
130
130
|
profile: "latency-optimized",
|
|
131
131
|
effort: "low",
|
|
@@ -153,14 +153,14 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
|
|
|
153
153
|
effort: "low",
|
|
154
154
|
thinking: { enabled: false },
|
|
155
155
|
},
|
|
156
|
-
// The
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
// pinned here.
|
|
156
|
+
// The managed-only profile resolves through the Vellum connection. On a
|
|
157
|
+
// BYOK install it has no body, so the judge is unavailable and the front
|
|
158
|
+
// door's own decision stands.
|
|
159
|
+
voiceEscalationJudge: {
|
|
160
|
+
profile: JEV_MANAGED_PROFILE_KEY,
|
|
161
|
+
},
|
|
162
|
+
// The barge-in continuation judge remains off until a TypeSafe profile is
|
|
163
|
+
// pinned here (see live-voice/continuation-judge.ts).
|
|
164
164
|
voiceContinuationJudge: { profile: "cost-optimized" },
|
|
165
165
|
// Names the background continuation a barge-in spawns, from the interrupted
|
|
166
166
|
// transcript. The label is fixed at spawn, so the call runs under a short
|