@vellumai/assistant 0.12.4-staging.1 → 0.12.4-staging.3
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__/openai-responses-prompt-cache.test.ts +3 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
- package/src/__tests__/pricing.test.ts +33 -0
- package/src/__tests__/skill-load-tool.test.ts +42 -11
- package/src/__tests__/skill-tool-factory.test.ts +50 -2
- 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/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/plugins/defaults/memory/substrate/skill-content.ts +1 -1
- 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/anthropic/client.ts +2 -2
- package/src/providers/model-catalog.ts +257 -4
- package/src/providers/openai/codex-models.ts +2 -0
- 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/skills/validate-input.ts +75 -11
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/format-input-schema.ts +15 -0
- package/src/tools/skills/load.ts +2 -26
- package/src/tools/skills/skill-tool-factory.ts +2 -1
- 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
|
@@ -14,12 +14,14 @@ import {
|
|
|
14
14
|
parseVellumModel,
|
|
15
15
|
} from "../../providers/vellum-model-routing.js";
|
|
16
16
|
import {
|
|
17
|
-
BACKUP_PROFILE_KEYS,
|
|
18
17
|
DEFAULT_PROFILE_KEYS,
|
|
19
18
|
DEFAULT_PROFILE_PROVIDERS,
|
|
20
19
|
FALLBACK_PROFILE_BY_KEY,
|
|
21
|
-
isBackupProfileKey,
|
|
22
20
|
isDefaultProfileKey,
|
|
21
|
+
isManagedOnlyProfileKey,
|
|
22
|
+
JEV_MANAGED_PROFILE_CALL_SITES,
|
|
23
|
+
JEV_MANAGED_PROFILE_KEY,
|
|
24
|
+
MANAGED_ONLY_PROFILE_KEYS,
|
|
23
25
|
} from "../default-profile-names.js";
|
|
24
26
|
import { InputModalitiesSchema } from "../input-modalities.js";
|
|
25
27
|
|
|
@@ -766,10 +768,12 @@ function unresolvableProfileReason(
|
|
|
766
768
|
name: string,
|
|
767
769
|
backupsResolve: boolean,
|
|
768
770
|
): string {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
771
|
+
if (backupsResolve || !isManagedOnlyProfileKey(name)) {
|
|
772
|
+
return "is not defined in llm.profiles";
|
|
773
|
+
}
|
|
774
|
+
return name === JEV_MANAGED_PROFILE_KEY
|
|
775
|
+
? "is the managed Jev profile, which resolves only while llm.defaultProvider is the managed provider"
|
|
776
|
+
: "is a managed backup profile, which resolves only while llm.defaultProvider is the managed provider";
|
|
773
777
|
}
|
|
774
778
|
|
|
775
779
|
/**
|
|
@@ -800,7 +804,7 @@ function referenceableProfileKeys(
|
|
|
800
804
|
): string[] {
|
|
801
805
|
return Object.entries(profiles ?? {})
|
|
802
806
|
.filter(([name, value]) => {
|
|
803
|
-
if (backupsResolve || !
|
|
807
|
+
if (backupsResolve || !isManagedOnlyProfileKey(name)) {
|
|
804
808
|
return true;
|
|
805
809
|
}
|
|
806
810
|
const entry =
|
|
@@ -854,7 +858,7 @@ export function collectFallbackProfileIssues(
|
|
|
854
858
|
const profileNames = new Set([
|
|
855
859
|
...referenceableProfileKeys(profiles, backupsResolve),
|
|
856
860
|
...DEFAULT_PROFILE_KEYS,
|
|
857
|
-
...(backupsResolve ?
|
|
861
|
+
...(backupsResolve ? MANAGED_ONLY_PROFILE_KEYS : []),
|
|
858
862
|
]);
|
|
859
863
|
const mixProfileNames = new Set(
|
|
860
864
|
entries
|
|
@@ -1004,6 +1008,8 @@ export const LLMSchema = z
|
|
|
1004
1008
|
// they join the set only under a managed `llm.defaultProvider`: on a BYOK
|
|
1005
1009
|
// or ChatGPT default provider they have no body to resolve to, and
|
|
1006
1010
|
// keeping the reference would strand a selection the picker cannot show.
|
|
1011
|
+
// The managed Jev profile (`JEV_MANAGED_PROFILE_KEY`) is scoped the same
|
|
1012
|
+
// way, as a pin target for the judge call sites.
|
|
1007
1013
|
// The flag-gated `os-beta` is excluded: it resolves only while a
|
|
1008
1014
|
// workspace entry exists, so a reference to it is valid only when that
|
|
1009
1015
|
// entry is present in `config.profiles`. A backup name materialized as a
|
|
@@ -1018,9 +1024,23 @@ export const LLMSchema = z
|
|
|
1018
1024
|
backupsResolve,
|
|
1019
1025
|
),
|
|
1020
1026
|
...DEFAULT_PROFILE_KEYS,
|
|
1021
|
-
...(backupsResolve ?
|
|
1027
|
+
...(backupsResolve ? MANAGED_ONLY_PROFILE_KEYS : []),
|
|
1022
1028
|
]);
|
|
1023
1029
|
for (const [siteId, siteConfig] of Object.entries(config.callSites ?? {})) {
|
|
1030
|
+
// The Jev profile answers with structured verdicts, so only the sites
|
|
1031
|
+
// built to read one may pin it; a text-producing site would receive
|
|
1032
|
+
// output it cannot use. (Mix arms are covered by the profile-level
|
|
1033
|
+
// mix validation below, which bars the profile from any mix.)
|
|
1034
|
+
if (
|
|
1035
|
+
siteConfig?.profile === JEV_MANAGED_PROFILE_KEY &&
|
|
1036
|
+
!(JEV_MANAGED_PROFILE_CALL_SITES as readonly string[]).includes(siteId)
|
|
1037
|
+
) {
|
|
1038
|
+
ctx.addIssue({
|
|
1039
|
+
code: "custom",
|
|
1040
|
+
path: ["callSites", siteId, "profile"],
|
|
1041
|
+
message: `Profile "${JEV_MANAGED_PROFILE_KEY}" returns structured answers rather than chat text, so it can only be pinned to ${JEV_MANAGED_PROFILE_CALL_SITES.join(", ")}`,
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1024
1044
|
if (siteConfig?.profile == null) {
|
|
1025
1045
|
continue;
|
|
1026
1046
|
}
|
|
@@ -1052,6 +1072,20 @@ export const LLMSchema = z
|
|
|
1052
1072
|
message: `Profile "${config.advisorProfile}" referenced by llm.advisorProfile ${unresolvableProfileReason(config.advisorProfile, backupsResolve)}`,
|
|
1053
1073
|
});
|
|
1054
1074
|
}
|
|
1075
|
+
// The managed Jev profile is a valid reference target for call-site pins
|
|
1076
|
+
// only. Its model returns structured verdicts rather than chat text, so
|
|
1077
|
+
// the conversation positions reject it here as well as at the write
|
|
1078
|
+
// routes, which keeps a raw config write from handing the main agent or
|
|
1079
|
+
// the advisor a model that cannot answer in prose.
|
|
1080
|
+
for (const field of ["activeProfile", "advisorProfile"] as const) {
|
|
1081
|
+
if (config[field] === JEV_MANAGED_PROFILE_KEY) {
|
|
1082
|
+
ctx.addIssue({
|
|
1083
|
+
code: "custom",
|
|
1084
|
+
path: [field],
|
|
1085
|
+
message: `Profile "${JEV_MANAGED_PROFILE_KEY}" referenced by llm.${field} returns structured answers rather than chat text, so it cannot be the conversation or advisor profile`,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1055
1089
|
|
|
1056
1090
|
// --- Mix profile validation --------------------------------------------
|
|
1057
1091
|
// Config keys a mix profile must NOT also set (a mix only references other
|
|
@@ -1105,6 +1139,17 @@ export const LLMSchema = z
|
|
|
1105
1139
|
});
|
|
1106
1140
|
continue;
|
|
1107
1141
|
}
|
|
1142
|
+
// A mix is a conversation profile, so an arm that returns structured
|
|
1143
|
+
// verdicts rather than text would hand a chat turn to the decision
|
|
1144
|
+
// model whenever the seeded pick lands on it.
|
|
1145
|
+
if (arm.profile === JEV_MANAGED_PROFILE_KEY) {
|
|
1146
|
+
ctx.addIssue({
|
|
1147
|
+
code: "custom",
|
|
1148
|
+
path: ["profiles", name, "mix", index, "profile"],
|
|
1149
|
+
message: `Mix profile "${name}" references "${JEV_MANAGED_PROFILE_KEY}", which returns structured answers rather than chat text and cannot be a mix constituent.`,
|
|
1150
|
+
});
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1108
1153
|
// (b) No nesting — a mix arm must be a standard (non-mix) profile.
|
|
1109
1154
|
if (mixProfileNames.has(arm.profile)) {
|
|
1110
1155
|
ctx.addIssue({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InterfaceId } from "../channels/types.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getConversationProfilesForProvider } from "../config/default-profile-catalog.js";
|
|
3
3
|
import { resolveEffectiveContextWindow } from "../config/llm-context-resolution.js";
|
|
4
4
|
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
|
|
5
5
|
import {
|
|
@@ -148,7 +148,7 @@ async function resolveModelCommand(
|
|
|
148
148
|
parse: ModelCommandParse,
|
|
149
149
|
): Promise<SlashResolution> {
|
|
150
150
|
const config = getConfig();
|
|
151
|
-
const profiles =
|
|
151
|
+
const profiles = getConversationProfilesForProvider(
|
|
152
152
|
config.llm.profiles,
|
|
153
153
|
config.llm.defaultProvider ?? null,
|
|
154
154
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure text-processing functions for dictation profiles.
|
|
3
3
|
*
|
|
4
|
-
* - expandSnippets:
|
|
5
|
-
* - applyDictionary:
|
|
4
|
+
* - expandSnippets: dictation mode only
|
|
5
|
+
* - applyDictionary: dictation and selected-text edits (including fallback paths)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type {
|
|
@@ -33,7 +33,7 @@ function wrapWordBoundary(
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Expand snippet triggers in text
|
|
36
|
+
* Expand user-configured snippet triggers in dictated text.
|
|
37
37
|
*
|
|
38
38
|
* - Only enabled snippets are considered
|
|
39
39
|
* - Sorted by trigger length descending (longest match wins)
|
|
@@ -80,7 +80,7 @@ export function expandSnippets(
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Apply dictionary
|
|
83
|
+
* Apply user-configured dictionary replacements to dictated or edited text.
|
|
84
84
|
*
|
|
85
85
|
* - Sorted by spoken length descending
|
|
86
86
|
* - Respects wholeWord (default true) and caseSensitive (default false) per entry
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getConversationProfilesForProvider } from "../config/default-profile-catalog.js";
|
|
2
2
|
import { getConfig } from "../config/loader.js";
|
|
3
3
|
import { isDispatchableProfile } from "../config/profile-dispatchability.js";
|
|
4
4
|
import { orderProfileKeys } from "../config/profile-order.js";
|
|
@@ -7,7 +7,9 @@ import type { ModelProfileInfo } from "./types.js";
|
|
|
7
7
|
/**
|
|
8
8
|
* List the workspace inference profiles a plugin can route to, in the order a
|
|
9
9
|
* user-facing model picker presents them (`llm.profileOrder` first, then the
|
|
10
|
-
* rest alphabetically). Managed backup routes
|
|
10
|
+
* rest alphabetically). Managed backup routes and the managed Jev profile
|
|
11
|
+
* are omitted with the rest of the conversation view: a plugin routes
|
|
12
|
+
* conversation turns, and a decision model cannot answer them.
|
|
11
13
|
* Metadata-only entries without a provider, model, or mix are not routing
|
|
12
14
|
* targets, so plugins never see them. Disabled profiles are included and flagged via
|
|
13
15
|
* {@link ModelProfileInfo.isDisabled}; weighted "mix" profiles are included and
|
|
@@ -20,7 +22,7 @@ import type { ModelProfileInfo } from "./types.js";
|
|
|
20
22
|
export function getModelProfiles(): ModelProfileInfo[] {
|
|
21
23
|
const { llm } = getConfig();
|
|
22
24
|
const { activeProfile } = llm;
|
|
23
|
-
const profiles =
|
|
25
|
+
const profiles = getConversationProfilesForProvider(
|
|
24
26
|
llm.profiles,
|
|
25
27
|
llm.defaultProvider ?? null,
|
|
26
28
|
);
|
|
@@ -34,7 +34,7 @@ export const DEFAULT_CARD_CHARS = 500;
|
|
|
34
34
|
* only, so a missing product is not evidence it is unsupported.
|
|
35
35
|
*/
|
|
36
36
|
export const SKILLS_INJECTION_CATALOG_HINT =
|
|
37
|
-
"Injected skills are only ones currently in the workspace. Run `assistant plugins search <name>` and `assistant skills search <name>` before concluding a given integration or skill is unsupported";
|
|
37
|
+
"These skill cards describe availability, not tool instructions. When skill_load is available, use it to load a skill's current instructions and input schemas before calling its tools; follow those schemas over remembered arguments. Injected skills are only ones currently in the workspace. Run `assistant plugins search <name>` and `assistant skills search <name>` before concluding a given integration or skill is unsupported";
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Render the prose-style capability statement embedded into the unified
|
|
@@ -31,6 +31,9 @@ describe("renderInjectionBlockInner", () => {
|
|
|
31
31
|
expect(inner).toContain("assistant plugins search <name>");
|
|
32
32
|
expect(inner).toContain("assistant skills search <name>");
|
|
33
33
|
expect(inner).toContain("currently in the workspace");
|
|
34
|
+
expect(inner).toContain("When skill_load is available, use it");
|
|
35
|
+
expect(inner).toContain("before calling its tools");
|
|
36
|
+
expect(inner).toContain("over remembered arguments");
|
|
34
37
|
expect(inner.indexOf("# Skills")).toBeLessThan(inner.indexOf("# Skill:"));
|
|
35
38
|
expect(inner.startsWith(`${V3_INJECTION_HEADER}\n\n# Skills\n`)).toBe(true);
|
|
36
39
|
});
|
|
@@ -57,6 +57,8 @@ describe("isConnectionCompatibleWithModel", () => {
|
|
|
57
57
|
test("oauth_subscription connection is compatible with a Codex model", () => {
|
|
58
58
|
const conn = { auth: oauthAuth };
|
|
59
59
|
expect(isConnectionCompatibleWithModel(conn, "gpt-6-astra")).toBe(true);
|
|
60
|
+
expect(isConnectionCompatibleWithModel(conn, "gpt-6-sol")).toBe(true);
|
|
61
|
+
expect(isConnectionCompatibleWithModel(conn, "gpt-6-luna")).toBe(true);
|
|
60
62
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-sol")).toBe(true);
|
|
61
63
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-terra")).toBe(true);
|
|
62
64
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-luna")).toBe(true);
|
|
@@ -1004,7 +1004,7 @@ export class AnthropicProvider implements Provider {
|
|
|
1004
1004
|
const isHaiku = effectiveModel.includes("haiku");
|
|
1005
1005
|
// Effort support is per-model: Haiku and Sonnet 4.5 reject the param (see isEffortSupported).
|
|
1006
1006
|
const supportsEffort = isEffortSupported(effectiveModel);
|
|
1007
|
-
// opus-4-7 / opus-4-8 / opus-5 and sonnet-5 reject `temperature`,
|
|
1007
|
+
// opus-4-7 / opus-4-8 / opus-5 / opus-5-5 and sonnet-5 reject `temperature`,
|
|
1008
1008
|
// `top_p`, and `top_k` with a 400 "`temperature`/`top_p` is deprecated
|
|
1009
1009
|
// for this model" — model-wide, not effort-conditional (verified
|
|
1010
1010
|
// 2026-06-23). opus-4-6 / sonnet-4-6 / haiku-4-5 still accept them.
|
|
@@ -1016,7 +1016,7 @@ export class AnthropicProvider implements Provider {
|
|
|
1016
1016
|
// suffix is what matches.
|
|
1017
1017
|
const deprecatesSamplingParams =
|
|
1018
1018
|
/claude-opus-4-[78]\b/.test(effectiveModel) ||
|
|
1019
|
-
/claude-opus-5
|
|
1019
|
+
/claude-opus-5(-5)?\b/.test(effectiveModel) ||
|
|
1020
1020
|
/claude-sonnet-5\b/.test(effectiveModel) ||
|
|
1021
1021
|
/claude-fable-/.test(effectiveModel);
|
|
1022
1022
|
const mergedOutputConfig = {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { PLATFORM_PROVIDER_META } from "./platform-proxy/constants.js";
|
|
2
2
|
|
|
3
3
|
export type LongContextMode =
|
|
4
|
-
"native-model"
|
|
4
|
+
| "native-model"
|
|
5
|
+
| "provider-request-option"
|
|
6
|
+
| "unsupported";
|
|
5
7
|
|
|
6
8
|
export interface CatalogModelPricingTier {
|
|
7
9
|
/**
|
|
@@ -243,6 +245,24 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
243
245
|
cacheReadPer1mTokens: 1,
|
|
244
246
|
},
|
|
245
247
|
},
|
|
248
|
+
{
|
|
249
|
+
id: "claude-opus-5-5",
|
|
250
|
+
displayName: "Claude Opus 5.5",
|
|
251
|
+
contextWindowTokens: 1000000,
|
|
252
|
+
maxOutputTokens: 128000,
|
|
253
|
+
longContextPricingThresholdTokens: 200000,
|
|
254
|
+
supportsThinking: true,
|
|
255
|
+
adaptiveThinkingOnly: true,
|
|
256
|
+
supportsCaching: true,
|
|
257
|
+
supportsVision: true,
|
|
258
|
+
supportsToolUse: true,
|
|
259
|
+
pricing: {
|
|
260
|
+
inputPer1mTokens: 4,
|
|
261
|
+
outputPer1mTokens: 20,
|
|
262
|
+
cacheWritePer1mTokens: 5,
|
|
263
|
+
cacheReadPer1mTokens: 0.2,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
246
266
|
{
|
|
247
267
|
id: "claude-opus-5",
|
|
248
268
|
displayName: "Claude Opus 5",
|
|
@@ -454,6 +474,66 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
454
474
|
],
|
|
455
475
|
},
|
|
456
476
|
},
|
|
477
|
+
{
|
|
478
|
+
id: "gpt-6-sol",
|
|
479
|
+
displayName: "GPT-6 Sol",
|
|
480
|
+
contextWindowTokens: 1050000,
|
|
481
|
+
maxOutputTokens: 128000,
|
|
482
|
+
longContextPricingThresholdTokens:
|
|
483
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
484
|
+
supportsThinking: true,
|
|
485
|
+
supportsCaching: true,
|
|
486
|
+
supportsVision: true,
|
|
487
|
+
supportsToolUse: true,
|
|
488
|
+
supportsPromptCacheBreakpoints: true,
|
|
489
|
+
maxEffort: "max",
|
|
490
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
491
|
+
pricing: {
|
|
492
|
+
inputPer1mTokens: 2,
|
|
493
|
+
outputPer1mTokens: 10,
|
|
494
|
+
cacheWritePer1mTokens: 2.5,
|
|
495
|
+
cacheReadPer1mTokens: 0.2,
|
|
496
|
+
tiers: [
|
|
497
|
+
{
|
|
498
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
499
|
+
inputPer1mTokens: 4,
|
|
500
|
+
outputPer1mTokens: 15,
|
|
501
|
+
cacheWritePer1mTokens: 5,
|
|
502
|
+
cacheReadPer1mTokens: 0.4,
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
id: "gpt-6-luna",
|
|
509
|
+
displayName: "GPT-6 Luna",
|
|
510
|
+
contextWindowTokens: 1050000,
|
|
511
|
+
maxOutputTokens: 128000,
|
|
512
|
+
longContextPricingThresholdTokens:
|
|
513
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
514
|
+
supportsThinking: true,
|
|
515
|
+
supportsCaching: true,
|
|
516
|
+
supportsVision: true,
|
|
517
|
+
supportsToolUse: true,
|
|
518
|
+
supportsPromptCacheBreakpoints: true,
|
|
519
|
+
maxEffort: "max",
|
|
520
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
521
|
+
pricing: {
|
|
522
|
+
inputPer1mTokens: 0.1,
|
|
523
|
+
outputPer1mTokens: 0.5,
|
|
524
|
+
cacheWritePer1mTokens: 0.125,
|
|
525
|
+
cacheReadPer1mTokens: 0.01,
|
|
526
|
+
tiers: [
|
|
527
|
+
{
|
|
528
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
529
|
+
inputPer1mTokens: 0.2,
|
|
530
|
+
outputPer1mTokens: 0.75,
|
|
531
|
+
cacheWritePer1mTokens: 0.25,
|
|
532
|
+
cacheReadPer1mTokens: 0.02,
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
},
|
|
457
537
|
// GPT-5.6 family (Sol / Terra / Luna). cacheRead is the 90% cached-read
|
|
458
538
|
// discount; cacheWrite is the 1.25x-input rate GPT-5.6+ bills for
|
|
459
539
|
// prompt tokens written to the cache (reported as `cache_write_tokens`
|
|
@@ -1207,6 +1287,24 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1207
1287
|
cacheReadPer1mTokens: 1,
|
|
1208
1288
|
},
|
|
1209
1289
|
},
|
|
1290
|
+
{
|
|
1291
|
+
id: "anthropic/claude-opus-5.5",
|
|
1292
|
+
displayName: "Claude Opus 5.5",
|
|
1293
|
+
contextWindowTokens: 1000000,
|
|
1294
|
+
maxOutputTokens: 128000,
|
|
1295
|
+
longContextPricingThresholdTokens: 200000,
|
|
1296
|
+
supportsThinking: true,
|
|
1297
|
+
adaptiveThinkingOnly: true,
|
|
1298
|
+
supportsCaching: true,
|
|
1299
|
+
supportsVision: true,
|
|
1300
|
+
supportsToolUse: true,
|
|
1301
|
+
pricing: {
|
|
1302
|
+
inputPer1mTokens: 4,
|
|
1303
|
+
outputPer1mTokens: 20,
|
|
1304
|
+
cacheWritePer1mTokens: 5,
|
|
1305
|
+
cacheReadPer1mTokens: 0.2,
|
|
1306
|
+
},
|
|
1307
|
+
},
|
|
1210
1308
|
{
|
|
1211
1309
|
id: "anthropic/claude-opus-5",
|
|
1212
1310
|
displayName: "Claude Opus 5",
|
|
@@ -1429,6 +1527,126 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1429
1527
|
],
|
|
1430
1528
|
},
|
|
1431
1529
|
},
|
|
1530
|
+
{
|
|
1531
|
+
id: "openai/gpt-6-sol",
|
|
1532
|
+
displayName: "GPT-6 Sol",
|
|
1533
|
+
contextWindowTokens: 1050000,
|
|
1534
|
+
maxOutputTokens: 128000,
|
|
1535
|
+
longContextPricingThresholdTokens:
|
|
1536
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1537
|
+
supportsThinking: true,
|
|
1538
|
+
supportsCaching: true,
|
|
1539
|
+
supportsVision: true,
|
|
1540
|
+
supportsToolUse: true,
|
|
1541
|
+
supportsPromptCacheBreakpoints: true,
|
|
1542
|
+
maxEffort: "max",
|
|
1543
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1544
|
+
pricing: {
|
|
1545
|
+
inputPer1mTokens: 2,
|
|
1546
|
+
outputPer1mTokens: 10,
|
|
1547
|
+
cacheWritePer1mTokens: 2.5,
|
|
1548
|
+
cacheReadPer1mTokens: 0.2,
|
|
1549
|
+
tiers: [
|
|
1550
|
+
{
|
|
1551
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1552
|
+
inputPer1mTokens: 4,
|
|
1553
|
+
outputPer1mTokens: 15,
|
|
1554
|
+
cacheWritePer1mTokens: 5,
|
|
1555
|
+
cacheReadPer1mTokens: 0.4,
|
|
1556
|
+
},
|
|
1557
|
+
],
|
|
1558
|
+
},
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
id: "openai/gpt-6-sol-pro",
|
|
1562
|
+
displayName: "GPT-6 Sol Pro",
|
|
1563
|
+
contextWindowTokens: 1050000,
|
|
1564
|
+
maxOutputTokens: 128000,
|
|
1565
|
+
longContextPricingThresholdTokens:
|
|
1566
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1567
|
+
supportsThinking: true,
|
|
1568
|
+
supportsCaching: true,
|
|
1569
|
+
supportsVision: true,
|
|
1570
|
+
supportsToolUse: true,
|
|
1571
|
+
supportsPromptCacheBreakpoints: true,
|
|
1572
|
+
maxEffort: "max",
|
|
1573
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1574
|
+
pricing: {
|
|
1575
|
+
inputPer1mTokens: 2,
|
|
1576
|
+
outputPer1mTokens: 10,
|
|
1577
|
+
cacheWritePer1mTokens: 2.5,
|
|
1578
|
+
cacheReadPer1mTokens: 0.2,
|
|
1579
|
+
tiers: [
|
|
1580
|
+
{
|
|
1581
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1582
|
+
inputPer1mTokens: 4,
|
|
1583
|
+
outputPer1mTokens: 15,
|
|
1584
|
+
cacheWritePer1mTokens: 5,
|
|
1585
|
+
cacheReadPer1mTokens: 0.4,
|
|
1586
|
+
},
|
|
1587
|
+
],
|
|
1588
|
+
},
|
|
1589
|
+
},
|
|
1590
|
+
{
|
|
1591
|
+
id: "openai/gpt-6-luna",
|
|
1592
|
+
displayName: "GPT-6 Luna",
|
|
1593
|
+
contextWindowTokens: 1050000,
|
|
1594
|
+
maxOutputTokens: 128000,
|
|
1595
|
+
longContextPricingThresholdTokens:
|
|
1596
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1597
|
+
supportsThinking: true,
|
|
1598
|
+
supportsCaching: true,
|
|
1599
|
+
supportsVision: true,
|
|
1600
|
+
supportsToolUse: true,
|
|
1601
|
+
supportsPromptCacheBreakpoints: true,
|
|
1602
|
+
maxEffort: "max",
|
|
1603
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1604
|
+
pricing: {
|
|
1605
|
+
inputPer1mTokens: 0.1,
|
|
1606
|
+
outputPer1mTokens: 0.5,
|
|
1607
|
+
cacheWritePer1mTokens: 0.125,
|
|
1608
|
+
cacheReadPer1mTokens: 0.01,
|
|
1609
|
+
tiers: [
|
|
1610
|
+
{
|
|
1611
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1612
|
+
inputPer1mTokens: 0.2,
|
|
1613
|
+
outputPer1mTokens: 0.75,
|
|
1614
|
+
cacheWritePer1mTokens: 0.25,
|
|
1615
|
+
cacheReadPer1mTokens: 0.02,
|
|
1616
|
+
},
|
|
1617
|
+
],
|
|
1618
|
+
},
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
id: "openai/gpt-6-luna-pro",
|
|
1622
|
+
displayName: "GPT-6 Luna Pro",
|
|
1623
|
+
contextWindowTokens: 1050000,
|
|
1624
|
+
maxOutputTokens: 128000,
|
|
1625
|
+
longContextPricingThresholdTokens:
|
|
1626
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1627
|
+
supportsThinking: true,
|
|
1628
|
+
supportsCaching: true,
|
|
1629
|
+
supportsVision: true,
|
|
1630
|
+
supportsToolUse: true,
|
|
1631
|
+
supportsPromptCacheBreakpoints: true,
|
|
1632
|
+
maxEffort: "max",
|
|
1633
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1634
|
+
pricing: {
|
|
1635
|
+
inputPer1mTokens: 0.1,
|
|
1636
|
+
outputPer1mTokens: 0.5,
|
|
1637
|
+
cacheWritePer1mTokens: 0.125,
|
|
1638
|
+
cacheReadPer1mTokens: 0.01,
|
|
1639
|
+
tiers: [
|
|
1640
|
+
{
|
|
1641
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1642
|
+
inputPer1mTokens: 0.2,
|
|
1643
|
+
outputPer1mTokens: 0.75,
|
|
1644
|
+
cacheWritePer1mTokens: 0.25,
|
|
1645
|
+
cacheReadPer1mTokens: 0.02,
|
|
1646
|
+
},
|
|
1647
|
+
],
|
|
1648
|
+
},
|
|
1649
|
+
},
|
|
1432
1650
|
// GPT-5.6 family (Sol / Terra / Luna). The `*-pro` slugs are the same
|
|
1433
1651
|
// underlying models served with `reasoning.mode: pro` at identical
|
|
1434
1652
|
// rates. cacheWrite is the 1.25x-input rate GPT-5.6+ bills for prompt
|
|
@@ -2140,6 +2358,24 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
2140
2358
|
cacheReadPer1mTokens: 1,
|
|
2141
2359
|
},
|
|
2142
2360
|
},
|
|
2361
|
+
{
|
|
2362
|
+
id: "anthropic/claude-opus-5.5",
|
|
2363
|
+
displayName: "Claude Opus 5.5",
|
|
2364
|
+
contextWindowTokens: 1000000,
|
|
2365
|
+
maxOutputTokens: 128000,
|
|
2366
|
+
longContextPricingThresholdTokens: 200000,
|
|
2367
|
+
supportsThinking: true,
|
|
2368
|
+
adaptiveThinkingOnly: true,
|
|
2369
|
+
supportsCaching: true,
|
|
2370
|
+
supportsVision: true,
|
|
2371
|
+
supportsToolUse: true,
|
|
2372
|
+
pricing: {
|
|
2373
|
+
inputPer1mTokens: 4,
|
|
2374
|
+
outputPer1mTokens: 20,
|
|
2375
|
+
cacheWritePer1mTokens: 5,
|
|
2376
|
+
cacheReadPer1mTokens: 0.2,
|
|
2377
|
+
},
|
|
2378
|
+
},
|
|
2143
2379
|
{
|
|
2144
2380
|
id: "anthropic/claude-opus-5",
|
|
2145
2381
|
displayName: "Claude Opus 5",
|
|
@@ -2602,6 +2838,16 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] =
|
|
|
2602
2838
|
supportsPlatformAuth: PLATFORM_PROVIDER_META[entry.id]?.managed === true,
|
|
2603
2839
|
}));
|
|
2604
2840
|
|
|
2841
|
+
/**
|
|
2842
|
+
* Providers that are routing identities rather than catalog owners. Mirrors
|
|
2843
|
+
* `ROUTING_IDENTITY_PROVIDERS` in `inference/auth.ts`, which cannot be
|
|
2844
|
+
* imported here without a cycle.
|
|
2845
|
+
*/
|
|
2846
|
+
const ROUTING_IDENTITY_PROVIDER_IDS: ReadonlySet<string> = new Set([
|
|
2847
|
+
"vellum",
|
|
2848
|
+
"chatgpt",
|
|
2849
|
+
]);
|
|
2850
|
+
|
|
2605
2851
|
/**
|
|
2606
2852
|
* Whether a catalog model produces free-form chat text. Unlisted providers
|
|
2607
2853
|
* and model ids default to true so custom endpoints and unknown snapshots
|
|
@@ -2614,9 +2860,16 @@ export function catalogModelSupportsText(
|
|
|
2614
2860
|
if (typeof provider !== "string" || typeof modelId !== "string") {
|
|
2615
2861
|
return true;
|
|
2616
2862
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2863
|
+
// A routing identity ("vellum", "chatgpt") owns no catalog models of the
|
|
2864
|
+
// upstream it dispatches to, so judge the model by its catalog owner: a
|
|
2865
|
+
// managed Jev profile is `vellum` + `jev-latest`, and `jev-latest` opts out
|
|
2866
|
+
// of text under `typesafe`.
|
|
2867
|
+
const catalogProvider = ROUTING_IDENTITY_PROVIDER_IDS.has(provider)
|
|
2868
|
+
? (getCatalogProviderForModel(modelId) ?? provider)
|
|
2869
|
+
: provider;
|
|
2870
|
+
const model = PROVIDER_CATALOG.find(
|
|
2871
|
+
(p) => p.id === catalogProvider,
|
|
2872
|
+
)?.models.find((m) => m.id === modelId);
|
|
2620
2873
|
return model?.supportsText !== false;
|
|
2621
2874
|
}
|
|
2622
2875
|
|