@opengeni/core 3.1.2 → 3.2.2
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/dist/dependencies.d.ts +3 -0
- package/dist/domain/vaults/external-provider-connections.d.ts +126 -0
- package/dist/domain/vaults/index.d.ts +2 -1
- package/dist/domain/vaults/mapping.d.ts +18 -2
- package/dist/domain/vaults/provider-registry.d.ts +4 -1
- package/dist/domain/vaults/provider-sync.d.ts +135 -21
- package/dist/domain/vaults/provider-writes.d.ts +102 -12
- package/dist/domain/vaults/remote-provider-runtime.d.ts +7 -0
- package/dist/domain/vaults/secret-resolver.d.ts +29 -1
- package/dist/goal-admission.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1432 -375
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
- package/src/dependencies.ts +3 -0
- package/src/domain/realtime-voice-billing.ts +4 -1
- package/src/domain/vaults/external-provider-connections.ts +395 -0
- package/src/domain/vaults/index.ts +8 -0
- package/src/domain/vaults/mapping.ts +95 -1
- package/src/domain/vaults/provider-registry.ts +29 -0
- package/src/domain/vaults/provider-sync.ts +492 -136
- package/src/domain/vaults/provider-writes.ts +415 -71
- package/src/domain/vaults/remote-provider-runtime.ts +129 -0
- package/src/domain/vaults/secret-resolver.ts +203 -33
- package/src/domain/vaults/vault-service.ts +226 -34
- package/src/domain/voice-input-billing.ts +5 -1
- package/src/index.ts +2 -0
|
@@ -56,8 +56,10 @@ import {
|
|
|
56
56
|
ensureTaskVault,
|
|
57
57
|
getSession,
|
|
58
58
|
getSessionSecretsView,
|
|
59
|
+
getProviderVaultItemRef,
|
|
59
60
|
getVault,
|
|
60
61
|
getVaultItem,
|
|
62
|
+
isVaultError,
|
|
61
63
|
listVaultFieldVersions,
|
|
62
64
|
listVaultItems,
|
|
63
65
|
listVaultsForSubject,
|
|
@@ -95,12 +97,21 @@ import { vaultErrorCodeForProvider } from "./provider-registry";
|
|
|
95
97
|
import {
|
|
96
98
|
LINKED_VAULT_OPEN_WAIT_MS,
|
|
97
99
|
LINKED_VAULT_SEARCH_MAX_AGE_MS,
|
|
100
|
+
providerVaultFreshness,
|
|
101
|
+
refreshLinkedVaults,
|
|
98
102
|
refreshStaleLinkedVaults,
|
|
99
103
|
} from "./provider-sync";
|
|
100
104
|
import {
|
|
101
105
|
archiveProviderVaultItem,
|
|
106
|
+
assertProviderVaultAccepts,
|
|
107
|
+
assertProviderVaultReleases,
|
|
108
|
+
observeProviderItemVersion,
|
|
109
|
+
refuseProviderOperation,
|
|
110
|
+
savedNotListedError,
|
|
102
111
|
writeProviderVaultItem,
|
|
112
|
+
writeProviderVaultItemOutcome,
|
|
103
113
|
type ProviderWriteDeps,
|
|
114
|
+
type ProviderWriteOutcome,
|
|
104
115
|
} from "./provider-writes";
|
|
105
116
|
import { chooseSecretCandidate, createSecretResolver } from "./secret-resolver";
|
|
106
117
|
import type { VaultRequestActor, VaultServiceDeps } from "./types";
|
|
@@ -423,6 +434,21 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
423
434
|
return deps.encryptionKey;
|
|
424
435
|
}
|
|
425
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Restore and permanent deletion are Opengeni-hosted operations. Connected
|
|
439
|
+
* items are archived recoverably in their provider and restored there, so
|
|
440
|
+
* these refuse with the reason instead of reaching any provider.
|
|
441
|
+
*/
|
|
442
|
+
async function refuseForConnectedItem(
|
|
443
|
+
context: VaultDbContext,
|
|
444
|
+
itemId: string,
|
|
445
|
+
operation: "restoreItem" | "deleteItem",
|
|
446
|
+
): Promise<void> {
|
|
447
|
+
const item = await getVaultItem(deps.db, context, { itemId });
|
|
448
|
+
if (item.vaultSource === "opengeni") return;
|
|
449
|
+
refuseProviderOperation(await getVault(deps.db, context, { vaultId: item.vaultId }), operation);
|
|
450
|
+
}
|
|
451
|
+
|
|
426
452
|
/**
|
|
427
453
|
* Move or copy an item when either side is a connected (provider) vault.
|
|
428
454
|
* Values never live in both stores' databases at once: each field is read
|
|
@@ -430,6 +456,25 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
430
456
|
* written to the target through its own path, and a move then archives the
|
|
431
457
|
* source so the old item stays recoverable. Opengeni-to-Opengeni moves keep
|
|
432
458
|
* the single-statement database move, which preserves the item id.
|
|
459
|
+
*
|
|
460
|
+
* Everything the target must accept (item type, one-time password, files)
|
|
461
|
+
* and, for a move, a recoverable archive of the original is checked before
|
|
462
|
+
* any value is read or written. The original is archived only after the
|
|
463
|
+
* target confirmed the write; an unknown or failed target write leaves it
|
|
464
|
+
* untouched, and a confirmed copy whose original could not be archived is
|
|
465
|
+
* reported as saved, so the move is not repeated.
|
|
466
|
+
*
|
|
467
|
+
* A move out of a connected vault records the provider's version of the
|
|
468
|
+
* original from a fresh listing before anything is copied. A version that
|
|
469
|
+
* differs from the mirrored one (whose field list the copy follows) is
|
|
470
|
+
* refused with `provider_conflict` before anything is saved: the vault must
|
|
471
|
+
* be synced first. The original is listed again just before archiving; if
|
|
472
|
+
* the provider then reports another version, no longer lists it, or cannot
|
|
473
|
+
* be asked, the original is kept and the copy is reported as saved with
|
|
474
|
+
* `sourceArchived: false`. These are separate checks, not an atomic read:
|
|
475
|
+
* values are read live between them, and providers offer no conditional
|
|
476
|
+
* archive, so a change landing after the last check is still archived
|
|
477
|
+
* (recoverably). A provider that does not version items gets no drift check.
|
|
433
478
|
*/
|
|
434
479
|
async function moveAcrossProviders(input: {
|
|
435
480
|
context: VaultDbContext;
|
|
@@ -465,6 +510,53 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
465
510
|
},
|
|
466
511
|
);
|
|
467
512
|
}
|
|
513
|
+
if (target.source !== "opengeni") {
|
|
514
|
+
await assertProviderVaultAccepts(input.providerWrites, target, source);
|
|
515
|
+
}
|
|
516
|
+
if (input.mode === "move" && source.vaultSource !== "opengeni") {
|
|
517
|
+
await assertProviderVaultReleases(input.providerWrites, sourceVault);
|
|
518
|
+
}
|
|
519
|
+
// The provider's version before anything is copied. It must equal the
|
|
520
|
+
// mirrored version the field list below comes from; an edit after this
|
|
521
|
+
// observation makes the pre-archive check differ and keeps the original.
|
|
522
|
+
// Values are read live afterwards, so this is not an atomic snapshot.
|
|
523
|
+
let pinned: { providerItemRef: string; version: string } | null = null;
|
|
524
|
+
if (input.mode === "move" && source.vaultSource !== "opengeni") {
|
|
525
|
+
const mirrored = await getProviderVaultItemRef(deps.db, input.context, {
|
|
526
|
+
vaultId: sourceVault.id,
|
|
527
|
+
itemId: source.id,
|
|
528
|
+
});
|
|
529
|
+
const { providerItemRef } = mirrored;
|
|
530
|
+
const observed = await observeProviderItemVersion(input.providerWrites, {
|
|
531
|
+
vault: sourceVault,
|
|
532
|
+
providerItemRef,
|
|
533
|
+
});
|
|
534
|
+
if (!observed.listed) {
|
|
535
|
+
throw new VaultError(
|
|
536
|
+
"vault_item_not_found",
|
|
537
|
+
`${source.name} is not listed in ${sourceVault.name} right now; sync the vault and try again`,
|
|
538
|
+
{ itemId: source.id, vaultId: sourceVault.id },
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
if (observed.version !== null) {
|
|
542
|
+
if (
|
|
543
|
+
mirrored.providerVersion !== observed.version ||
|
|
544
|
+
mirrored.generation !== source.generation
|
|
545
|
+
) {
|
|
546
|
+
throw new VaultError(
|
|
547
|
+
"provider_conflict",
|
|
548
|
+
`${source.name} changed in ${sourceVault.name} since Opengeni last synced it, so its fields here may be out of date. Nothing was moved: sync ${sourceVault.name}, then move it again.`,
|
|
549
|
+
{
|
|
550
|
+
itemId: source.id,
|
|
551
|
+
vaultId: sourceVault.id,
|
|
552
|
+
mirroredVersion: mirrored.providerVersion,
|
|
553
|
+
providerVersion: observed.version,
|
|
554
|
+
},
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
pinned = { providerItemRef, version: observed.version };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
468
560
|
const name = input.newName ?? source.name;
|
|
469
561
|
const fields: FieldSource[] = [];
|
|
470
562
|
for (const field of source.fields) {
|
|
@@ -493,14 +585,20 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
493
585
|
...(source.expiresAt ? { expiresAt: source.expiresAt } : {}),
|
|
494
586
|
fields,
|
|
495
587
|
};
|
|
496
|
-
let created: VaultItemRow;
|
|
588
|
+
let created: VaultItemRow | null = null;
|
|
589
|
+
let notListed: Extract<ProviderWriteOutcome, { status: "saved_not_listed" }> | null = null;
|
|
497
590
|
if (target.source !== "opengeni") {
|
|
498
|
-
|
|
591
|
+
// A failed or unknown target write throws here, before the original is touched.
|
|
592
|
+
const outcome = await writeProviderVaultItemOutcome(input.providerWrites, {
|
|
499
593
|
vault: target,
|
|
500
594
|
existing: null,
|
|
501
595
|
change,
|
|
502
596
|
});
|
|
503
|
-
|
|
597
|
+
if (outcome.status === "listed") {
|
|
598
|
+
created = await getVaultItem(deps.db, input.context, { itemId: outcome.itemId });
|
|
599
|
+
} else {
|
|
600
|
+
notListed = outcome;
|
|
601
|
+
}
|
|
504
602
|
} else {
|
|
505
603
|
const write = await itemWrite(
|
|
506
604
|
change,
|
|
@@ -515,16 +613,67 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
515
613
|
});
|
|
516
614
|
}
|
|
517
615
|
if (input.mode === "move") {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
616
|
+
try {
|
|
617
|
+
if (source.vaultSource !== "opengeni") {
|
|
618
|
+
if (pinned) await assertSourceUnchanged(input.providerWrites, sourceVault, pinned);
|
|
619
|
+
await archiveProviderVaultItem(input.providerWrites, {
|
|
620
|
+
vault: sourceVault,
|
|
621
|
+
itemId: source.id,
|
|
622
|
+
});
|
|
623
|
+
} else {
|
|
624
|
+
await archiveVaultItem(deps.db, input.context, { itemId: source.id });
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
const changed = isVaultError(error) && error.details?.sourceChanged === true;
|
|
628
|
+
throw new VaultError(
|
|
629
|
+
isVaultError(error) ? error.code : "provider_outcome_unknown",
|
|
630
|
+
changed
|
|
631
|
+
? `Saved ${name} in ${target.name} with the values that were read, but the original in ${sourceVault.name} ${error.message}, so it was kept. Do not move it again: compare the two, then update the copy or archive the original in ${sourceVault.name}.`
|
|
632
|
+
: `Saved ${name} in ${target.name}, but archiving the original in ${sourceVault.name} did not complete${isVaultError(error) ? ` (${error.message})` : ""}. Do not move it again: check ${sourceVault.name} and archive the original there if it is still listed.`,
|
|
633
|
+
{
|
|
634
|
+
saved: true,
|
|
635
|
+
sourceArchived: false,
|
|
636
|
+
...(changed ? { sourceChanged: true } : {}),
|
|
637
|
+
vaultId: target.id,
|
|
638
|
+
...(created ? { itemId: created.id } : {}),
|
|
639
|
+
sourceItemId: source.id,
|
|
640
|
+
sourceVaultId: sourceVault.id,
|
|
641
|
+
},
|
|
642
|
+
);
|
|
525
643
|
}
|
|
526
644
|
}
|
|
527
|
-
|
|
645
|
+
if (notListed) {
|
|
646
|
+
const error = savedNotListedError(target, name, notListed);
|
|
647
|
+
throw new VaultError(error.code, error.message, {
|
|
648
|
+
...error.details,
|
|
649
|
+
...(input.mode === "move" ? { sourceArchived: true } : {}),
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
return created!;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Refuses (before archiving) an original the provider no longer reports at
|
|
657
|
+
* the version its values were copied from. A failed listing throws its own
|
|
658
|
+
* provider error, so the original is kept either way.
|
|
659
|
+
*/
|
|
660
|
+
async function assertSourceUnchanged(
|
|
661
|
+
providerWrites: ProviderWriteDeps,
|
|
662
|
+
vault: VaultRow,
|
|
663
|
+
pinned: { providerItemRef: string; version: string },
|
|
664
|
+
): Promise<void> {
|
|
665
|
+
const now = await observeProviderItemVersion(providerWrites, {
|
|
666
|
+
vault,
|
|
667
|
+
providerItemRef: pinned.providerItemRef,
|
|
668
|
+
});
|
|
669
|
+
if (now.listed && now.version === pinned.version) return;
|
|
670
|
+
throw new VaultError(
|
|
671
|
+
now.listed ? "vault_generation_conflict" : "vault_item_not_found",
|
|
672
|
+
now.listed
|
|
673
|
+
? "changed after its values were read"
|
|
674
|
+
: "is no longer listed at the version that was read",
|
|
675
|
+
{ sourceChanged: true, vaultId: vault.id },
|
|
676
|
+
);
|
|
528
677
|
}
|
|
529
678
|
|
|
530
679
|
function toItem(row: VaultItemRow): VaultItem {
|
|
@@ -550,7 +699,12 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
550
699
|
// Providers charge the budget per call themselves.
|
|
551
700
|
const budget = createProviderCallBudget(VAULT_LIMITS.providerReadsPerAttempt);
|
|
552
701
|
const [value] = await provider.readFields(
|
|
553
|
-
|
|
702
|
+
// The durable epoch invalidates provider value caches after a refresh anywhere.
|
|
703
|
+
{
|
|
704
|
+
connectionId: read.providerConnectionId,
|
|
705
|
+
budget,
|
|
706
|
+
cacheEpoch: read.providerCacheEpoch ?? null,
|
|
707
|
+
},
|
|
554
708
|
read.providerVaultRef ?? "",
|
|
555
709
|
read.providerItemRef ?? "",
|
|
556
710
|
[read.providerFieldRef ?? ""],
|
|
@@ -618,6 +772,10 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
618
772
|
const vaults = await listVaultsForSubject(deps.db, principalContext(principal), {
|
|
619
773
|
workspaceId: principal.workspaceId,
|
|
620
774
|
});
|
|
775
|
+
return agentVaultFrom(vaults, nameOrId);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function agentVaultFrom(vaults: readonly VaultRow[], nameOrId: string): VaultRow {
|
|
621
779
|
const byId = vaults.find((vault) => vault.id === nameOrId.toLowerCase());
|
|
622
780
|
if (byId) return byId;
|
|
623
781
|
const matches = vaults.filter((vault) => secretNameKey(vault.name) === secretNameKey(nameOrId));
|
|
@@ -874,13 +1032,16 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
874
1032
|
},
|
|
875
1033
|
|
|
876
1034
|
async restoreItem(actor, workspaceId, itemId) {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
);
|
|
1035
|
+
const context = subjectContext(actor, workspaceId);
|
|
1036
|
+
await refuseForConnectedItem(context, itemId, "restoreItem");
|
|
1037
|
+
return toItem(await restoreVaultItem(deps.db, context, { itemId }));
|
|
880
1038
|
},
|
|
881
1039
|
|
|
882
1040
|
async deleteItemPermanently(actor, workspaceId, itemId) {
|
|
883
|
-
|
|
1041
|
+
const context = subjectContext(actor, workspaceId);
|
|
1042
|
+
// Never a permanent provider deletion: Archive is the only way an item leaves.
|
|
1043
|
+
await refuseForConnectedItem(context, itemId, "deleteItem");
|
|
1044
|
+
await deleteVaultItemPermanently(deps.db, context, { itemId });
|
|
884
1045
|
},
|
|
885
1046
|
|
|
886
1047
|
async moveItem(actor, workspaceId, itemId, req) {
|
|
@@ -924,6 +1085,12 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
924
1085
|
async restoreFieldVersion(actor, workspaceId, itemId, fieldId, version) {
|
|
925
1086
|
const context = subjectContext(actor, workspaceId);
|
|
926
1087
|
const existing = await getVaultItem(deps.db, context, { itemId });
|
|
1088
|
+
if (existing.vaultSource !== "opengeni") {
|
|
1089
|
+
refuseProviderOperation(
|
|
1090
|
+
await getVault(deps.db, context, { vaultId: existing.vaultId }),
|
|
1091
|
+
"restoreFieldHistory",
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
927
1094
|
const field = existing.fields.find((candidate) => candidate.id === fieldId);
|
|
928
1095
|
if (!field) throw new VaultError("vault_field_not_found", "field not found", { fieldId });
|
|
929
1096
|
return toItem(
|
|
@@ -963,29 +1130,48 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
963
1130
|
let reachable = await listVaultsForSubject(deps.db, context, {
|
|
964
1131
|
workspaceId: principal.workspaceId,
|
|
965
1132
|
});
|
|
1133
|
+
let scoped: VaultRow[] =
|
|
1134
|
+
input.vault !== undefined ? [agentVaultFrom(reachable, input.vault)] : reachable;
|
|
966
1135
|
// Items added straight in a connected provider (1Password) appear here
|
|
967
|
-
// without anyone pressing Sync: stale mirrors
|
|
968
|
-
|
|
969
|
-
|
|
1136
|
+
// without anyone pressing Sync: stale mirrors in the search's scope
|
|
1137
|
+
// refresh before the scan. `refresh` (first page only) re-lists them and
|
|
1138
|
+
// bypasses provider caches for later reads, never an active rate limit.
|
|
1139
|
+
let refreshing = new Set<string>();
|
|
1140
|
+
const connected = scoped.filter((vault) => vault.source !== "opengeni");
|
|
1141
|
+
if (deps.providers && connected.length > 0) {
|
|
1142
|
+
const report = await refreshLinkedVaults({
|
|
970
1143
|
db: deps.db,
|
|
971
1144
|
context,
|
|
972
|
-
vaults:
|
|
1145
|
+
vaults: connected,
|
|
973
1146
|
providers: deps.providers,
|
|
974
1147
|
maxAgeMs: LINKED_VAULT_SEARCH_MAX_AGE_MS,
|
|
1148
|
+
...(input.refresh && input.cursor === undefined ? { force: true } : {}),
|
|
975
1149
|
});
|
|
976
|
-
|
|
1150
|
+
refreshing = new Set(report.pending);
|
|
1151
|
+
if (
|
|
1152
|
+
report.outcomes.some((outcome) => outcome.state === "done" || outcome.state === "failed")
|
|
1153
|
+
) {
|
|
977
1154
|
reachable = await listVaultsForSubject(deps.db, context, {
|
|
978
1155
|
workspaceId: principal.workspaceId,
|
|
979
1156
|
});
|
|
1157
|
+
scoped = scoped.map((vault) => reachable.find((row) => row.id === vault.id) ?? vault);
|
|
980
1158
|
}
|
|
981
1159
|
}
|
|
1160
|
+
// Every page says which connected sources were checked, so an empty or
|
|
1161
|
+
// short result is never mistaken for proof that a secret does not exist.
|
|
1162
|
+
const checkedAt = new Date();
|
|
1163
|
+
const freshnessOf = (vault: VaultRow) =>
|
|
1164
|
+
providerVaultFreshness(vault, checkedAt, { refreshing: refreshing.has(vault.id) });
|
|
1165
|
+
const sources = scoped.flatMap((vault) => {
|
|
1166
|
+
const freshness = freshnessOf(vault);
|
|
1167
|
+
return freshness ? [{ vaultId: vault.id, freshness }] : [];
|
|
1168
|
+
});
|
|
1169
|
+
const complete = sources.every((source) => source.freshness.state === "current");
|
|
982
1170
|
const byTier = (vaults: VaultRow[]) =>
|
|
983
1171
|
[...vaults].sort(
|
|
984
1172
|
(left, right) => left.tier - right.tier || left.name.localeCompare(right.name),
|
|
985
1173
|
);
|
|
986
|
-
const vaults = byTier(
|
|
987
|
-
input.vault !== undefined ? [await agentVault(principal, input.vault)] : reachable,
|
|
988
|
-
);
|
|
1174
|
+
const vaults = byTier(scoped);
|
|
989
1175
|
const limit = input.limit ?? 20;
|
|
990
1176
|
const filters = agentSearchFilterKey(input);
|
|
991
1177
|
// Resume exactly after the last item the previous page examined.
|
|
@@ -1060,16 +1246,22 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
1060
1246
|
nextCursor: next === null ? null : encodeAgentSearchCursor(next),
|
|
1061
1247
|
...(input.cursor === undefined
|
|
1062
1248
|
? {
|
|
1063
|
-
vaults: byTier(reachable).map((vault) =>
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1249
|
+
vaults: byTier(reachable).map((vault) => {
|
|
1250
|
+
const freshness = freshnessOf(vault);
|
|
1251
|
+
return {
|
|
1252
|
+
id: vault.id,
|
|
1253
|
+
name: vault.name,
|
|
1254
|
+
ownerKind: vault.ownerKind,
|
|
1255
|
+
source: vault.source,
|
|
1256
|
+
mode: vault.mode,
|
|
1257
|
+
itemCount: vault.itemCount,
|
|
1258
|
+
...(freshness ? { freshness } : {}),
|
|
1259
|
+
};
|
|
1260
|
+
}),
|
|
1071
1261
|
}
|
|
1072
1262
|
: {}),
|
|
1263
|
+
complete,
|
|
1264
|
+
sources,
|
|
1073
1265
|
};
|
|
1074
1266
|
|
|
1075
1267
|
function agentSearchResult(vault: VaultRow, row: VaultItemRow) {
|
|
@@ -1225,7 +1417,7 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
1225
1417
|
const current = await getVaultItem(deps.db, context, { itemId: target.itemId });
|
|
1226
1418
|
if (current.vaultSource !== "opengeni") {
|
|
1227
1419
|
const vault = await getVault(deps.db, context, { vaultId: current.vaultId });
|
|
1228
|
-
await archiveProviderVaultItem(
|
|
1420
|
+
const { archiveKind } = await archiveProviderVaultItem(
|
|
1229
1421
|
{
|
|
1230
1422
|
db: deps.db,
|
|
1231
1423
|
context,
|
|
@@ -1236,7 +1428,7 @@ export function createVaultService(deps: VaultServiceDeps): VaultService {
|
|
|
1236
1428
|
);
|
|
1237
1429
|
return {
|
|
1238
1430
|
archived: true,
|
|
1239
|
-
restoreHint: `A person can restore ${current.name} from the
|
|
1431
|
+
restoreHint: `A person can restore ${current.name} from the ${archiveKind === "trash" ? "trash" : "archive"} in ${vault.name}'s provider; it reappears here after the next sync.`,
|
|
1240
1432
|
};
|
|
1241
1433
|
}
|
|
1242
1434
|
const row = await archiveVaultItem(deps.db, context, {
|
|
@@ -201,7 +201,11 @@ export function createVoiceInputBilling(deps: {
|
|
|
201
201
|
usage: input.usage,
|
|
202
202
|
trustedDurationSeconds: input.billing.trustedDurationSeconds,
|
|
203
203
|
});
|
|
204
|
-
const cost = calculateVoiceInputCost(
|
|
204
|
+
const cost = calculateVoiceInputCost(
|
|
205
|
+
input.pricing,
|
|
206
|
+
usage,
|
|
207
|
+
deps.settings.creditMarginBpsOverride,
|
|
208
|
+
);
|
|
205
209
|
return await settleVoiceInputUsage(deps.db, {
|
|
206
210
|
accountId: input.accountId,
|
|
207
211
|
workspaceId: input.workspaceId,
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,8 @@ export * from "./domain/realtime-voice-billing";
|
|
|
40
40
|
// SessionWorkflowClient, DocumentIndexClient, ObjectStorageDependency).
|
|
41
41
|
export * from "./dependencies";
|
|
42
42
|
export * from "./domain/vaults/provider-registry";
|
|
43
|
+
export * from "./domain/vaults/external-provider-connections";
|
|
44
|
+
export * from "./domain/vaults/remote-provider-runtime";
|
|
43
45
|
export * from "./domain/vaults/provider-sync";
|
|
44
46
|
export * from "./workflow-wake-contract";
|
|
45
47
|
export * from "./user-presence";
|