@vellumai/assistant 0.12.5-dev.202609281620.da5cb5b → 0.12.5-dev.202609281822.fb70dee

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.
Files changed (40) hide show
  1. package/openapi.yaml +10 -4
  2. package/package.json +1 -1
  3. package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
  4. package/src/__tests__/config-get-vision-flag.test.ts +1 -1
  5. package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
  6. package/src/__tests__/conversation-pairing.test.ts +112 -0
  7. package/src/__tests__/emit-signal-routing-intent.test.ts +159 -0
  8. package/src/__tests__/llm-catalog-parity.test.ts +4 -4
  9. package/src/__tests__/llm-schema.test.ts +1 -1
  10. package/src/__tests__/openai-provider.test.ts +11 -11
  11. package/src/__tests__/pricing.test.ts +11 -0
  12. package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
  13. package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
  14. package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
  15. package/src/config/default-profile-catalog.ts +1 -1
  16. package/src/notifications/README.md +41 -3
  17. package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
  18. package/src/notifications/__tests__/broadcaster.test.ts +299 -4
  19. package/src/notifications/__tests__/completion-policy.test.ts +37 -0
  20. package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
  21. package/src/notifications/assistant-initiated-thread.ts +101 -0
  22. package/src/notifications/broadcaster.ts +71 -12
  23. package/src/notifications/completion-policy.ts +7 -1
  24. package/src/notifications/conversation-pairing.ts +15 -70
  25. package/src/notifications/emit-signal.ts +34 -3
  26. package/src/persistence/conversation-queries.ts +15 -8
  27. package/src/plugin-api/vision-support.test.ts +21 -21
  28. package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
  29. package/src/providers/model-catalog.ts +47 -84
  30. package/src/providers/model-intents.ts +4 -4
  31. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
  32. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
  33. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
  34. package/src/runtime/routes/conversation-list-routes.ts +10 -2
  35. package/src/runtime/routes/user-route-dispatcher.ts +5 -1
  36. package/src/util/pricing.ts +25 -0
  37. package/src/workspace/byok-default-profile-ensure.ts +11 -0
  38. package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
  39. package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
  40. package/src/workspace/migrations/registry.ts +4 -0
package/openapi.yaml CHANGED
@@ -8042,8 +8042,9 @@ paths:
8042
8042
  Filter by conversation type. Pass "background" to list background and scheduled conversations together (the
8043
8043
  back-compat umbrella), "scheduled" to list only scheduled conversations, or "all" for the union of the
8044
8044
  standard listing and that umbrella, so one paginated cursor spans every type. Combine "all" with
8045
- archiveStatus=all for a whole-history read; rows are recency-ordered (COALESCE(last_message_at, updated_at)
8046
- descending) like every other list read, and pinned rows are never appended to it.
8045
+ archiveStatus=all for a whole-history read; rows are ordered by last activity (the later of the last message
8046
+ and the time the row was marked done, descending) like every other list read, and pinned rows are never
8047
+ appended to it.
8047
8048
  - name: archiveStatus
8048
8049
  in: query
8049
8050
  required: false
@@ -8083,8 +8084,9 @@ paths:
8083
8084
  description:
8084
8085
  Filter to a single group, so each sidebar section can load independently of the paginated list. Pass
8085
8086
  "system:all" for conversations in no group, "system:pinned" for the Pinned section, or a custom group id. A
8086
- group-scoped request is recency-ordered like every list read (COALESCE(last_message_at, updated_at)
8087
- descending) and never has pinned rows appended to it. Omit to span every group.
8087
+ group-scoped request is ordered by last activity like every list read (the later of the last message and the
8088
+ time the row was marked done, descending) and never has pinned rows appended to it. Omit to span every
8089
+ group.
8088
8090
  - name: needsAttention
8089
8091
  in: query
8090
8092
  required: false
@@ -8340,10 +8342,14 @@ paths:
8340
8342
  - sortPosition
8341
8343
  - isSystemGroup
8342
8344
  additionalProperties: false
8345
+ orderedBy:
8346
+ type: string
8347
+ const: lastActivity
8343
8348
  required:
