@vellumai/assistant 0.11.8-staging.1 → 0.11.8
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/docs/guardian-request-flow.md +3 -3
- package/openapi.yaml +0 -24
- package/package.json +1 -1
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +2 -0
- package/src/__tests__/channel-availability-routes.test.ts +15 -0
- package/src/__tests__/channel-readiness-routes.test.ts +96 -3
- package/src/__tests__/channel-readiness-service.test.ts +19 -4
- package/src/__tests__/config-loader-backfill.test.ts +2 -2
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/email-invite-adapter.test.ts +53 -12
- package/src/__tests__/external-binding-chat-name.test.ts +60 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +28 -0
- package/src/__tests__/managed-profile-guard.test.ts +1 -1
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/pricing.test.ts +13 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +16 -3
- package/src/api/responses/home.ts +0 -6
- package/src/config/__tests__/default-profile-catalog.test.ts +1 -1
- package/src/config/call-site-defaults.ts +1 -1
- package/src/config/default-profile-catalog.ts +12 -12
- package/src/config/default-profile-names.ts +2 -2
- package/src/email/byo-email-credential.test.ts +39 -0
- package/src/email/byo-email-credential.ts +76 -0
- package/src/email/registered-inbox.test.ts +272 -0
- package/src/email/registered-inbox.ts +169 -0
- package/src/messaging/providers/slack/approval-source.test.ts +70 -0
- package/src/messaging/providers/slack/approval-source.ts +18 -5
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +13 -14
- package/src/notifications/approval-card-data.ts +13 -5
- package/src/notifications/emit-signal.ts +5 -8
- package/src/notifications/guardian-feed-projection.ts +9 -74
- package/src/notifications/guardian-question-mode.ts +33 -1
- package/src/notifications/home-feed-side-effect.ts +0 -4
- package/src/persistence/external-conversation-store.ts +5 -1
- package/src/providers/anthropic/client.ts +1 -1
- package/src/providers/model-catalog.ts +54 -0
- package/src/providers/model-intents.ts +3 -3
- package/src/runtime/approval-source-link.ts +6 -0
- package/src/runtime/channel-invite-transports/email.ts +10 -9
- package/src/runtime/channel-readiness-service.ts +83 -23
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -1
- package/src/runtime/routes/channel-availability-routes.ts +18 -31
- package/src/runtime/routes/credential-routes.ts +3 -0
- package/src/runtime/routes/email-routes.ts +35 -47
- package/src/runtime/verification-outbound-actions.ts +8 -12
- package/src/tools/credentials/store.ts +3 -0
- package/src/util/pricing.ts +2 -1
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
* Email channel invite adapter.
|
|
3
3
|
*
|
|
4
4
|
* Resolves the assistant's email address for use in invite instructions.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The address is a managed inbox registration, which lives on the platform
|
|
6
|
+
* (nothing about one lands in workspace config), so it resolves through the
|
|
7
|
+
* shared registered-inbox reader. Returns `undefined` when no address is
|
|
8
|
+
* registered or the platform cannot be asked, which causes the invite
|
|
7
9
|
* instruction generator to emit generic "on Email" wording.
|
|
8
10
|
*
|
|
9
11
|
* Email invites use the universal 6-digit code path for redemption, so
|
|
10
|
-
* this adapter only implements `resolveChannelHandleAsync
|
|
12
|
+
* this adapter only implements `resolveChannelHandleAsync`, with no
|
|
11
13
|
* `buildShareLink` or `extractInboundToken` needed.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
import {
|
|
16
|
+
import { resolveRegisteredInbox } from "../../email/registered-inbox.js";
|
|
15
17
|
import type { ChannelInviteAdapter } from "../channel-invite-types.js";
|
|
16
18
|
|
|
17
19
|
// ---------------------------------------------------------------------------
|
|
@@ -23,13 +25,12 @@ export const emailInviteAdapter: ChannelInviteAdapter = {
|
|
|
23
25
|
|
|
24
26
|
async resolveChannelHandleAsync(): Promise<string | undefined> {
|
|
25
27
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return address;
|
|
28
|
+
const inbox = await resolveRegisteredInbox();
|
|
29
|
+
if (inbox.status === "registered") {
|
|
30
|
+
return inbox.address;
|
|
30
31
|
}
|
|
31
32
|
} catch {
|
|
32
|
-
//
|
|
33
|
+
// Platform unavailable; fall through to generic wording
|
|
33
34
|
}
|
|
34
35
|
return undefined;
|
|
35
36
|
},
|
|
@@ -283,32 +283,92 @@ const emailProbe: ChannelProbe = {
|
|
|
283
283
|
"Email invite code redemption is enabled",
|
|
284
284
|
"Email invite code redemption is disabled",
|
|
285
285
|
),
|
|
286
|
-
|
|
286
|
+
// Managed callbacks allowed: inbound email arrives through the platform
|
|
287
|
+
// callback route the gateway registers (`registerEmailCallbackRoute`,
|
|
288
|
+
// the same pattern as Telegram's webhook route), so a platform-connected
|
|
289
|
+
// deployment with no public ingress URL still receives email.
|
|
290
|
+
await checkIngress(true),
|
|
287
291
|
];
|
|
288
292
|
},
|
|
293
|
+
/**
|
|
294
|
+
* Ask the platform whether an inbox address is registered, falling back to
|
|
295
|
+
* a "your own" provider credential. The platform's email-addresses API is
|
|
296
|
+
* the only writer of managed inbox registrations (nothing about one lands
|
|
297
|
+
* in workspace config), and a BYO deployment's configuration claim is its
|
|
298
|
+
* stored provider API key, so those are the only sources this check may
|
|
299
|
+
* read.
|
|
300
|
+
*
|
|
301
|
+
* Three outcomes, matching the Telegram probe: an answer passes or fails
|
|
302
|
+
* the check outright, and an unreachable platform (with no BYO credential
|
|
303
|
+
* to fall back to) is indeterminate, which is not evidence of a fault and
|
|
304
|
+
* must not report the channel broken.
|
|
305
|
+
*/
|
|
289
306
|
async runRemoteChecks(): Promise<ReadinessCheckResult[]> {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
307
|
+
// Imported here rather than at module scope, matching the other probes:
|
|
308
|
+
// the platform client pulls in a module graph that unrelated consumers of
|
|
309
|
+
// this service should not have to mock.
|
|
310
|
+
const { resolveRegisteredInbox } =
|
|
311
|
+
await import("../email/registered-inbox.js");
|
|
312
|
+
// Fresh, because this service already caches remote checks for
|
|
313
|
+
// REMOTE_TTL_MS; layering the resolver's own cache under that would make
|
|
314
|
+
// an explicit readiness refresh serve a stale answer anyway.
|
|
315
|
+
const inbox = await resolveRegisteredInbox({ fresh: true });
|
|
316
|
+
|
|
317
|
+
// Published identifier (see the Telegram probe's `webhook_delivery` note):
|
|
318
|
+
// external clients search readiness responses for this name.
|
|
319
|
+
const name = "inbox_configured";
|
|
320
|
+
|
|
321
|
+
if (inbox.status !== "registered") {
|
|
322
|
+
const { resolveConfiguredByoEmailService } =
|
|
323
|
+
await import("../email/byo-email-credential.js");
|
|
324
|
+
const byoService = await resolveConfiguredByoEmailService();
|
|
325
|
+
if (byoService) {
|
|
326
|
+
return [
|
|
327
|
+
{
|
|
328
|
+
name,
|
|
329
|
+
passed: true,
|
|
330
|
+
message: `Email is configured through your own provider (${byoService} API key is stored)`,
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
switch (inbox.status) {
|
|
337
|
+
case "registered":
|
|
338
|
+
return [
|
|
339
|
+
{
|
|
340
|
+
name,
|
|
341
|
+
passed: true,
|
|
342
|
+
message: `Inbox address is registered (${inbox.address})`,
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
case "none":
|
|
346
|
+
return [
|
|
347
|
+
{
|
|
348
|
+
name,
|
|
349
|
+
passed: false,
|
|
350
|
+
message:
|
|
351
|
+
"No inbox address registered. Register one with: assistant email register <username>, or configure your own provider (Resend or Mailgun)",
|
|
352
|
+
},
|
|
353
|
+
];
|
|
354
|
+
case "no_platform":
|
|
355
|
+
return [
|
|
356
|
+
{
|
|
357
|
+
name,
|
|
358
|
+
passed: false,
|
|
359
|
+
message:
|
|
360
|
+
"Email is not configured. Connect the platform for a managed inbox (assistant platform connect), or configure your own provider (Resend or Mailgun)",
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
case "unavailable":
|
|
364
|
+
return [
|
|
365
|
+
{
|
|
366
|
+
name,
|
|
367
|
+
passed: true,
|
|
368
|
+
indeterminate: true,
|
|
369
|
+
message: `Could not reach the platform to check inbox registration (${inbox.detail})`,
|
|
370
|
+
},
|
|
371
|
+
];
|
|
312
372
|
}
|
|
313
373
|
},
|
|
314
374
|
};
|
|
@@ -1771,7 +1771,7 @@ describe("config invariant flag enrichment", () => {
|
|
|
1771
1771
|
expect(profiles.custom!).not.toHaveProperty("invariant");
|
|
1772
1772
|
});
|
|
1773
1773
|
|
|
1774
|
-
test("every managed default is invariant on the wire,
|
|
1774
|
+
test("every managed default is invariant on the wire, Fast included", async () => {
|
|
1775
1775
|
// The clients drive their read-only lock off this flag, so a default that
|
|
1776
1776
|
// resolves from the catalog with no workspace stub must still carry it.
|
|
1777
1777
|
const body = await configGetRoute.handler({});
|
|
@@ -26,7 +26,8 @@ import {
|
|
|
26
26
|
type ChannelInfo,
|
|
27
27
|
} from "../../channels/types.js";
|
|
28
28
|
import { getConfig } from "../../config/loader.js";
|
|
29
|
-
import {
|
|
29
|
+
import { resolveConfiguredByoEmailService } from "../../email/byo-email-credential.js";
|
|
30
|
+
import { resolveRegisteredInbox } from "../../email/registered-inbox.js";
|
|
30
31
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
31
32
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
32
33
|
|
|
@@ -39,45 +40,31 @@ const BASE_AVAILABLE_CHANNELS: readonly ChannelId[] = [
|
|
|
39
40
|
"phone",
|
|
40
41
|
] as const;
|
|
41
42
|
|
|
42
|
-
interface EmailAddressListResponse {
|
|
43
|
-
count?: number;
|
|
44
|
-
results?: Array<{ id: string; address: string }>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
43
|
/**
|
|
48
|
-
* Best-effort check that
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* platform is
|
|
44
|
+
* Best-effort check that email is configured for this assistant: a managed
|
|
45
|
+
* inbox registered on the platform, or a "your own" provider credential
|
|
46
|
+
* (whose gateway webhook routes carry inbound just as the managed pipeline
|
|
47
|
+
* does). An unavailable platform is treated as "no inbox": we prefer to
|
|
48
|
+
* under-report than block the entire Contacts page when the platform is
|
|
49
|
+
* briefly unreachable.
|
|
50
|
+
*
|
|
51
|
+
* Fresh, preserving this route's long-standing live-read-per-request
|
|
52
|
+
* behavior: availability is fetched on surface loads, not on a poll, so it
|
|
53
|
+
* does not need the resolver's cache and should not inherit its staleness.
|
|
52
54
|
*/
|
|
53
|
-
async function
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const response = await client.fetch(
|
|
61
|
-
`/v1/assistants/${client.platformAssistantId}/email-addresses/`,
|
|
62
|
-
);
|
|
63
|
-
if (!response.ok) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
const data = (await response.json()) as EmailAddressListResponse;
|
|
67
|
-
if (typeof data.count === "number") {
|
|
68
|
-
return data.count > 0;
|
|
69
|
-
}
|
|
70
|
-
return Array.isArray(data.results) && data.results.length > 0;
|
|
71
|
-
} catch {
|
|
72
|
-
return false;
|
|
55
|
+
async function hasConfiguredEmail(): Promise<boolean> {
|
|
56
|
+
const inbox = await resolveRegisteredInbox({ fresh: true });
|
|
57
|
+
if (inbox.status === "registered") {
|
|
58
|
+
return true;
|
|
73
59
|
}
|
|
60
|
+
return (await resolveConfiguredByoEmailService()) !== undefined;
|
|
74
61
|
}
|
|
75
62
|
|
|
76
63
|
async function handleGetChannelAvailability(
|
|
77
64
|
_args: RouteHandlerArgs,
|
|
78
65
|
): Promise<{ channels: AvailableChannel[] }> {
|
|
79
66
|
const ids: ChannelId[] = [...BASE_AVAILABLE_CHANNELS];
|
|
80
|
-
if (await
|
|
67
|
+
if (await hasConfiguredEmail()) {
|
|
81
68
|
ids.push("email");
|
|
82
69
|
}
|
|
83
70
|
if (isA2AEnabled(getConfig())) {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type ManagedCredentialDescriptor,
|
|
24
24
|
} from "../../credential-execution/managed-catalog.js";
|
|
25
25
|
import { buildForChatSentinel } from "../../daemon/chat-credential-redaction.js";
|
|
26
|
+
import { invalidateEmailReadinessForByoCredential } from "../../email/byo-email-credential.js";
|
|
26
27
|
import {
|
|
27
28
|
disconnectOAuthProvider,
|
|
28
29
|
getConnectionByProvider,
|
|
@@ -551,6 +552,8 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
551
552
|
|
|
552
553
|
invalidateConnectionsAfterCredentialDelete(affectedConnections);
|
|
553
554
|
|
|
555
|
+
await invalidateEmailReadinessForByoCredential(service);
|
|
556
|
+
|
|
554
557
|
return { service, field, affectedConnections };
|
|
555
558
|
}
|
|
556
559
|
|
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
+
import { getReadinessService } from "../../daemon/handlers/config-channels.js";
|
|
10
11
|
import { markdownToEmailHtml } from "../../email/html-renderer.js";
|
|
12
|
+
import {
|
|
13
|
+
invalidateRegisteredInboxCache,
|
|
14
|
+
listEmailAddresses,
|
|
15
|
+
type RegisteredEmailAddress,
|
|
16
|
+
} from "../../email/registered-inbox.js";
|
|
11
17
|
import { VellumPlatformClient } from "../../platform/client.js";
|
|
12
18
|
import { LOCAL_PRINCIPALS } from "../auth/route-policy.js";
|
|
13
19
|
import {
|
|
@@ -70,29 +76,42 @@ async function handleEmailRegister({ body = {} }: RouteHandlerArgs) {
|
|
|
70
76
|
address: string;
|
|
71
77
|
created_at: string;
|
|
72
78
|
};
|
|
79
|
+
invalidateEmailReadinessState();
|
|
73
80
|
return data;
|
|
74
81
|
}
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
/**
|
|
84
|
+
* A registration change must be visible on the next readiness read: both the
|
|
85
|
+
* registered-inbox cache and the readiness service's cached email snapshot
|
|
86
|
+
* would otherwise keep answering with the pre-change state for their TTL.
|
|
87
|
+
*/
|
|
88
|
+
function invalidateEmailReadinessState(): void {
|
|
89
|
+
invalidateRegisteredInboxCache();
|
|
90
|
+
getReadinessService().invalidateChannel("email");
|
|
91
|
+
}
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The registered addresses, or the route-appropriate error: a listing
|
|
95
|
+
* failure maps to LIST_FAILED with the platform's status when it sent one.
|
|
96
|
+
*/
|
|
97
|
+
async function requireEmailAddressList(
|
|
98
|
+
client: VellumPlatformClient,
|
|
99
|
+
): Promise<RegisteredEmailAddress[]> {
|
|
100
|
+
const list = await listEmailAddresses(client);
|
|
101
|
+
if (!list.ok) {
|
|
84
102
|
throw new RouteError(
|
|
85
|
-
`Failed to list email addresses:
|
|
103
|
+
`Failed to list email addresses: ${list.detail}`,
|
|
86
104
|
"LIST_FAILED",
|
|
87
|
-
|
|
105
|
+
list.status ?? 502,
|
|
88
106
|
);
|
|
89
107
|
}
|
|
108
|
+
return list.addresses;
|
|
109
|
+
}
|
|
90
110
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|
|
111
|
+
async function handleEmailUnregister(_args: RouteHandlerArgs) {
|
|
112
|
+
const client = await requireClient();
|
|
94
113
|
|
|
95
|
-
const addresses =
|
|
114
|
+
const addresses = await requireEmailAddressList(client);
|
|
96
115
|
if (addresses.length === 0) {
|
|
97
116
|
throw new NotFoundError("No email address registered for this assistant.");
|
|
98
117
|
}
|
|
@@ -117,29 +136,14 @@ async function handleEmailUnregister(_args: RouteHandlerArgs) {
|
|
|
117
136
|
);
|
|
118
137
|
}
|
|
119
138
|
|
|
139
|
+
invalidateEmailReadinessState();
|
|
120
140
|
return { unregistered: target.address };
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
async function handleEmailStatus(_args: RouteHandlerArgs) {
|
|
124
144
|
const client = await requireClient();
|
|
125
145
|
|
|
126
|
-
const
|
|
127
|
-
`/v1/assistants/${client.platformAssistantId}/email-addresses/`,
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
if (!listResponse.ok) {
|
|
131
|
-
throw new RouteError(
|
|
132
|
-
`Failed to list email addresses: HTTP ${listResponse.status}`,
|
|
133
|
-
"LIST_FAILED",
|
|
134
|
-
listResponse.status,
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const listData = (await listResponse.json()) as {
|
|
139
|
-
results: { id: string; address: string }[];
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const addresses = listData.results ?? [];
|
|
146
|
+
const addresses = await requireEmailAddressList(client);
|
|
143
147
|
if (addresses.length === 0) {
|
|
144
148
|
throw new NotFoundError(
|
|
145
149
|
"No email address registered for this assistant. Run: assistant email register <username>",
|
|
@@ -258,23 +262,7 @@ async function handleEmailSend({ body = {} }: RouteHandlerArgs) {
|
|
|
258
262
|
const client = await requireClient();
|
|
259
263
|
|
|
260
264
|
// Resolve "from" address
|
|
261
|
-
const
|
|
262
|
-
`/v1/assistants/${client.platformAssistantId}/email-addresses/`,
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
if (!listResponse.ok) {
|
|
266
|
-
throw new RouteError(
|
|
267
|
-
`Failed to list email addresses: HTTP ${listResponse.status}`,
|
|
268
|
-
"LIST_FAILED",
|
|
269
|
-
listResponse.status,
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const listData = (await listResponse.json()) as {
|
|
274
|
-
results: { id: string; address: string }[];
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
const addresses = listData.results ?? [];
|
|
265
|
+
const addresses = await requireEmailAddressList(client);
|
|
278
266
|
if (addresses.length === 0) {
|
|
279
267
|
throw new NotFoundError(
|
|
280
268
|
"No email address registered for this assistant. Run: assistant email register <username>",
|
|
@@ -575,27 +575,23 @@ export function deliverVerificationEmail(
|
|
|
575
575
|
return;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
);
|
|
581
|
-
if (!
|
|
578
|
+
const { listEmailAddresses } =
|
|
579
|
+
await import("../email/registered-inbox.js");
|
|
580
|
+
const list = await listEmailAddresses(client);
|
|
581
|
+
if (!list.ok) {
|
|
582
582
|
log.error(
|
|
583
|
-
{ status:
|
|
583
|
+
{ status: list.status, detail: list.detail },
|
|
584
584
|
"Failed to list email addresses for verification",
|
|
585
585
|
);
|
|
586
586
|
return;
|
|
587
587
|
}
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
};
|
|
591
|
-
const addresses = listData.results ?? [];
|
|
592
|
-
if (addresses.length === 0) {
|
|
588
|
+
const fromAddress = list.addresses[0]?.address;
|
|
589
|
+
if (!fromAddress) {
|
|
593
590
|
log.error(
|
|
594
|
-
"No email address registered
|
|
591
|
+
"No email address registered; cannot deliver verification email",
|
|
595
592
|
);
|
|
596
593
|
return;
|
|
597
594
|
}
|
|
598
|
-
const fromAddress = addresses[0].address;
|
|
599
595
|
|
|
600
596
|
const { markdownToEmailHtml } = await import("../email/html-renderer.js");
|
|
601
597
|
const html = markdownToEmailHtml(text);
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
ACP_SERVICE,
|
|
29
29
|
assertAcpCredentialFormat,
|
|
30
30
|
} from "../../acp/acp-credentials.js";
|
|
31
|
+
import { invalidateEmailReadinessForByoCredential } from "../../email/byo-email-credential.js";
|
|
31
32
|
import { credentialKey } from "../../security/credential-key.js";
|
|
32
33
|
import { normalizeSecretValue } from "../../security/secret-normalize.js";
|
|
33
34
|
import {
|
|
@@ -176,5 +177,7 @@ export async function storeCredentialValue(
|
|
|
176
177
|
);
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
await invalidateEmailReadinessForByoCredential(service);
|
|
181
|
+
|
|
179
182
|
return { credentialId: metadata.credentialId, service, field };
|
|
180
183
|
}
|
package/src/util/pricing.ts
CHANGED
|
@@ -380,7 +380,8 @@ function calculateUsageCost(
|
|
|
380
380
|
directInputCost +
|
|
381
381
|
outputCost +
|
|
382
382
|
calculateTokenCost(
|
|
383
|
-
effectivePricing.
|
|
383
|
+
effectivePricing.cacheReadPer1M ??
|
|
384
|
+
effectivePricing.inputPer1M * ANTHROPIC_PROMPT_CACHE_MULTIPLIERS.read,
|
|
384
385
|
usage.cacheReadInputTokens,
|
|
385
386
|
) +
|
|
386
387
|
calculateTokenCost(
|