@vellumai/assistant 0.12.3 → 0.12.4-dev.202609231620.a15f321
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/Dockerfile +7 -0
- package/README.md +12 -12
- package/docker/mousepad/90-vellum.gschema.override +4 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/notification-avatar.test.ts +48 -4
- package/node_modules/@vellumai/avatar-manifest/src/notification-avatar.ts +12 -8
- package/openapi.yaml +97 -4
- package/package.json +1 -1
- package/src/__tests__/approval-routes-http.test.ts +4 -2
- package/src/__tests__/call-routes-http.test.ts +4 -2
- package/src/__tests__/channel-readiness-discord.test.ts +8 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +8 -0
- package/src/__tests__/config-loader-backfill.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-resurface-done.test.ts +289 -0
- package/src/__tests__/default-profile-catalog-fallback.test.ts +1 -0
- package/src/__tests__/gemini-provider.test.ts +31 -0
- package/src/__tests__/inference-profile-validation-jev.test.ts +17 -0
- package/src/__tests__/job-handler-registration-guard.test.ts +1 -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__/openai-responses-prompt-cache.test.ts +3 -1
- package/src/__tests__/platform-callback-registration.test.ts +46 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/pricing.test.ts +33 -0
- package/src/__tests__/provider-error-scenarios.test.ts +46 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +45 -15
- package/src/__tests__/retry-fallback-escalation.test.ts +22 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +116 -66
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -2
- package/src/__tests__/skill-load-inline-command.test.ts +35 -0
- package/src/__tests__/skill-load-tool.test.ts +42 -11
- package/src/__tests__/skill-tool-factory.test.ts +50 -2
- package/src/__tests__/steer-on-enqueue-question.test.ts +265 -34
- package/src/__tests__/validate-input.test.ts +73 -0
- package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +197 -0
- package/src/api/ask-question-tool.ts +50 -0
- package/src/api/events/tool-result.ts +11 -0
- package/src/api/index.ts +7 -0
- package/src/api/requests/dictation.ts +2 -2
- package/src/api/responses/home.ts +27 -0
- package/src/avatar/notification-avatar.test.ts +42 -34
- package/src/avatar/notification-avatar.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +63 -1
- package/src/calls/voice-escalation-judge.ts +6 -5
- package/src/calls/voice-screen-annotation.ts +44 -0
- package/src/calls/voice-session-bridge.ts +22 -0
- package/src/cli/commands/__tests__/plugins.test.ts +29 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +12 -2
- package/src/cli/commands/platform/index.help.ts +2 -1
- package/src/cli/commands/platform/index.ts +8 -2
- package/src/cli/commands/plugins.ts +16 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +29 -0
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +92 -2
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +15 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +66 -2
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +21 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +65 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +43 -0
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +26 -0
- package/src/cli/lib/bundled-marketplace.json +4 -4
- package/src/cli/lib/inspect-plugin.ts +5 -0
- package/src/cli/lib/plugin-catalog-cache.ts +41 -4
- package/src/cli/lib/plugin-catalog-local.ts +9 -0
- package/src/cli/lib/plugin-catalog-platform.ts +48 -9
- package/src/cli/lib/plugin-catalog-visibility.ts +41 -0
- package/src/cli/lib/plugin-details.ts +10 -7
- package/src/cli/lib/search-plugins.ts +2 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +61 -1
- package/src/config/__tests__/jev-managed-profile.test.ts +82 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +71 -0
- package/src/config/bundled-skills/screen-annotation/SKILL.md +6 -3
- package/src/config/call-site-defaults.ts +17 -11
- package/src/config/default-profile-catalog.ts +118 -19
- package/src/config/default-profile-names.ts +69 -0
- package/src/config/feature-flag-registry.json +25 -9
- 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 +8 -1
- package/src/config/schemas/llm.ts +70 -9
- package/src/config/sidebar-done-gate.ts +27 -0
- package/src/config/sync-gated-profiles.ts +96 -44
- package/src/daemon/__tests__/auto-profile-router.test.ts +228 -0
- package/src/daemon/auto-profile-router.ts +220 -0
- package/src/daemon/conversation-agent-loop.ts +77 -4
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-turn-finalize.ts +12 -0
- package/src/daemon/dictation-text-processing.ts +4 -4
- package/src/daemon/handlers/__tests__/conversation-history-search.test.ts +61 -0
- package/src/daemon/handlers/config-channels.ts +3 -1
- package/src/daemon/handlers/conversation-history.ts +11 -1
- package/src/daemon/handlers/conversations.ts +118 -10
- package/src/daemon/pkb-reminder-builder.test.ts +10 -3
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/desktop/desktop-browser-endpoint.ts +1 -0
- package/src/home/feed-types.ts +2 -0
- package/src/inbound/platform-callback-registration.ts +19 -6
- package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +49 -1
- package/src/ipc/routes/conversation-sync-ipc-routes.ts +38 -2
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +27 -0
- package/src/live-voice/__tests__/protocol.test.ts +22 -0
- package/src/live-voice/continuation-judge.ts +1 -1
- package/src/live-voice/live-voice-session.ts +7 -6
- package/src/live-voice/protocol.ts +16 -5
- package/src/messaging/providers/gmail/client.ts +4 -0
- package/src/notifications/README.md +11 -3
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +87 -3
- package/src/notifications/conversation-pairing.ts +23 -8
- package/src/notifications/home-feed-mirror.ts +39 -0
- package/src/notifications/home-feed-side-effect.ts +46 -19
- package/src/oauth/AGENTS.md +7 -1
- package/src/permissions/question-prompter.ts +14 -4
- package/src/persistence/conversation-crud.ts +114 -3
- package/src/persistence/conversation-queries.ts +48 -3
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +228 -32
- package/src/persistence/embeddings/embedding-backend-shutdown.test.ts +47 -0
- package/src/persistence/embeddings/embedding-backend.ts +23 -41
- package/src/persistence/embeddings/embedding-gemini.test.ts +17 -0
- package/src/persistence/embeddings/embedding-gemini.ts +3 -1
- package/src/persistence/embeddings/embedding-local.ts +72 -17
- package/src/persistence/embeddings/embedding-types.ts +0 -19
- package/src/persistence/jobs-store.ts +229 -0
- package/src/persistence/migrations/383-normalize-opencode-host-connections.ts +44 -0
- package/src/persistence/migrations/__tests__/383-normalize-opencode-host-connections.test.ts +102 -0
- package/src/persistence/steps.ts +12 -0
- package/src/platform/sync-avatar.test.ts +29 -0
- package/src/plugin-api/model-profiles.ts +5 -3
- package/src/plugins/defaults/memory/AGENTS.md +5 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-boundary-guard.test.ts +45 -0
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-job.test.ts +580 -0
- package/src/plugins/defaults/memory/graph/tools.ts +9 -5
- package/src/plugins/defaults/memory/hooks/conversation-deleted.ts +4 -0
- package/src/plugins/defaults/memory/job-handlers.ts +8 -2
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +5 -0
- package/src/plugins/defaults/memory/skill-update-receipt-job.ts +334 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +1 -1
- package/src/plugins/defaults/memory/tools.test.ts +17 -0
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +3 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +2 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/anthropic/client.ts +2 -2
- package/src/providers/gemini/client.ts +17 -1
- package/src/providers/inference/auth.ts +25 -0
- package/src/providers/jev/ask.test.ts +66 -0
- package/src/{calls/typesafe-noul.ts → providers/jev/ask.ts} +75 -36
- package/src/providers/malformed-tool-call.ts +55 -0
- package/src/providers/model-catalog.ts +257 -4
- package/src/providers/openai/__tests__/chat-completions-malformed-tool-call.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +30 -0
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/retry.ts +68 -36
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/vellum-model-routing.test.ts +1 -0
- package/src/runtime/__tests__/agent-wake-archived-gate.test.ts +90 -0
- package/src/runtime/__tests__/question-resolution.test.ts +32 -0
- package/src/runtime/agent-wake.ts +9 -2
- package/src/runtime/auth/__tests__/middleware.test.ts +138 -0
- package/src/runtime/auth/middleware.ts +15 -11
- package/src/runtime/middleware/auth.ts +7 -1
- package/src/runtime/pending-interactions.ts +8 -8
- package/src/runtime/question-resolution.ts +54 -8
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +146 -0
- package/src/runtime/routes/__tests__/dictation-processing.test.ts +174 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +74 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +60 -0
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +16 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +8 -0
- package/src/runtime/routes/conversation-list-routes.ts +16 -14
- package/src/runtime/routes/conversation-query-routes.ts +4 -0
- package/src/runtime/routes/conversation-routes.ts +4 -2
- package/src/runtime/routes/diagnostics-routes.ts +66 -229
- package/src/runtime/routes/global-search-routes.ts +22 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +25 -8
- package/src/runtime/routes/llm-call-sites-routes.ts +19 -10
- package/src/runtime/routes/platform-routes.ts +6 -1
- package/src/runtime/routes/plugins-routes.ts +5 -2
- package/src/runtime/sync/documents-sidecar-publish.test.ts +50 -4
- package/src/runtime/sync/resource-sync-events.ts +11 -0
- package/src/runtime/sync/worker-daemon-notify.test.ts +30 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -0
- package/src/runtime/verification-outbound-actions.ts +2 -2
- package/src/skills/validate-input.ts +75 -11
- package/src/telegram/__tests__/webhook-health.test.ts +8 -0
- package/src/tools/ask-question/ask-question-tool.test.ts +38 -0
- package/src/tools/ask-question/ask-question-tool.ts +12 -1
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +28 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +34 -0
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +35 -1
- package/src/tools/network/__tests__/web-search.test.ts +96 -0
- package/src/tools/network/web-fetch.ts +30 -9
- package/src/tools/network/web-search.ts +235 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/format-input-schema.ts +15 -0
- package/src/tools/skills/load.ts +15 -43
- package/src/tools/skills/scaffold-managed.ts +86 -71
- package/src/tools/skills/skill-tool-factory.ts +2 -1
- package/src/tools/workflows/manage-workflows.ts +4 -2
- package/src/util/worker-pipe.test.ts +146 -0
- package/src/util/worker-pipe.ts +49 -0
- package/src/workspace/migrations/158-repoint-opencode-host-provider-fragments.ts +163 -0
- package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +344 -0
- package/src/workspace/migrations/__tests__/158-repoint-opencode-host-provider-fragments.test.ts +188 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
2
|
+
import type { PluginCatalog } from "./search-plugins.js";
|
|
3
|
+
|
|
4
|
+
export const MCP_CATALOG_QA_INTEGRATIONS_FLAG = "mcp-catalog-qa-integrations";
|
|
5
|
+
|
|
6
|
+
const MCP_CATALOG_QA_INTEGRATION_NAMES = new Set([
|
|
7
|
+
"gamma",
|
|
8
|
+
"guru",
|
|
9
|
+
"intercom",
|
|
10
|
+
"ramp",
|
|
11
|
+
"semrush",
|
|
12
|
+
"typeform",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export type PluginCatalogFeatureFlagResolver = (key: string) => boolean;
|
|
16
|
+
|
|
17
|
+
export function isPluginCatalogEntryVisible(
|
|
18
|
+
name: string,
|
|
19
|
+
isFeatureFlagEnabled: PluginCatalogFeatureFlagResolver = isAssistantFeatureFlagEnabled,
|
|
20
|
+
): boolean {
|
|
21
|
+
return (
|
|
22
|
+
!MCP_CATALOG_QA_INTEGRATION_NAMES.has(name) ||
|
|
23
|
+
isFeatureFlagEnabled(MCP_CATALOG_QA_INTEGRATIONS_FLAG)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Hide catalog entries whose OAuth flows are still being validated. Installed
|
|
29
|
+
* plugins are unaffected because this filter only applies to catalog reads.
|
|
30
|
+
*/
|
|
31
|
+
export function filterPluginCatalogByFeatureFlags(
|
|
32
|
+
catalog: PluginCatalog,
|
|
33
|
+
isFeatureFlagEnabled: PluginCatalogFeatureFlagResolver = isAssistantFeatureFlagEnabled,
|
|
34
|
+
): PluginCatalog {
|
|
35
|
+
const matches = catalog.matches.filter((match) =>
|
|
36
|
+
isPluginCatalogEntryVisible(match.name, isFeatureFlagEnabled),
|
|
37
|
+
);
|
|
38
|
+
return matches.length === catalog.matches.length
|
|
39
|
+
? catalog
|
|
40
|
+
: { ...catalog, matches };
|
|
41
|
+
}
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
findCatalogEntry,
|
|
47
47
|
resolveSourceFromMatch,
|
|
48
48
|
} from "./plugin-catalog-resolve.js";
|
|
49
|
+
import { isPluginCatalogEntryVisible } from "./plugin-catalog-visibility.js";
|
|
49
50
|
import { DEFAULT_PLUGIN_REF } from "./plugin-constants.js";
|
|
50
51
|
import { readValidatedPluginIcon } from "./plugin-icon-file.js";
|
|
51
52
|
import { fetchMarketplaceEntries } from "./plugin-marketplace.js";
|
|
@@ -156,8 +157,8 @@ export class PluginDetailsNotFoundError extends Error {
|
|
|
156
157
|
* Resolve the detail view for {@link opts.name}.
|
|
157
158
|
*
|
|
158
159
|
* Throws {@link PluginDetailsNotFoundError} when the name is neither installed
|
|
159
|
-
* locally nor present in the catalog
|
|
160
|
-
*
|
|
160
|
+
* locally nor present in the visibility-gated catalog at either the default
|
|
161
|
+
* or an explicit historical ref. A gated-catalog outage
|
|
161
162
|
* ({@link PluginCatalogUnavailableError}) degrades to the on-disk fields only
|
|
162
163
|
* when a local copy exists; with nothing installed to render it propagates so
|
|
163
164
|
* the caller can map the transient failure to a retryable 503 rather than a
|
|
@@ -400,11 +401,10 @@ async function fetchRawFile(
|
|
|
400
401
|
/**
|
|
401
402
|
* Resolve the external catalog entry claiming {@link name}.
|
|
402
403
|
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* history / inspect carve-out.
|
|
404
|
+
* Both default and historical-ref lookups apply catalog visibility first. The
|
|
405
|
+
* default ref then reads the gated catalog used by search and install, while an
|
|
406
|
+
* explicit historical {@link ref} reads the GitHub marketplace manifest at
|
|
407
|
+
* that revision.
|
|
408
408
|
*
|
|
409
409
|
* A gated-catalog outage (fail-hard {@link PluginCatalogUnavailableError})
|
|
410
410
|
* propagates when nothing is installed — there is nothing to render and the
|
|
@@ -420,6 +420,9 @@ async function resolveCatalogEntry(
|
|
|
420
420
|
fetchFn: FetchLike,
|
|
421
421
|
installed: boolean,
|
|
422
422
|
): Promise<PluginSearchMatch | null> {
|
|
423
|
+
if (!isPluginCatalogEntryVisible(name)) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
423
426
|
try {
|
|
424
427
|
const match =
|
|
425
428
|
ref === DEFAULT_PLUGIN_REF
|
|
@@ -19,6 +19,8 @@ import type { MarketplaceEntry } from "./plugin-marketplace.js";
|
|
|
19
19
|
export interface SearchPluginsDeps {
|
|
20
20
|
/** HTTP client. Production callers pass `globalThis.fetch.bind(globalThis)`. */
|
|
21
21
|
readonly fetch: FetchLike;
|
|
22
|
+
/** Optional feature-flag resolver for deterministic tests. */
|
|
23
|
+
readonly featureFlagEnabled?: (key: string) => boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/** Where a catalog match comes from. */
|
|
@@ -5,10 +5,12 @@ 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,
|
|
11
12
|
getUserSelectableProfilesForProvider,
|
|
13
|
+
MANAGED_PROFILE_TEMPLATES,
|
|
12
14
|
PROFILE_IMPLS,
|
|
13
15
|
resolveDefaultProfileForProvider,
|
|
14
16
|
} from "../default-profile-catalog.js";
|
|
@@ -59,6 +61,64 @@ describe("getEffectiveProfiles", () => {
|
|
|
59
61
|
}
|
|
60
62
|
});
|
|
61
63
|
|
|
64
|
+
test("the managed Jev profile routes jev-latest through TypeSafe and is managed-only", () => {
|
|
65
|
+
const jev = CODE_DEFAULT_PROFILE_ENTRIES["jev-managed"];
|
|
66
|
+
expect(jev.model).toBe("jev-latest");
|
|
67
|
+
expect(jev.provider_connection).toBeUndefined();
|
|
68
|
+
expect(resolveRoutingIdentity(jev.provider, jev.model)).toEqual({
|
|
69
|
+
connectionName: "vellum",
|
|
70
|
+
expectedProvider: "typesafe",
|
|
71
|
+
});
|
|
72
|
+
expect(
|
|
73
|
+
resolveDefaultProfileForProvider(undefined, "jev-managed", null),
|
|
74
|
+
).toBeDefined();
|
|
75
|
+
expect(
|
|
76
|
+
resolveDefaultProfileForProvider(undefined, "jev-managed", {
|
|
77
|
+
provider: "anthropic",
|
|
78
|
+
connectionName: "anthropic-personal",
|
|
79
|
+
}),
|
|
80
|
+
).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("the Auto profile is the managed Balanced body relabeled, managed-only, and flag-gated", () => {
|
|
84
|
+
const auto = CODE_DEFAULT_PROFILE_ENTRIES.auto;
|
|
85
|
+
const balanced = CODE_DEFAULT_PROFILE_ENTRIES.balanced;
|
|
86
|
+
expect(auto.label).toBe("Auto");
|
|
87
|
+
expect(auto.model).toBe(balanced.model);
|
|
88
|
+
expect(auto.provider).toBe(balanced.provider);
|
|
89
|
+
expect(auto.fallbackProfile).toBeUndefined();
|
|
90
|
+
// Not seeded unconditionally: the flag reconcile writes the stub.
|
|
91
|
+
expect(Object.keys(MANAGED_PROFILE_TEMPLATES)).not.toContain("auto");
|
|
92
|
+
expect(
|
|
93
|
+
resolveDefaultProfileForProvider(undefined, "auto", null),
|
|
94
|
+
).toBeUndefined();
|
|
95
|
+
const stub: Record<string, ProfileEntry> = { auto: { source: "managed" } };
|
|
96
|
+
expect(resolveDefaultProfileForProvider(stub, "auto", null)?.model).toBe(
|
|
97
|
+
balanced.model,
|
|
98
|
+
);
|
|
99
|
+
expect(
|
|
100
|
+
resolveDefaultProfileForProvider(stub, "auto", {
|
|
101
|
+
provider: "anthropic",
|
|
102
|
+
connectionName: "anthropic-personal",
|
|
103
|
+
}),
|
|
104
|
+
).toBeUndefined();
|
|
105
|
+
expect(
|
|
106
|
+
Object.keys(getConversationProfilesForProvider(stub, null)),
|
|
107
|
+
).toContain("auto");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("the conversation view drops the managed Jev profile but keeps it selectable for call sites", () => {
|
|
111
|
+
expect(
|
|
112
|
+
Object.keys(getConversationProfilesForProvider(undefined, null)),
|
|
113
|
+
).not.toContain("jev-managed");
|
|
114
|
+
expect(
|
|
115
|
+
Object.keys(getUserSelectableProfilesForProvider(undefined, null)),
|
|
116
|
+
).toContain("jev-managed");
|
|
117
|
+
expect(
|
|
118
|
+
Object.keys(getConversationProfilesForProvider(undefined, null)),
|
|
119
|
+
).toContain("balanced");
|
|
120
|
+
});
|
|
121
|
+
|
|
62
122
|
test("the managed Balanced profile routes GLM 5.3 Flash through Fireworks", () => {
|
|
63
123
|
const balanced = CODE_DEFAULT_PROFILE_ENTRIES.balanced;
|
|
64
124
|
expect(balanced.model).toBe("accounts/fireworks/models/glm-5p3-flash");
|
|
@@ -80,7 +140,7 @@ describe("getEffectiveProfiles", () => {
|
|
|
80
140
|
test("defaults absent from the workspace resolve from the catalog; os-beta stays flag-gated", () => {
|
|
81
141
|
const effective = getEffectiveProfiles(undefined);
|
|
82
142
|
expect(Object.keys(effective).sort()).toEqual(
|
|
83
|
-
[...DEFAULT_PROFILE_KEYS, ...BACKUP_PROFILE_KEYS].sort(),
|
|
143
|
+
[...DEFAULT_PROFILE_KEYS, ...BACKUP_PROFILE_KEYS, "jev-managed"].sort(),
|
|
84
144
|
);
|
|
85
145
|
expect(getEffectiveProfile({}, "balanced")?.model).toBe(
|
|
86
146
|
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
|
+
});
|
|
@@ -368,4 +368,75 @@ describe("reconcileFlagGatedProfiles", () => {
|
|
|
368
368
|
expect(reconcileFlagGatedProfiles()).toBe(false);
|
|
369
369
|
expect(readFileSync(CONFIG_PATH, "utf-8")).toBe(before);
|
|
370
370
|
});
|
|
371
|
+
|
|
372
|
+
test("auto-profile flag on materializes the managed auto profile at the head of the order", () => {
|
|
373
|
+
process.env.IS_PLATFORM = "true";
|
|
374
|
+
seedBalancedConfig();
|
|
375
|
+
setOverridesForTesting({ "auto-profile": true });
|
|
376
|
+
|
|
377
|
+
expect(reconcileFlagGatedProfiles()).toBe(true);
|
|
378
|
+
|
|
379
|
+
const raw = readConfig();
|
|
380
|
+
expect(raw.llm.profiles.auto).toEqual({ source: "managed" });
|
|
381
|
+
expect(raw.llm.profileOrder[0]).toBe("auto");
|
|
382
|
+
const effective = getEffectiveProfile(raw.llm.profiles, "auto")!;
|
|
383
|
+
expect(effective.label).toBe("Auto");
|
|
384
|
+
expect(effective.model).toBe(
|
|
385
|
+
getEffectiveProfile(raw.llm.profiles, "balanced")!.model,
|
|
386
|
+
);
|
|
387
|
+
expect(LLMSchema.safeParse(raw.llm).success).toBe(true);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("auto-profile flag on in BYOK mode writes a plain stub that the effective view hides", () => {
|
|
391
|
+
seedBalancedConfig();
|
|
392
|
+
setOverridesForTesting({ "auto-profile": true });
|
|
393
|
+
|
|
394
|
+
expect(reconcileFlagGatedProfiles()).toBe(true);
|
|
395
|
+
|
|
396
|
+
const raw = readConfig();
|
|
397
|
+
expect(raw.llm.profiles.auto).toEqual({ source: "managed" });
|
|
398
|
+
expect(getEffectiveProfile(raw.llm.profiles, "auto")).toBeDefined();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("auto-profile flag off removes the managed auto profile and repoints its references", () => {
|
|
402
|
+
process.env.IS_PLATFORM = "true";
|
|
403
|
+
seedBalancedConfig();
|
|
404
|
+
setOverridesForTesting({ "auto-profile": true });
|
|
405
|
+
reconcileFlagGatedProfiles();
|
|
406
|
+
|
|
407
|
+
const raw = readConfig();
|
|
408
|
+
raw.llm.activeProfile = "auto";
|
|
409
|
+
(raw.llm as Record<string, unknown>).callSites = {
|
|
410
|
+
subagentSpawn: { profile: "auto" },
|
|
411
|
+
};
|
|
412
|
+
writeConfig(raw);
|
|
413
|
+
invalidateConfigCache();
|
|
414
|
+
|
|
415
|
+
setOverridesForTesting({ "auto-profile": false });
|
|
416
|
+
expect(reconcileFlagGatedProfiles()).toBe(true);
|
|
417
|
+
|
|
418
|
+
const after = readConfig();
|
|
419
|
+
expect(after.llm.profiles.auto).toBeUndefined();
|
|
420
|
+
expect(after.llm.profileOrder.includes("auto")).toBe(false);
|
|
421
|
+
expect(after.llm.activeProfile).toBe("balanced");
|
|
422
|
+
expect((after.llm as Record<string, unknown>).callSites).toEqual({
|
|
423
|
+
subagentSpawn: {},
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("os-beta and auto-profile reconcile independently in one pass", () => {
|
|
428
|
+
process.env.IS_PLATFORM = "true";
|
|
429
|
+
seedBalancedConfig();
|
|
430
|
+
setOverridesForTesting({ "os-beta": true, "auto-profile": true });
|
|
431
|
+
expect(reconcileFlagGatedProfiles()).toBe(true);
|
|
432
|
+
let order = readConfig().llm.profileOrder;
|
|
433
|
+
expect(order[0]).toBe("auto");
|
|
434
|
+
expect(order.indexOf("os-beta")).toBe(order.indexOf("balanced") + 1);
|
|
435
|
+
|
|
436
|
+
setOverridesForTesting({ "os-beta": false, "auto-profile": true });
|
|
437
|
+
expect(reconcileFlagGatedProfiles()).toBe(true);
|
|
438
|
+
order = readConfig().llm.profileOrder;
|
|
439
|
+
expect(order[0]).toBe("auto");
|
|
440
|
+
expect(order.includes("os-beta")).toBe(false);
|
|
441
|
+
});
|
|
371
442
|
});
|
|
@@ -46,9 +46,12 @@ nothing, because no control is called that. Give the label on its own:
|
|
|
46
46
|
The arrow points at the middle of the control and stops just short, so what
|
|
47
47
|
you are sending someone to stays visible the whole time.
|
|
48
48
|
|
|
49
|
-
You are answered with what was drawn and the name it resolved
|
|
50
|
-
|
|
51
|
-
the
|
|
49
|
+
You are answered with what was drawn and the accessibility name it resolved
|
|
50
|
+
to. Some apps expose internal names such as `ToolbarExportControl` instead of
|
|
51
|
+
the visible label. When a returned name identifies the intended control in
|
|
52
|
+
the shared image, use that exact name as `target`; use the visible label in
|
|
53
|
+
the caption and speech. Do not invent an internal name or choose an unclear
|
|
54
|
+
candidate.
|
|
52
55
|
|
|
53
56
|
A name the surface does not carry draws nothing and comes back with the names
|
|
54
57
|
it does carry. That is the answer, not a setback: the thing is nearly always
|
|
@@ -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,20 @@ 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
|
-
//
|
|
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
|
+
// Picks the default profile for a turn pinned to the Auto profile. On a
|
|
163
|
+
// BYOK install the managed-only Jev profile has no body, so the router is
|
|
164
|
+
// unavailable and the turn runs on Balanced.
|
|
165
|
+
autoProfileRouter: {
|
|
166
|
+
profile: JEV_MANAGED_PROFILE_KEY,
|
|
167
|
+
},
|
|
168
|
+
// The barge-in continuation judge remains off until a TypeSafe profile is
|
|
169
|
+
// pinned here (see live-voice/continuation-judge.ts).
|
|
164
170
|
voiceContinuationJudge: { profile: "cost-optimized" },
|
|
165
171
|
// Names the background continuation a barge-in spawns, from the interrupted
|
|
166
172
|
// transcript. The label is fixed at spawn, so the call runs under a short
|
|
@@ -8,6 +8,7 @@ import { isCodexSubscriptionModel } from "../providers/openai/codex-models.js";
|
|
|
8
8
|
import type { ModelIntent } from "../providers/types.js";
|
|
9
9
|
import { getManagedUpstream } from "../providers/vellum-model-routing.js";
|
|
10
10
|
import {
|
|
11
|
+
AUTO_PROFILE_KEY,
|
|
11
12
|
BACKUP_PROFILE_KEYS,
|
|
12
13
|
type BackupProfileKey,
|
|
13
14
|
DEFAULT_PROFILE_KEYS,
|
|
@@ -15,12 +16,15 @@ import {
|
|
|
15
16
|
type DefaultProfileKey,
|
|
16
17
|
type DefaultProfileProvider,
|
|
17
18
|
FALLBACK_PROFILE_BY_KEY,
|
|
18
|
-
isBackupProfileKey,
|
|
19
19
|
isDefaultProfileKey,
|
|
20
20
|
isDefaultProfileProvider,
|
|
21
|
+
isFlagGatedProfileKey,
|
|
22
|
+
isManagedOnlyProfileKey,
|
|
23
|
+
JEV_MANAGED_PROFILE_KEY,
|
|
21
24
|
OS_BETA_PROFILE_KEY,
|
|
22
25
|
} from "./default-profile-names.js";
|
|
23
26
|
import { resolveDefaultConnectionName } from "./default-provider-resolution.js";
|
|
27
|
+
import { profileSupportsTextGeneration } from "./profile-text-generation.js";
|
|
24
28
|
import {
|
|
25
29
|
backupProfilesResolveUnderDefaultProvider,
|
|
26
30
|
DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
|
|
@@ -385,18 +389,60 @@ export const PROFILE_IMPLS: Record<
|
|
|
385
389
|
Record<DefaultProfileProvider, DefaultProfileTemplate>
|
|
386
390
|
>;
|
|
387
391
|
|
|
392
|
+
/**
|
|
393
|
+
* The managed Jev profile: TypeSafe's System One decision model through the
|
|
394
|
+
* platform proxy. Managed column only, because the platform is the only
|
|
395
|
+
* route the catalog offers for it here, and never a conversation profile,
|
|
396
|
+
* because `jev-latest` returns structured answers rather than chat text
|
|
397
|
+
* (`supportsText: false` on the `typesafe` catalog entry). Its budget is
|
|
398
|
+
* TypeSafe's published request limit rather than the default context window.
|
|
399
|
+
*/
|
|
400
|
+
export const JEV_MANAGED_PROFILE_TEMPLATE: DefaultProfileTemplate = {
|
|
401
|
+
model: "jev-latest",
|
|
402
|
+
provider: "vellum",
|
|
403
|
+
source: "managed",
|
|
404
|
+
label: "Jev",
|
|
405
|
+
description:
|
|
406
|
+
"TypeSafe decision model for yes/no verdicts, for the voice judges and memory selection",
|
|
407
|
+
maxTokens: 4096,
|
|
408
|
+
thinking: { enabled: false, streamThinking: false },
|
|
409
|
+
contextWindow: { maxInputTokens: 32_000 },
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* The Auto profile: the managed Balanced body under its own label, so that
|
|
414
|
+
* anything dispatching the name directly runs Balanced. The per-message
|
|
415
|
+
* choice among the defaults happens in the agent loop, not here (see
|
|
416
|
+
* `AUTO_PROFILE_KEY`). No `fallbackProfile`: the code-owned fallback mapping
|
|
417
|
+
* is keyed by default profile, and a direct dispatch of this name is already
|
|
418
|
+
* the fallback path. Flag-gated like `os-beta`: NOT in
|
|
419
|
+
* `MANAGED_PROFILE_TEMPLATES`, so the unconditional boot seed never creates
|
|
420
|
+
* it; the flag-gated profile reconcile materializes it while the
|
|
421
|
+
* `auto-profile` feature flag is on.
|
|
422
|
+
*/
|
|
423
|
+
export const AUTO_PROFILE_TEMPLATE: DefaultProfileTemplate = (() => {
|
|
424
|
+
const { fallbackProfile: _fallbackProfile, ...balanced } =
|
|
425
|
+
VELLUM_PROFILE_IMPLS.balanced;
|
|
426
|
+
return {
|
|
427
|
+
...balanced,
|
|
428
|
+
label: "Auto",
|
|
429
|
+
description: "Picks the profile that fits each message, in beta",
|
|
430
|
+
};
|
|
431
|
+
})();
|
|
432
|
+
|
|
388
433
|
/**
|
|
389
434
|
* Managed profiles, i.e. the `vellum` column keyed by profile name, plus the
|
|
390
|
-
* managed backup profiles. Backups come after the
|
|
391
|
-
* places them after the primaries in the seeded
|
|
392
|
-
* inserts missing managed keys in this record's
|
|
393
|
-
* user-facing defaults only: an internal profile is
|
|
394
|
-
* listed or ordered.
|
|
435
|
+
* managed backup profiles and the managed Jev profile. Backups come after the
|
|
436
|
+
* primaries, which is what places them after the primaries in the seeded
|
|
437
|
+
* `profileOrder`: the seeder inserts missing managed keys in this record's
|
|
438
|
+
* order. Keyed by the user-facing defaults only: an internal profile is
|
|
439
|
+
* code-resolved and never listed or ordered.
|
|
395
440
|
*/
|
|
396
441
|
export const MANAGED_PROFILE_TEMPLATES: Record<string, DefaultProfileTemplate> =
|
|
397
442
|
Object.fromEntries([
|
|
398
443
|
...DEFAULT_PROFILE_KEYS.map((key) => [key, PROFILE_IMPLS[key].vellum]),
|
|
399
444
|
...BACKUP_PROFILE_KEYS.map((key) => [key, BACKUP_PROFILE_IMPLS[key]]),
|
|
445
|
+
[JEV_MANAGED_PROFILE_KEY, JEV_MANAGED_PROFILE_TEMPLATE],
|
|
400
446
|
]);
|
|
401
447
|
|
|
402
448
|
/**
|
|
@@ -484,6 +530,9 @@ export const OS_BETA_PROFILE_TEMPLATE: DefaultProfileTemplate = {
|
|
|
484
530
|
export const CODE_OWNED_PROFILE_NAMES = new Set<string>([
|
|
485
531
|
"latency-optimized",
|
|
486
532
|
...BACKUP_PROFILE_KEYS,
|
|
533
|
+
// A shadow could repoint the judges at a chat model, which answers a
|
|
534
|
+
// yes/no question with prose the verdict parser cannot read.
|
|
535
|
+
JEV_MANAGED_PROFILE_KEY,
|
|
487
536
|
]);
|
|
488
537
|
|
|
489
538
|
// All managed profiles, including the flag-gated os-beta, are invariant:
|
|
@@ -494,6 +543,8 @@ export const CODE_OWNED_PROFILE_NAMES = new Set<string>([
|
|
|
494
543
|
export const INVARIANT_PROFILE_NAMES = new Set<string>([
|
|
495
544
|
...DEFAULT_PROFILE_KEYS,
|
|
496
545
|
...BACKUP_PROFILE_KEYS,
|
|
546
|
+
JEV_MANAGED_PROFILE_KEY,
|
|
547
|
+
AUTO_PROFILE_KEY,
|
|
497
548
|
OS_BETA_PROFILE_KEY,
|
|
498
549
|
]);
|
|
499
550
|
|
|
@@ -507,6 +558,8 @@ export const INVARIANT_PROFILE_NAMES = new Set<string>([
|
|
|
507
558
|
export const MANAGED_PROFILE_NAMES = new Set<string>([
|
|
508
559
|
...DEFAULT_PROFILE_KEYS,
|
|
509
560
|
...BACKUP_PROFILE_KEYS,
|
|
561
|
+
JEV_MANAGED_PROFILE_KEY,
|
|
562
|
+
AUTO_PROFILE_KEY,
|
|
510
563
|
OS_BETA_PROFILE_KEY,
|
|
511
564
|
]);
|
|
512
565
|
|
|
@@ -590,6 +643,19 @@ for (const key of BACKUP_PROFILE_KEYS) {
|
|
|
590
643
|
}
|
|
591
644
|
}
|
|
592
645
|
|
|
646
|
+
// The managed Jev profile pins a model the platform proxy must front, and the
|
|
647
|
+
// judges depend on it being the non-text TypeSafe model rather than a chat
|
|
648
|
+
// model that happens to share the id.
|
|
649
|
+
if (
|
|
650
|
+
getManagedUpstream(JEV_MANAGED_PROFILE_TEMPLATE.model ?? "") !== "typesafe"
|
|
651
|
+
) {
|
|
652
|
+
throw new Error(
|
|
653
|
+
`JEV_MANAGED_PROFILE_TEMPLATE references model "${JEV_MANAGED_PROFILE_TEMPLATE.model ?? ""}" ` +
|
|
654
|
+
`which the managed route does not serve through typesafe. ` +
|
|
655
|
+
`Update model-catalog.ts, platform-proxy/constants.ts, or default-profile-catalog.ts.`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
593
659
|
// Provider choices without a named column materialize from the shared BYOK
|
|
594
660
|
// templates; verify each one's resolved model lands in the catalog.
|
|
595
661
|
for (const provider of DEFAULT_PROVIDER_CHOICES) {
|
|
@@ -621,6 +687,14 @@ function buildDefaultProfileEntries(): Record<string, ProfileEntry> {
|
|
|
621
687
|
const impl = BACKUP_PROFILE_IMPLS[key];
|
|
622
688
|
entries[key] = materializeProfile(impl, impl.provider);
|
|
623
689
|
}
|
|
690
|
+
entries[JEV_MANAGED_PROFILE_KEY] = materializeProfile(
|
|
691
|
+
JEV_MANAGED_PROFILE_TEMPLATE,
|
|
692
|
+
JEV_MANAGED_PROFILE_TEMPLATE.provider,
|
|
693
|
+
);
|
|
694
|
+
entries[AUTO_PROFILE_KEY] = materializeProfile(
|
|
695
|
+
AUTO_PROFILE_TEMPLATE,
|
|
696
|
+
AUTO_PROFILE_TEMPLATE.provider,
|
|
697
|
+
);
|
|
624
698
|
entries[OS_BETA_PROFILE_KEY] = materializeProfile(
|
|
625
699
|
OS_BETA_PROFILE_TEMPLATE,
|
|
626
700
|
OS_BETA_PROFILE_TEMPLATE.provider,
|
|
@@ -697,12 +771,13 @@ function resolveAgainstBody(
|
|
|
697
771
|
if (body == null) {
|
|
698
772
|
// A managed stub is the workspace's slot for a code-owned profile, never
|
|
699
773
|
// a profile of its own, so with no body behind it there is nothing to
|
|
700
|
-
// resolve. Only a
|
|
701
|
-
// default keys always have a body), and it
|
|
702
|
-
//
|
|
703
|
-
// profile with no provider or model and let a
|
|
704
|
-
// valid, which is precisely what `LLMSchema` rejects
|
|
705
|
-
|
|
774
|
+
// resolve. Only a managed-only name (a backup, the Jev profile) reaches
|
|
775
|
+
// this branch with a stub (the default keys always have a body), and it
|
|
776
|
+
// reaches it on the columns where those profiles do not exist. Resolving
|
|
777
|
+
// to the stub would list a profile with no provider or model and let a
|
|
778
|
+
// reference to it look valid, which is precisely what `LLMSchema` rejects
|
|
779
|
+
// on those columns.
|
|
780
|
+
if (isManagedOnlyProfileKey(name) && workspace?.source === "managed") {
|
|
706
781
|
return undefined;
|
|
707
782
|
}
|
|
708
783
|
return workspace;
|
|
@@ -711,7 +786,7 @@ function resolveAgainstBody(
|
|
|
711
786
|
return { ...body };
|
|
712
787
|
}
|
|
713
788
|
if (workspace == null) {
|
|
714
|
-
return name
|
|
789
|
+
return isFlagGatedProfileKey(name) ? undefined : { ...body };
|
|
715
790
|
}
|
|
716
791
|
if (workspace.source !== "managed") {
|
|
717
792
|
return workspace;
|
|
@@ -787,14 +862,14 @@ function defaultProfileBodyForProvider(
|
|
|
787
862
|
defaultProvider: DefaultProviderConfig | null,
|
|
788
863
|
): ProfileEntry | undefined {
|
|
789
864
|
if (!isDefaultProfileKey(name)) {
|
|
790
|
-
// Backup profiles
|
|
791
|
-
// under a BYOK or chatgpt default provider the
|
|
792
|
-
// `fallbackProfile` pointers, and
|
|
793
|
-
//
|
|
865
|
+
// Backup profiles and the managed Jev profile exist on the managed
|
|
866
|
+
// (`vellum`) column only: under a BYOK or chatgpt default provider the
|
|
867
|
+
// primaries carry no `fallbackProfile` pointers, and neither may
|
|
868
|
+
// materialize, since the install may hold no credential for their
|
|
794
869
|
// provider. A null defaultProvider predates `llm.defaultProvider` and
|
|
795
|
-
// resolves to the managed column, so it keeps
|
|
870
|
+
// resolves to the managed column, so it keeps them.
|
|
796
871
|
if (
|
|
797
|
-
|
|
872
|
+
isManagedOnlyProfileKey(name) &&
|
|
798
873
|
!backupProfilesResolveUnderDefaultProvider(defaultProvider)
|
|
799
874
|
) {
|
|
800
875
|
return undefined;
|
|
@@ -923,3 +998,27 @@ export function getUserSelectableProfilesForProvider(
|
|
|
923
998
|
}
|
|
924
999
|
return selectable;
|
|
925
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* The profiles a conversation can run on: the user-selectable view minus any
|
|
1004
|
+
* profile whose model returns structured answers rather than chat text (the
|
|
1005
|
+
* managed Jev profile). Every surface that picks or validates a conversation
|
|
1006
|
+
* profile (the `/model` command, a per-conversation pin, the plugin and
|
|
1007
|
+
* workflow listings) reads this view; the call-site override pickers keep
|
|
1008
|
+
* the wider selectable view, since the verdict sites are what Jev is for.
|
|
1009
|
+
*/
|
|
1010
|
+
export function getConversationProfilesForProvider(
|
|
1011
|
+
workspaceProfiles: Record<string, ProfileEntry> | undefined,
|
|
1012
|
+
defaultProvider: DefaultProviderConfig | null,
|
|
1013
|
+
): Record<string, ProfileEntry> {
|
|
1014
|
+
const selectable = getUserSelectableProfilesForProvider(
|
|
1015
|
+
workspaceProfiles,
|
|
1016
|
+
defaultProvider,
|
|
1017
|
+
);
|
|
1018
|
+
for (const [name, entry] of Object.entries(selectable)) {
|
|
1019
|
+
if (!profileSupportsTextGeneration(entry, selectable)) {
|
|
1020
|
+
delete selectable[name];
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return selectable;
|
|
1024
|
+
}
|