8344
8349
  - conversations
8345
8350
  - nextOffset
8346
8351
  - hasMore
8352
+ - orderedBy
8347
8353
  additionalProperties: false
8348
8354
  post:
8349
8355
  operationId: conversations_post
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.12.5-dev.202609281620.da5cb5b",
3
+ "version": "0.12.5-dev.202609281822.fb70dee",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -302,6 +302,13 @@ describe("ensureByokDefaultProfiles", () => {
302
302
  "cost-optimized",
303
303
  "accounts/fireworks/models/deepseek-v4-flash",
304
304
  ],
305
+ [
306
+ "fireworks",
307
+ "cost-optimized",
308
+ "accounts/fireworks/models/deepseek-v4-flash-0731",
309
+ ],
310
+ ["fireworks", "balanced", "accounts/fireworks/models/deepseek-v4p1-flash"],
311
+ ["fireworks", "quality-optimized", "accounts/fireworks/models/kimi-k2p6"],
305
312
  ] as const)(
306
313
  "a historical intent-era copy converts (%s %s pinned to %s)",
307
314
  (provider, key, model) => {
@@ -40,7 +40,7 @@ describe("GET /v1/config profile vision enrichment", () => {
40
40
  seedProfiles({
41
41
  "test-no-vision": {
42
42
  provider: "fireworks",
43
- model: "accounts/fireworks/models/glm-5p2",
43
+ model: "accounts/fireworks/models/glm-5p3",
44
44
  },
45
45
  });
46
46
 
@@ -171,7 +171,7 @@ describe("config-validation-reset notice sentinel", () => {
171
171
  profiles: {
172
172
  "glm-default": {
173
173
  provider: "vellum",
174
- model: "accounts/fireworks/models/glm-5p2",
174
+ model: "accounts/fireworks/models/glm-5p3",
175
175
  },
176
176
  },
177
177
  activeProfile: "glm-default",
@@ -193,7 +193,7 @@ describe("config-validation-reset notice sentinel", () => {
193
193
  expect(config.llm.activeProfile).toBe("glm-default");
194
194
  expect(config.llm.callSites.mainAgent?.profile).toBe("glm-default");
195
195
  expect(config.llm.profiles["glm-default"]?.model).toBe(
196
- "accounts/fireworks/models/glm-5p2",
196
+ "accounts/fireworks/models/glm-5p3",
197
197
  );
198
198
  });
199
199
 
@@ -116,6 +116,7 @@ mock.module("../config/assistant-initiated-threads-gate.js", () => ({
116
116
  isAssistantInitiatedThreadsEnabled: () => assistantInitiatedThreadsEnabled,
117
117
  }));
118
118
 
119
+ import { resolveAssistantInitiatedThread } from "../notifications/assistant-initiated-thread.js";
119
120
  import { pairDeliveryWithConversation } from "../notifications/conversation-pairing.js";
120
121
  import type { NotificationSignal } from "../notifications/signal.js";
121
122
  import type {
@@ -1478,6 +1479,117 @@ describe("pairDeliveryWithConversation", () => {
1478
1479
  ...overrides,
1479
1480
  });
1480
1481
 
1482
+ test("a prepared resolution is reused even if the source changes before pairing", async () => {
1483
+ assistantInitiatedThreadsEnabled = true;
1484
+ const signal = share({
1485
+ conversationMetadata: { groupId: "group-123" },
1486
+ });
1487
+ const original = structuredClone(signal);
1488
+ const assistantInitiatedThread = resolveAssistantInitiatedThread(signal);
1489
+ getConversationMock.mockClear();
1490
+ mockExistingConversations[signal.sourceContextId] = {
1491
+ id: signal.sourceContextId,
1492
+ source: "user",
1493
+ title: null,
1494
+ conversationType: "standard",
1495
+ };
1496
+
1497
+ const result = await pairDeliveryWithConversation(
1498
+ signal,
1499
+ "vellum",
1500
+ makeCopy(),
1501
+ { assistantInitiatedThread },
1502
+ );
1503
+
1504
+ expect(result.createdNewConversation).toBe(true);
1505
+ expect(result.messageId).toBe("msg-001");
1506
+ expect(createConversationMock).toHaveBeenCalledWith(
1507
+ expect.objectContaining({
1508
+ source: "assistant_initiated",
1509
+ groupId: "group-123",
1510
+ }),
1511
+ );
1512
+ expect(getConversationMock).not.toHaveBeenCalled();
1513
+ expect(signal).toEqual(original);
1514
+ });
1515
+
1516
+ test("an ineligible prepared resolution retains passive pairing when the flag changes", async () => {
1517
+ const signal = share();
1518
+ const assistantInitiatedThread = resolveAssistantInitiatedThread(signal);
1519
+ assistantInitiatedThreadsEnabled = true;
1520
+ mockExistingConversations[signal.sourceContextId] = {
1521
+ id: signal.sourceContextId,
1522
+ source: "heartbeat",
1523
+ title: null,
1524
+ conversationType: "background",
1525
+ };
1526
+
1527
+ const result = await pairDeliveryWithConversation(
1528
+ signal,
1529
+ "vellum",
1530
+ makeCopy(),
1531
+ { assistantInitiatedThread },
1532
+ );
1533
+
1534
+ expect(result.createdNewConversation).toBe(false);
1535
+ expect(result.conversationId).toBe(signal.sourceContextId);
1536
+ expect(createConversationMock).not.toHaveBeenCalled();
1537
+ expect(addMessageMock).toHaveBeenCalledTimes(1);
1538
+ });
1539
+
1540
+ test.each(["slack", "telegram", "discord"] as const)(
1541
+ "%s retains the producer metadata despite a prepared vellum resolution",
1542
+ async (channel) => {
1543
+ assistantInitiatedThreadsEnabled = true;
1544
+ const signal = share({
1545
+ conversationMetadata: { groupId: "group-123" },
1546
+ });
1547
+ const original = structuredClone(signal);
1548
+ const assistantInitiatedThread =
1549
+ resolveAssistantInitiatedThread(signal);
1550
+ getConversationMock.mockClear();
1551
+
1552
+ const result = await pairDeliveryWithConversation(
1553
+ signal,
1554
+ channel,
1555
+ makeCopy(),
1556
+ { assistantInitiatedThread },
1557
+ );
1558
+
1559
+ expect(result.createdNewConversation).toBe(true);
1560
+ expect(result.strategy).toBe("continue_existing_conversation");
1561
+ expect(createConversationMock).toHaveBeenCalledWith(
1562
+ expect.objectContaining({
1563
+ conversationType: "background",
1564
+ source: "notification",
1565
+ groupId: "group-123",
1566
+ }),
1567
+ );
1568
+ expect(addMessageMock).not.toHaveBeenCalled();
1569
+ expect(getConversationMock).not.toHaveBeenCalled();
1570
+ expect(signal).toEqual(original);
1571
+ },
1572
+ );
1573
+
1574
+ test("platform delivery does not pair a prepared vellum resolution", async () => {
1575
+ assistantInitiatedThreadsEnabled = true;
1576
+ const signal = share();
1577
+ const assistantInitiatedThread = resolveAssistantInitiatedThread(signal);
1578
+
1579
+ const result = await pairDeliveryWithConversation(
1580
+ signal,
1581
+ "platform",
1582
+ makeCopy(),
1583
+ { assistantInitiatedThread },
1584
+ );
1585
+
1586
+ expect(result.strategy).toBe("push_only");
1587
+ expect(result.createdNewConversation).toBe(false);
1588
+ expect(result.conversationId).toBeNull();
1589
+ expect(createConversationMock).not.toHaveBeenCalled();
1590
+ expect(addMessageMock).not.toHaveBeenCalled();
1591
+ });
1592
+
1481
1593
  test("flag on: a share from a background run materializes a section thread", async () => {
1482
1594
  assistantInitiatedThreadsEnabled = true;
1483
1595
  mockExistingConversations["conv-heartbeat"] = {
@@ -1,8 +1,26 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import { saveRawConfig } from "../config/loader.js";
4
+ import type { NotificationSignal } from "../notifications/signal.js";
4
5
  import type { NotificationChannel } from "../notifications/types.js";
5
6
 
7
+ let assistantInitiatedCandidate = false;
8
+ const resolveAssistantInitiatedThreadMock = mock(
9
+ (signal: NotificationSignal) => ({
10
+ isCandidate: assistantInitiatedCandidate,
11
+ vellumSignal: assistantInitiatedCandidate
12
+ ? {
13
+ ...signal,
14
+ requiresConversation: true,
15
+ conversationMetadata: { source: "assistant_initiated" },
16
+ }
17
+ : signal,
18
+ }),
19
+ );
20
+ mock.module("../notifications/assistant-initiated-thread.js", () => ({
21
+ resolveAssistantInitiatedThread: resolveAssistantInitiatedThreadMock,
22
+ }));
23
+
6
24
  const evaluateSignalMock = mock();
7
25
  const enforceRoutingIntentMock = mock();
8
26
  const updateDecisionMock = mock();
@@ -113,6 +131,8 @@ import {
113
131
 
114
132
  beforeEach(() => {
115
133
  saveRawConfig({});
134
+ assistantInitiatedCandidate = false;
135
+ resolveAssistantInitiatedThreadMock.mockClear();
116
136
  writeHomeFeedItemForSignalMock.mockClear();
117
137
  evaluateSignalMock.mockReset();
118
138
  enforceRoutingIntentMock.mockReset();
@@ -1020,3 +1040,142 @@ describe("classifyNotificationReceipt", () => {
1020
1040
  ).toBe("unknown");
1021
1041
  });
1022
1042
  });
1043
+
1044
+ describe("emitNotificationSignal From me push defaults", () => {
1045
+ const params = {
1046
+ sourceEventName: "assistant.share",
1047
+ sourceChannel: "assistant_tool" as const,
1048
+ sourceContextId: "conv-background",
1049
+ attentionHints: {
1050
+ urgency: "low" as const,
1051
+ requiresAction: false,
1052
+ isAsyncBackground: true,
1053
+ visibleInSourceNow: false,
1054
+ },
1055
+ };
1056
+ const decision = (selectedChannels: NotificationChannel[] = ["vellum"]) => ({
1057
+ shouldNotify: true,
1058
+ selectedChannels,
1059
+ reasoningSummary: "Assistant-authored share",
1060
+ renderedCopy: { vellum: { title: "An update", body: "A useful result." } },
1061
+ dedupeKey: "share-1",
1062
+ confidence: 1,
1063
+ fallbackUsed: false,
1064
+ persistedDecisionId: "decision-share",
1065
+ });
1066
+
1067
+ beforeEach(() => {
1068
+ assistantInitiatedCandidate = true;
1069
+ evaluateSignalMock.mockResolvedValue(decision());
1070
+ });
1071
+
1072
+ test("adds configured platform after routing and persists the final decision", async () => {
1073
+ const result = await emitNotificationSignal(params);
1074
+ expect(result.selectedChannels).toEqual(["vellum", "platform"]);
1075
+ expect(resolveAssistantInitiatedThreadMock).toHaveBeenCalledTimes(1);
1076
+ expect(updateDecisionMock).toHaveBeenCalledWith(
1077
+ "decision-share",
1078
+ expect.objectContaining({
1079
+ selectedChannels: ["vellum", "platform"],
1080
+ }),
1081
+ );
1082
+ expect(dispatchDecisionMock.mock.calls[0]?.[0]).toMatchObject({
1083
+ attentionHints: { urgency: "low" },
1084
+ });
1085
+ expect(dispatchDecisionMock.mock.calls[0]?.[3]).toMatchObject({
1086
+ assistantInitiatedThread: {
1087
+ platformAdded: true,
1088
+ resolution: {
1089
+ isCandidate: true,
1090
+ vellumSignal: {
1091
+ requiresConversation: true,
1092
+ conversationMetadata: { source: "assistant_initiated" },
1093
+ },
1094
+ },
1095
+ },
1096
+ });
1097
+ });
1098
+
1099
+ test.each([
1100
+ "quiet",
1101
+ "unconfigured",
1102
+ "noncandidate",
1103
+ "suppressed",
1104
+ "single_channel",
1105
+ "external_only",
1106
+ "vellum_allowlist",
1107
+ ])("respects %s policy without adding platform", async (scenario) => {
1108
+ assistantInitiatedCandidate = scenario !== "noncandidate";
1109
+ isPlatformClientConfiguredMock.mockResolvedValue(
1110
+ scenario !== "unconfigured",
1111
+ );
1112
+ evaluateSignalMock.mockResolvedValue({
1113
+ ...decision(scenario === "external_only" ? ["telegram"] : ["vellum"]),
1114
+ shouldNotify: scenario !== "suppressed",
1115
+ });
1116
+ const result = await emitNotificationSignal({
1117
+ ...params,
1118
+ routingIntent:
1119
+ scenario === "single_channel" ? "single_channel" : undefined,
1120
+ contextPayload: {
1121
+ quiet: scenario === "quiet",
1122
+ ...(scenario === "vellum_allowlist"
1123
+ ? { channelAllowlist: ["vellum"] }
1124
+ : {}),
1125
+ },
1126
+ });
1127
+ expect(result.selectedChannels).not.toContain("platform");
1128
+ expect(dispatchDecisionMock.mock.calls[0]?.[3]).toMatchObject({
1129
+ assistantInitiatedThread: { platformAdded: false },
1130
+ });
1131
+ });
1132
+
1133
+ test("keeps existing platform and external selections independent of this feature", async () => {
1134
+ evaluateSignalMock.mockResolvedValue(
1135
+ decision(["vellum", "platform", "telegram"]),
1136
+ );
1137
+ const result = await emitNotificationSignal({
1138
+ ...params,
1139
+ contextPayload: { quiet: true },
1140
+ });
1141
+ expect(result.selectedChannels).toEqual(["vellum", "platform", "telegram"]);
1142
+ expect(isPlatformClientConfiguredMock).not.toHaveBeenCalled();
1143
+ expect(dispatchDecisionMock.mock.calls[0]?.[3]).toMatchObject({
1144
+ assistantInitiatedThread: { platformAdded: false },
1145
+ });
1146
+ });
1147
+
1148
+ test("probes an unbound platform once across urgency and From me policy", async () => {
1149
+ isPlatformClientConfiguredMock.mockResolvedValue(false);
1150
+ const result = await emitNotificationSignal({
1151
+ ...params,
1152
+ attentionHints: { ...params.attentionHints, urgency: "high" },
1153
+ });
1154
+ expect(result.selectedChannels).toEqual(["vellum"]);
1155
+ expect(isPlatformClientConfiguredMock).toHaveBeenCalledTimes(1);
1156
+ });
1157
+
1158
+ test("preserves preferred external selections while adding the platform default", async () => {
1159
+ evaluateSignalMock.mockResolvedValue(decision(["vellum", "telegram"]));
1160
+ const result = await emitNotificationSignal(params);
1161
+ expect(result.selectedChannels).toEqual(["vellum", "telegram", "platform"]);
1162
+ });
1163
+
1164
+ test.each(["deduplicated", "source_active"])(
1165
+ "does not resolve candidates for %s signals",
1166
+ async (scenario) => {
1167
+ if (scenario === "deduplicated") {
1168
+ createEventMock.mockReturnValue(null);
1169
+ }
1170
+ await emitNotificationSignal({
1171
+ ...params,
1172
+ attentionHints: {
1173
+ ...params.attentionHints,
1174
+ visibleInSourceNow: scenario === "source_active",
1175
+ },
1176
+ });
1177
+ expect(resolveAssistantInitiatedThreadMock).not.toHaveBeenCalled();
1178
+ expect(dispatchDecisionMock).not.toHaveBeenCalled();
1179
+ },
1180
+ );
1181
+ });
@@ -390,9 +390,9 @@ describe("LLM catalog parity: daemon vs client", () => {
390
390
  });
391
391
  });
392
392
 
393
- test("Fireworks catalog includes GLM 5.2", () => {
393
+ test("Fireworks catalog includes GLM 5.3", () => {
394
394
  expect(
395
- isModelInCatalog("fireworks", "accounts/fireworks/models/glm-5p2"),
395
+ isModelInCatalog("fireworks", "accounts/fireworks/models/glm-5p3"),
396
396
  ).toBe(true);
397
397
 
398
398
  const fireworks = PROVIDER_CATALOG.find(
@@ -400,10 +400,10 @@ describe("LLM catalog parity: daemon vs client", () => {
400
400
  );
401
401
  expect(
402
402
  fireworks?.models.find(
403
- (model) => model.id === "accounts/fireworks/models/glm-5p2",
403
+ (model) => model.id === "accounts/fireworks/models/glm-5p3",
404
404
  ),
405
405
  ).toMatchObject({
406
- displayName: "GLM 5.2",
406
+ displayName: "GLM 5.3",
407
407
  contextWindowTokens: 1040000,
408
408
  maxOutputTokens: 131072,
409
409
  supportsToolUse: true,
@@ -198,7 +198,7 @@ describe("LLMSchema", () => {
198
198
  profiles: {
199
199
  "glm-default": {
200
200
  provider: "vellum",
201
- model: "accounts/fireworks/models/glm-5p2",
201
+ model: "accounts/fireworks/models/glm-5p3",
202
202
  },
203
203
  },
204
204
  activeProfile: "glm-default",
@@ -1960,10 +1960,10 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
1960
1960
  lastCreateParams = null;
1961
1961
  });
1962
1962
 
1963
- test('DeepSeek V4 Pro accepts "max" unclamped', async () => {
1963
+ test('DeepSeek V4.1 Flash accepts "max" unclamped', async () => {
1964
1964
  const fw = new FireworksProvider(
1965
1965
  "fw-key",
1966
- "accounts/fireworks/models/deepseek-v4-pro-0813",
1966
+ "accounts/fireworks/models/deepseek-v4p1-flash",
1967
1967
  );
1968
1968
  await fw.sendMessage([userMsg("hi")], {
1969
1969
  systemPrompt: "system",
@@ -1972,10 +1972,10 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
1972
1972
  expect(lastCreateParams!.reasoning_effort).toBe("max");
1973
1973
  });
1974
1974
 
1975
- test('DeepSeek V4 Pro accepts "xhigh" unclamped', async () => {
1975
+ test('DeepSeek V4.1 Flash accepts "xhigh" unclamped', async () => {
1976
1976
  const fw = new FireworksProvider(
1977
1977
  "fw-key",
1978
- "accounts/fireworks/models/deepseek-v4-pro-0813",
1978
+ "accounts/fireworks/models/deepseek-v4p1-flash",
1979
1979
  );
1980
1980
  await fw.sendMessage([userMsg("hi")], {
1981
1981
  systemPrompt: "system",
@@ -1984,10 +1984,10 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
1984
1984
  expect(lastCreateParams!.reasoning_effort).toBe("xhigh");
1985
1985
  });
1986
1986
 
1987
- test('Kimi K2.6 clamps "xhigh"/"max" down to "high"', async () => {
1987
+ test('MiniMax M3 clamps "xhigh"/"max" down to "high"', async () => {
1988
1988
  const fw = new FireworksProvider(
1989
1989
  "fw-key",
1990
- "accounts/fireworks/models/kimi-k2p6",
1990
+ "accounts/fireworks/models/minimax-m3",
1991
1991
  );
1992
1992
  await fw.sendMessage([userMsg("hi")], {
1993
1993
  systemPrompt: "system",
@@ -2016,7 +2016,7 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
2016
2016
  test("effort below ceiling is forwarded verbatim", async () => {
2017
2017
  const fw = new FireworksProvider(
2018
2018
  "fw-key",
2019
- "accounts/fireworks/models/deepseek-v4-pro-0813",
2019
+ "accounts/fireworks/models/deepseek-v4p1-flash",
2020
2020
  );
2021
2021
  await fw.sendMessage([userMsg("hi")], {
2022
2022
  systemPrompt: "system",
@@ -2028,7 +2028,7 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
2028
2028
  test('effort: "none" passes through regardless of ceiling', async () => {
2029
2029
  const fw = new FireworksProvider(
2030
2030
  "fw-key",
2031
- "accounts/fireworks/models/kimi-k2p6",
2031
+ "accounts/fireworks/models/minimax-m3",
2032
2032
  );
2033
2033
  await fw.sendMessage([userMsg("hi")], {
2034
2034
  systemPrompt: "system",
@@ -2040,13 +2040,13 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
2040
2040
  test("model override picks ceiling from runtime model, not constructor model", async () => {
2041
2041
  const fw = new FireworksProvider(
2042
2042
  "fw-key",
2043
- "accounts/fireworks/models/kimi-k2p6",
2043
+ "accounts/fireworks/models/minimax-m3",
2044
2044
  );
2045
2045
  await fw.sendMessage([userMsg("hi")], {
2046
2046
  systemPrompt: "system",
2047
2047
  config: {
2048
2048
  effort: "max",
2049
- model: "accounts/fireworks/models/deepseek-v4-pro-0813",
2049
+ model: "accounts/fireworks/models/deepseek-v4p1-flash",
2050
2050
  },
2051
2051
  });
2052
2052
  expect(lastCreateParams!.reasoning_effort).toBe("max");
@@ -2111,7 +2111,7 @@ describe("FireworksProvider sparse reasoning_effort support (GLM 5.3)", () => {
2111
2111
 
2112
2112
  test("models without supportedEfforts are unaffected", async () => {
2113
2113
  expect(
2114
- await send("accounts/fireworks/models/deepseek-v4-pro-0813", "medium"),
2114
+ await send("accounts/fireworks/models/deepseek-v4p1-flash", "medium"),
2115
2115
  ).toBe("medium");
2116
2116
  });
2117
2117
  });
@@ -10,6 +10,17 @@ import {
10
10
  } from "../util/pricing.js";
11
11
 
12
12
  describe("resolvePricing", () => {
13
+ test.each([
14
+ ["accounts/fireworks/models/glm-5p2", 1.4 + 4.4],
15
+ ["accounts/fireworks/models/kimi-k2p6", 0.95 + 4.0],
16
+ ["accounts/fireworks/models/deepseek-v4-pro-0813", 1.32 + 3.96],
17
+ ["accounts/fireworks/models/deepseek-v4-flash-0731", 0.14 + 0.28],
18
+ ])("prices stored usage for retired Fireworks model %s", (model, cost) => {
19
+ const result = resolvePricing("fireworks", model, 1_000_000, 1_000_000);
20
+ expect(result.pricingStatus).toBe("priced");
21
+ expect(result.estimatedCostUsd).toBeCloseTo(cost, 10);
22
+ });
23
+
13
24
  describe("Anthropic models", () => {
14
25
  test("returns priced for claude-opus-4-6", () => {
15
26
  const result = resolvePricing(
@@ -100,7 +100,7 @@ describe("133-collapse-provider-connections migration", () => {
100
100
  source: "user",
101
101
  provider: "fireworks",
102
102
  provider_connection: "vellum",
103
- model: "accounts/fireworks/models/glm-5p2",
103
+ model: "accounts/fireworks/models/minimax-m3",
104
104
  },
105
105
  },
106
106
  },
@@ -257,7 +257,7 @@ describe("133-collapse-provider-connections migration", () => {
257
257
  encoded: {
258
258
  source: "user",
259
259
  provider: "vellum",
260
- model: "fireworks/accounts/fireworks/models/glm-5p2",
260
+ model: "fireworks/accounts/fireworks/models/minimax-m3",
261
261
  },
262
262
  },
263
263
  },
@@ -272,7 +272,7 @@ describe("133-collapse-provider-connections migration", () => {
272
272
  expect(llm.profiles.encoded).toEqual({
273
273
  source: "user",
274
274
  provider: "vellum",
275
- model: "accounts/fireworks/models/glm-5p2",
275
+ model: "accounts/fireworks/models/minimax-m3",
276
276
  });
277
277
  });
278
278