@mycadet/assistant 0.12.10 → 0.12.11
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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
- package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
- package/openapi.yaml +1 -1
- package/package.json +1 -1
- package/src/__tests__/cloud-profile-guard.test.ts +39 -3
- package/src/config/schemas/llm.ts +3 -5
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
- package/src/runtime/routes/conversation-query-routes.ts +0 -96
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
trimmedNonEmptyString,
|
|
11
11
|
TUNNEL_PROVIDERS,
|
|
12
12
|
type TunnelRecord,
|
|
13
|
-
|
|
13
|
+
tunnelHostForCloudHost,
|
|
14
14
|
} from "../ingress.js";
|
|
15
15
|
import {
|
|
16
16
|
buildTwilioMediaStreamUrl,
|
|
@@ -19,27 +19,27 @@ import {
|
|
|
19
19
|
resolveTwilioPublicBaseUrl,
|
|
20
20
|
} from "../twilio-ingress.js";
|
|
21
21
|
|
|
22
|
-
describe("
|
|
22
|
+
describe("tunnelHostForCloudHost", () => {
|
|
23
23
|
test("maps the prod platform host to the prod tunnel", () => {
|
|
24
|
-
expect(
|
|
24
|
+
expect(tunnelHostForCloudHost("cloud.mycadet.ai")).toBe(
|
|
25
25
|
"tunnel.mycadet.ai",
|
|
26
26
|
);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test("maps env-prefixed platform hosts to their env tunnel", () => {
|
|
30
|
-
expect(
|
|
30
|
+
expect(tunnelHostForCloudHost("staging-cloud.mycadet.ai")).toBe(
|
|
31
31
|
"tunnel-staging.mycadet.ai",
|
|
32
32
|
);
|
|
33
|
-
expect(
|
|
33
|
+
expect(tunnelHostForCloudHost("dev-cloud.mycadet.ai")).toBe(
|
|
34
34
|
"tunnel-dev.mycadet.ai",
|
|
35
35
|
);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test("returns null for hosts outside the deployment convention", () => {
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
39
|
+
expect(tunnelHostForCloudHost("localhost")).toBeNull();
|
|
40
|
+
expect(tunnelHostForCloudHost("platform.example.com")).toBeNull();
|
|
41
41
|
expect(
|
|
42
|
-
|
|
42
|
+
tunnelHostForCloudHost("staging-cloud.mycadet.ai.evil.com"),
|
|
43
43
|
).toBeNull();
|
|
44
44
|
});
|
|
45
45
|
});
|
package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* gateway speech relay and the web live-voice client so the two ends of the
|
|
7
7
|
* cloud-speech transport can't drift.
|
|
8
8
|
*/
|
|
9
|
-
export function
|
|
9
|
+
export function tunnelHostForCloudHost(host: string): string | null {
|
|
10
10
|
if (host === "cloud.mycadet.ai") {
|
|
11
11
|
return "tunnel.mycadet.ai";
|
|
12
12
|
}
|
|
@@ -143,6 +143,19 @@ export interface CesHttpCredentialClient {
|
|
|
143
143
|
// Factory
|
|
144
144
|
// ---------------------------------------------------------------------------
|
|
145
145
|
|
|
146
|
+
function listErrorContext(
|
|
147
|
+
response: Response,
|
|
148
|
+
path: string,
|
|
149
|
+
): Record<string, unknown> {
|
|
150
|
+
const requestId = response.headers.get("x-ces-error-id");
|
|
151
|
+
return {
|
|
152
|
+
status: response.status,
|
|
153
|
+
method: "GET",
|
|
154
|
+
path,
|
|
155
|
+
...(requestId && /^[a-f0-9-]{36}$/i.test(requestId) ? { requestId } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
146
159
|
export function createCesHttpCredentialClient(
|
|
147
160
|
config: CesHttpCredentialConfig,
|
|
148
161
|
logger: CesHttpLogger,
|
|
@@ -298,7 +311,7 @@ export function createCesHttpCredentialClient(
|
|
|
298
311
|
if (!res) return { accounts: [], unreachable: true };
|
|
299
312
|
if (!res.ok) {
|
|
300
313
|
logger.warn(
|
|
301
|
-
|
|
314
|
+
listErrorContext(res, "/v1/credentials"),
|
|
302
315
|
"CES credential list returned non-OK status",
|
|
303
316
|
);
|
|
304
317
|
return { accounts: [], unreachable: true };
|
|
@@ -399,6 +412,10 @@ export function createCesHttpCredentialClient(
|
|
|
399
412
|
return { records: [], unreachable: true };
|
|
400
413
|
}
|
|
401
414
|
if (!res.ok) {
|
|
415
|
+
logger.warn(
|
|
416
|
+
listErrorContext(res, "/v1/metadata"),
|
|
417
|
+
"CES metadata list returned non-OK status",
|
|
418
|
+
);
|
|
402
419
|
return { records: [], unreachable: true };
|
|
403
420
|
}
|
|
404
421
|
const data = (await res.json()) as {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
trimmedNonEmptyString,
|
|
11
11
|
TUNNEL_PROVIDERS,
|
|
12
12
|
type TunnelRecord,
|
|
13
|
-
|
|
13
|
+
tunnelHostForCloudHost,
|
|
14
14
|
} from "../ingress.js";
|
|
15
15
|
import {
|
|
16
16
|
buildTwilioMediaStreamUrl,
|
|
@@ -19,27 +19,27 @@ import {
|
|
|
19
19
|
resolveTwilioPublicBaseUrl,
|
|
20
20
|
} from "../twilio-ingress.js";
|
|
21
21
|
|
|
22
|
-
describe("
|
|
22
|
+
describe("tunnelHostForCloudHost", () => {
|
|
23
23
|
test("maps the prod platform host to the prod tunnel", () => {
|
|
24
|
-
expect(
|
|
24
|
+
expect(tunnelHostForCloudHost("cloud.mycadet.ai")).toBe(
|
|
25
25
|
"tunnel.mycadet.ai",
|
|
26
26
|
);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test("maps env-prefixed platform hosts to their env tunnel", () => {
|
|
30
|
-
expect(
|
|
30
|
+
expect(tunnelHostForCloudHost("staging-cloud.mycadet.ai")).toBe(
|
|
31
31
|
"tunnel-staging.mycadet.ai",
|
|
32
32
|
);
|
|
33
|
-
expect(
|
|
33
|
+
expect(tunnelHostForCloudHost("dev-cloud.mycadet.ai")).toBe(
|
|
34
34
|
"tunnel-dev.mycadet.ai",
|
|
35
35
|
);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test("returns null for hosts outside the deployment convention", () => {
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
39
|
+
expect(tunnelHostForCloudHost("localhost")).toBeNull();
|
|
40
|
+
expect(tunnelHostForCloudHost("platform.example.com")).toBeNull();
|
|
41
41
|
expect(
|
|
42
|
-
|
|
42
|
+
tunnelHostForCloudHost("staging-cloud.mycadet.ai.evil.com"),
|
|
43
43
|
).toBeNull();
|
|
44
44
|
});
|
|
45
45
|
});
|
package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* gateway speech relay and the web live-voice client so the two ends of the
|
|
7
7
|
* cloud-speech transport can't drift.
|
|
8
8
|
*/
|
|
9
|
-
export function
|
|
9
|
+
export function tunnelHostForCloudHost(host: string): string | null {
|
|
10
10
|
if (host === "cloud.mycadet.ai") {
|
|
11
11
|
return "tunnel.mycadet.ai";
|
|
12
12
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
trimmedNonEmptyString,
|
|
11
11
|
TUNNEL_PROVIDERS,
|
|
12
12
|
type TunnelRecord,
|
|
13
|
-
|
|
13
|
+
tunnelHostForCloudHost,
|
|
14
14
|
} from "../ingress.js";
|
|
15
15
|
import {
|
|
16
16
|
buildTwilioMediaStreamUrl,
|
|
@@ -19,27 +19,27 @@ import {
|
|
|
19
19
|
resolveTwilioPublicBaseUrl,
|
|
20
20
|
} from "../twilio-ingress.js";
|
|
21
21
|
|
|
22
|
-
describe("
|
|
22
|
+
describe("tunnelHostForCloudHost", () => {
|
|
23
23
|
test("maps the prod platform host to the prod tunnel", () => {
|
|
24
|
-
expect(
|
|
24
|
+
expect(tunnelHostForCloudHost("cloud.mycadet.ai")).toBe(
|
|
25
25
|
"tunnel.mycadet.ai",
|
|
26
26
|
);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test("maps env-prefixed platform hosts to their env tunnel", () => {
|
|
30
|
-
expect(
|
|
30
|
+
expect(tunnelHostForCloudHost("staging-cloud.mycadet.ai")).toBe(
|
|
31
31
|
"tunnel-staging.mycadet.ai",
|
|
32
32
|
);
|
|
33
|
-
expect(
|
|
33
|
+
expect(tunnelHostForCloudHost("dev-cloud.mycadet.ai")).toBe(
|
|
34
34
|
"tunnel-dev.mycadet.ai",
|
|
35
35
|
);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test("returns null for hosts outside the deployment convention", () => {
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
39
|
+
expect(tunnelHostForCloudHost("localhost")).toBeNull();
|
|
40
|
+
expect(tunnelHostForCloudHost("platform.example.com")).toBeNull();
|
|
41
41
|
expect(
|
|
42
|
-
|
|
42
|
+
tunnelHostForCloudHost("staging-cloud.mycadet.ai.evil.com"),
|
|
43
43
|
).toBeNull();
|
|
44
44
|
});
|
|
45
45
|
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* gateway speech relay and the web live-voice client so the two ends of the
|
|
7
7
|
* cloud-speech transport can't drift.
|
|
8
8
|
*/
|
|
9
|
-
export function
|
|
9
|
+
export function tunnelHostForCloudHost(host: string): string | null {
|
|
10
10
|
if (host === "cloud.mycadet.ai") {
|
|
11
11
|
return "tunnel.mycadet.ai";
|
|
12
12
|
}
|
package/openapi.yaml
CHANGED
package/package.json
CHANGED
|
@@ -138,6 +138,42 @@ function savedProfile(name: string): Record<string, unknown> {
|
|
|
138
138
|
>;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
describe("partial default-provider config", () => {
|
|
142
|
+
for (const provider of ["cadet", "chatgpt"]) {
|
|
143
|
+
test(`timezone PATCH accepts a model-less ${provider} default`, async () => {
|
|
144
|
+
seedRawConfig({ llm: { default: { provider } } });
|
|
145
|
+
await patchRoute.handler({
|
|
146
|
+
body: { ui: { userTimezone: "America/New_York" } },
|
|
147
|
+
});
|
|
148
|
+
const saved = committedRaw() as {
|
|
149
|
+
ui: { userTimezone: string };
|
|
150
|
+
llm: { default: { provider: string; model?: string } };
|
|
151
|
+
};
|
|
152
|
+
expect(saved.ui.userTimezone).toBe("America/New_York");
|
|
153
|
+
expect(saved.llm.default).toEqual({ provider });
|
|
154
|
+
expectOneCommitCycle();
|
|
155
|
+
});
|
|
156
|
+
test(`PATCH can select ${provider} as default without a model`, async () => {
|
|
157
|
+
seedRawConfig({});
|
|
158
|
+
await patchRoute.handler({ body: { llm: { default: { provider } } } });
|
|
159
|
+
expect(
|
|
160
|
+
(committedRaw() as { llm: { default: { provider: string } } }).llm
|
|
161
|
+
.default,
|
|
162
|
+
).toEqual({ provider });
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
test("Zod rejects a default model with the wrong type", async () => {
|
|
166
|
+
await expect(
|
|
167
|
+
patchRoute.handler({
|
|
168
|
+
body: {
|
|
169
|
+
llm: { default: { provider: "cadet", model: 42 } },
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
).rejects.toThrow(/model|expected string/i);
|
|
173
|
+
expectNothingCommitted();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
141
177
|
// ---------------------------------------------------------------------------
|
|
142
178
|
// PUT /v1/config/llm/profiles/:name — replace inference profile
|
|
143
179
|
// ---------------------------------------------------------------------------
|
|
@@ -210,13 +246,13 @@ describe("PUT /v1/config/llm/profiles/:name — cloud profile guard", () => {
|
|
|
210
246
|
).toBeUndefined();
|
|
211
247
|
});
|
|
212
248
|
|
|
213
|
-
test("rejects
|
|
249
|
+
test("Zod rejects invalid fields in a replacement profile", async () => {
|
|
214
250
|
await expect(
|
|
215
251
|
replaceRoute.handler({
|
|
216
252
|
pathParams: { name: "my-custom" },
|
|
217
|
-
body: { provider: "cadet", model: "
|
|
253
|
+
body: { provider: "cadet", model: "claude-opus-4-8", maxTokens: 0 },
|
|
218
254
|
}),
|
|
219
|
-
).rejects.toThrow(/
|
|
255
|
+
).rejects.toThrow(/expected number to be >0/);
|
|
220
256
|
expect(committedRaw()).toBeNull();
|
|
221
257
|
});
|
|
222
258
|
|
|
@@ -54,9 +54,8 @@ const ALWAYS_RESOLVING_MANAGED_ONLY_PROFILE_KEYS =
|
|
|
54
54
|
* an open string so a stored provider outside this list parses instead of
|
|
55
55
|
* stripping its profile; dispatch resolves such a label as a connection
|
|
56
56
|
* entry name and fails with an explainable resolution error when no row
|
|
57
|
-
* carries it.
|
|
58
|
-
*
|
|
59
|
-
* is where entry names unlock when the entries model enables them.
|
|
57
|
+
* carries it. The generic config routes follow this open-string schema;
|
|
58
|
+
* dedicated profile routes apply their own provider selection rules.
|
|
60
59
|
*/
|
|
61
60
|
export const KNOWN_LLM_PROVIDERS = [
|
|
62
61
|
"anthropic",
|
|
@@ -153,8 +152,7 @@ export function isByokDefaultProviderChoice(
|
|
|
153
152
|
* on that profile/call site deterministically — a call-site fragment naming
|
|
154
153
|
* an identity without a model would inherit whatever model the winning
|
|
155
154
|
* profile carries, which the identity may not serve. Enforced at parse time
|
|
156
|
-
* (LLMSchema.superRefine)
|
|
157
|
-
* (commitConfigWrite), and by the profile write route.
|
|
155
|
+
* (LLMSchema.superRefine) and by the profile creation route.
|
|
158
156
|
*/
|
|
159
157
|
export function routingIdentityModelIssue(
|
|
160
158
|
provider: string,
|
|
@@ -1625,7 +1625,7 @@ describe("config GET serves profiles verbatim", () => {
|
|
|
1625
1625
|
});
|
|
1626
1626
|
});
|
|
1627
1627
|
|
|
1628
|
-
describe("provider
|
|
1628
|
+
describe("provider labels follow the open-string Zod schema", () => {
|
|
1629
1629
|
const configPatchRoute = ROUTES.find(
|
|
1630
1630
|
(r) => r.operationId === "config_patch",
|
|
1631
1631
|
)!;
|
|
@@ -1659,21 +1659,23 @@ describe("provider membership at the write choke point", () => {
|
|
|
1659
1659
|
expect(llm.profiles?.work?.provider).toBe("anthropic-work");
|
|
1660
1660
|
});
|
|
1661
1661
|
|
|
1662
|
-
test("
|
|
1662
|
+
test("a new provider label is accepted by the schema", async () => {
|
|
1663
1663
|
rawConfigFixture = { llm: {} };
|
|
1664
1664
|
seedRawConfig();
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1665
|
+
await configPatchRoute.handler({
|
|
1666
|
+
body: {
|
|
1667
|
+
llm: {
|
|
1668
|
+
profiles: {
|
|
1669
|
+
work: {
|
|
1670
|
+
source: "user",
|
|
1671
|
+
provider: "anthropic-work",
|
|
1672
|
+
model: "claude-opus-4-8",
|
|
1672
1673
|
},
|
|
1673
1674
|
},
|
|
1674
1675
|
},
|
|
1675
|
-
}
|
|
1676
|
-
)
|
|
1676
|
+
},
|
|
1677
|
+
});
|
|
1678
|
+
expect(persistedProfile("work").provider).toBe("anthropic-work");
|
|
1677
1679
|
});
|
|
1678
1680
|
});
|
|
1679
1681
|
|
|
@@ -53,8 +53,6 @@ import {
|
|
|
53
53
|
LLMDefaultConfigSchema,
|
|
54
54
|
LLMSchema,
|
|
55
55
|
ProfileEntry,
|
|
56
|
-
routingIdentityModelIssue,
|
|
57
|
-
unknownLlmProviderIssue,
|
|
58
56
|
} from "../../config/schemas/llm.js";
|
|
59
57
|
import { VALID_MEMORY_EMBEDDING_PROVIDERS } from "../../config/schemas/memory-storage.js";
|
|
60
58
|
import { ServiceModeSchema } from "../../config/schemas/services.js";
|
|
@@ -106,7 +104,6 @@ import { getMemoryRecallLogByMessageIds } from "../../plugins/defaults/memory/me
|
|
|
106
104
|
import { getMemoryV2ActivationLogByMessageIds } from "../../plugins/defaults/memory/v2/activation-log-store.js";
|
|
107
105
|
import { getMemoryV3SelectionForInspectorByMessageIds } from "../../plugins/defaults/memory/v3/selection-log-store.js";
|
|
108
106
|
import { CLOUD_ROUTABLE_PROVIDERS } from "../../providers/cadet-model-routing.js";
|
|
109
|
-
import { writableProfileProviderIssue } from "../../providers/connection-resolution.js";
|
|
110
107
|
import { ROUTING_IDENTITY_PROVIDERS } from "../../providers/inference/auth.js";
|
|
111
108
|
import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "../../providers/inference/auth.js";
|
|
112
109
|
import {
|
|
@@ -684,98 +681,6 @@ function handleGetConfigSchema({ queryParams = {} }: RouteHandlerArgs) {
|
|
|
684
681
|
};
|
|
685
682
|
}
|
|
686
683
|
|
|
687
|
-
/**
|
|
688
|
-
* Reject writes that would store a routing-identity provider with a missing
|
|
689
|
-
* or unroutable model (see `routingIdentityModelIssue`). saveRawConfig
|
|
690
|
-
* persists without schema validation, so the parse-time rejection alone
|
|
691
|
-
* would let the pair reach disk and be stripped on the next read — a silent
|
|
692
|
-
* no-op where the user expects a saved override.
|
|
693
|
-
*/
|
|
694
|
-
function collectProviderEntries(
|
|
695
|
-
raw: Record<string, unknown>,
|
|
696
|
-
): [string, { provider?: unknown; model?: unknown }][] {
|
|
697
|
-
const llm = raw.llm as
|
|
698
|
-
| {
|
|
699
|
-
default?: { provider?: unknown; model?: unknown } | null;
|
|
700
|
-
profiles?: Record<
|
|
701
|
-
string,
|
|
702
|
-
{ provider?: unknown; model?: unknown } | null | undefined
|
|
703
|
-
>;
|
|
704
|
-
callSites?: Record<
|
|
705
|
-
string,
|
|
706
|
-
{ provider?: unknown; model?: unknown } | null | undefined
|
|
707
|
-
>;
|
|
708
|
-
}
|
|
709
|
-
| undefined;
|
|
710
|
-
const entries: [string, { provider?: unknown; model?: unknown }][] = [];
|
|
711
|
-
// llm.default is a raw compatibility field outside the parsed schema;
|
|
712
|
-
// profile materialization uses the on-disk blob as its fill base, so it is
|
|
713
|
-
// checked like the schema-carried sections.
|
|
714
|
-
if (llm?.default) {
|
|
715
|
-
entries.push(["llm.default", llm.default]);
|
|
716
|
-
}
|
|
717
|
-
for (const section of ["profiles", "callSites"] as const) {
|
|
718
|
-
for (const [name, entry] of Object.entries(llm?.[section] ?? {})) {
|
|
719
|
-
if (entry) {
|
|
720
|
-
entries.push([`llm.${section}.${name}`, entry]);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
return entries;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function assertRoutableIdentityEntries(
|
|
728
|
-
preWrite: Record<string, unknown>,
|
|
729
|
-
raw: Record<string, unknown>,
|
|
730
|
-
): void {
|
|
731
|
-
const entries = collectProviderEntries(raw);
|
|
732
|
-
const priorProviders = new Map(
|
|
733
|
-
collectProviderEntries(preWrite).flatMap(([label, entry]) =>
|
|
734
|
-
typeof entry.provider === "string" ? [[label, entry.provider]] : [],
|
|
735
|
-
),
|
|
736
|
-
);
|
|
737
|
-
for (const [label, entry] of entries) {
|
|
738
|
-
if (typeof entry.provider !== "string") {
|
|
739
|
-
continue;
|
|
740
|
-
}
|
|
741
|
-
// The provider schema is an open string (a stored value outside the
|
|
742
|
-
// known set parses instead of stripping its profile), so write-time
|
|
743
|
-
// membership is enforced here and at the profiles route. Scoped to
|
|
744
|
-
// providers this write introduces or changes: a stored value outside
|
|
745
|
-
// the known set is readable and dispatchable by design, and
|
|
746
|
-
// re-validating it on every write would make all later settings saves
|
|
747
|
-
// fail with no in-product repair path. Profiles accept entry names;
|
|
748
|
-
// call-site fragments and the legacy default blob stay vendor-only
|
|
749
|
-
// (overrides become model-only with the entries demolition).
|
|
750
|
-
if (entry.provider !== priorProviders.get(label)) {
|
|
751
|
-
const providerIssue = label.startsWith("llm.profiles.")
|
|
752
|
-
? writableProfileProviderIssue(entry.provider)
|
|
753
|
-
: unknownLlmProviderIssue(entry.provider);
|
|
754
|
-
if (providerIssue) {
|
|
755
|
-
throw new BadRequestError(`${providerIssue} (${label})`);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
// A model-less entry under a code-defined default name is the thin
|
|
759
|
-
// managed stub — its content is code-owned, so the routing-identity
|
|
760
|
-
// model check applies only to entries carrying their own body.
|
|
761
|
-
const profileName = label.startsWith("llm.profiles.")
|
|
762
|
-
? label.slice("llm.profiles.".length)
|
|
763
|
-
: undefined;
|
|
764
|
-
const isManagedName =
|
|
765
|
-
profileName != null && MANAGED_PROFILE_NAMES.has(profileName);
|
|
766
|
-
if (entry.model == null && isManagedName) {
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
const issue = routingIdentityModelIssue(
|
|
770
|
-
entry.provider,
|
|
771
|
-
typeof entry.model === "string" ? entry.model : undefined,
|
|
772
|
-
);
|
|
773
|
-
if (issue) {
|
|
774
|
-
throw new BadRequestError(`${issue} (${label})`);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
684
|
export async function commitConfigWrite(
|
|
780
685
|
raw: Record<string, unknown>,
|
|
781
686
|
opLabel: string,
|
|
@@ -792,7 +697,6 @@ export async function commitConfigWrite(
|
|
|
792
697
|
if (!chatSettings.success) {
|
|
793
698
|
throw new BadRequestError(chatSettings.error.message);
|
|
794
699
|
}
|
|
795
|
-
assertRoutableIdentityEntries(preWrite, raw);
|
|
796
700
|
|
|
797
701
|
// Suppress the file-watcher callback for the duration of the debounce
|
|
798
702
|
// window. Without this, the ConfigWatcher detects the config.json write
|