@cortexkit/opencode-openai-auth 1.0.5 → 1.2.0

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 (104) hide show
  1. package/dist/bin.js +9 -8
  2. package/dist/{pool-lifecycle-C_6YRmsh.d.ts → codex-request-CjXETpTA.d.ts} +190 -27
  3. package/dist/index-03em8hv1.js +69 -0
  4. package/dist/index-6n4pnfkg.js +2 -0
  5. package/dist/{index-pg305s90.js → index-bc89akak.js} +1 -1
  6. package/dist/{index-y4gxyxqd.js → index-ehjj89rx.js} +2 -2
  7. package/dist/index-f21addzm.js +90 -0
  8. package/dist/{index-fsky1rsh.js → index-ffg0sgg5.js} +2 -2
  9. package/dist/{index-nx2qh047.js → index-ftkhhr6d.js} +1 -1
  10. package/dist/{index-826s1yxa.js → index-htvcmtcx.js} +2 -2
  11. package/dist/{index-fsxbncpw.js → index-qp5hdpw8.js} +1 -1
  12. package/dist/index-qxbchh2d.js +8 -0
  13. package/dist/{index-116q849r.js → index-r2s51ac1.js} +2 -2
  14. package/dist/index-rwbrhkse.js +65 -0
  15. package/dist/{index-66asrje2.js → index-xwvze96g.js} +1 -1
  16. package/dist/index.d.ts +18 -8
  17. package/dist/index.js +1 -1
  18. package/dist/{protocol-DctqemRI.d.ts → model-CQcw10oW.d.ts} +67 -25
  19. package/dist/{port-file-ZPlxXB4G.d.ts → port-file-BDJnzgHH.d.ts} +1 -1
  20. package/dist/protocol-C-kagE6O.d.ts +29 -0
  21. package/dist/rpc/port-file.d.ts +1 -1
  22. package/dist/rpc/port-file.js +1 -1
  23. package/dist/rpc/protocol.d.ts +1 -1
  24. package/dist/rpc/protocol.js +1 -1
  25. package/dist/rpc/rpc-client.d.ts +3 -3
  26. package/dist/rpc/rpc-client.js +1 -1
  27. package/dist/rpc/rpc-dir.js +1 -1
  28. package/dist/{sidebar-state-z_M7Trnd.d.ts → sidebar-state-BfQ4mpZO.d.ts} +109 -10
  29. package/dist/sidebar-state.d.ts +2 -2
  30. package/dist/sidebar-state.js +1 -1
  31. package/dist/token-util-cf0nyh7n.js +1 -0
  32. package/dist/{token-bzhkgc29.js → token-z6qq7a1d.js} +1 -1
  33. package/dist/tui-preferences.js +1 -1
  34. package/dist/tui.d.ts +20 -2
  35. package/dist/v2/server.d.ts +180 -1
  36. package/dist/v2/server.js +3 -3
  37. package/package.json +3 -3
  38. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +148 -26
  39. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +371 -0
  40. package/src/tui-compiled/raw/shared/134b008900698fe0-backoff.js +92 -0
  41. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +430 -0
  42. package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  43. package/src/tui-compiled/raw/shared/2dce6e9613993329-quota-manager.js +473 -0
  44. package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +566 -0
  45. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  46. package/src/tui-compiled/raw/shared/4593a4d71edf69fd-tombstone.js +25 -0
  47. package/src/tui-compiled/raw/shared/4d05c2d7a531aece-oauth.js +500 -0
  48. package/src/tui-compiled/raw/shared/6371b24a0573d79e-any-signal.js +42 -0
  49. package/src/tui-compiled/raw/shared/7c140680fc81be92-interlock.js +29 -0
  50. package/src/tui-compiled/raw/shared/85512cc5263ba259-provider.js +87 -0
  51. package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +192 -0
  52. package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +806 -0
  53. package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  54. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +0 -1
  55. package/src/tui-compiled/raw/shared/ca0fccaba1d87af4-errors.js +8 -0
  56. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +152 -0
  57. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
  58. package/src/tui-compiled/raw/shared/d9d2892a4c371301-host-slot.js +72 -0
  59. package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +1470 -0
  60. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  61. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +24 -0
  62. package/src/tui-compiled/raw/sidebar-state.js +103 -14
  63. package/src/tui-compiled/raw/tui/command-dialogs.tsx +3 -34
  64. package/src/tui-compiled/raw/tui/section-text.js +21 -0
  65. package/src/tui-compiled/raw/tui/v2-dialog.js +137 -0
  66. package/src/tui-compiled/raw/tui/v2.tsx +133 -0
  67. package/src/tui-compiled/raw/tui.tsx +228 -70
  68. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +148 -26
  69. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +371 -0
  70. package/src/tui-compiled/runtime/shared/134b008900698fe0-backoff.js +92 -0
  71. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +430 -0
  72. package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  73. package/src/tui-compiled/runtime/shared/2dce6e9613993329-quota-manager.js +473 -0
  74. package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +566 -0
  75. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  76. package/src/tui-compiled/runtime/shared/4593a4d71edf69fd-tombstone.js +25 -0
  77. package/src/tui-compiled/runtime/shared/4d05c2d7a531aece-oauth.js +500 -0
  78. package/src/tui-compiled/runtime/shared/6371b24a0573d79e-any-signal.js +42 -0
  79. package/src/tui-compiled/runtime/shared/7c140680fc81be92-interlock.js +29 -0
  80. package/src/tui-compiled/runtime/shared/85512cc5263ba259-provider.js +87 -0
  81. package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +192 -0
  82. package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +806 -0
  83. package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  84. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +0 -1
  85. package/src/tui-compiled/runtime/shared/ca0fccaba1d87af4-errors.js +8 -0
  86. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +152 -0
  87. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
  88. package/src/tui-compiled/runtime/shared/d9d2892a4c371301-host-slot.js +72 -0
  89. package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +1470 -0
  90. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  91. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +24 -0
  92. package/src/tui-compiled/runtime/sidebar-state.js +103 -14
  93. package/src/tui-compiled/runtime/tui/command-dialogs.js +3 -15
  94. package/src/tui-compiled/runtime/tui/section-text.js +21 -0
  95. package/src/tui-compiled/runtime/tui/v2-dialog.js +137 -0
  96. package/src/tui-compiled/runtime/tui/v2.js +95 -0
  97. package/src/tui-compiled/runtime/tui.js +351 -222
  98. package/dist/index-4fnghj9e.js +0 -65
  99. package/dist/index-a69gr76b.js +0 -2
  100. package/dist/index-h263aftt.js +0 -157
  101. package/dist/index-ssj7m2rd.js +0 -8
  102. package/dist/index-ydz3hbhc.js +0 -2
  103. package/dist/map-m6f5a_IA.d.ts +0 -56
  104. package/dist/token-util-4a2xxnpg.js +0 -1
@@ -0,0 +1,85 @@
1
+ import {
2
+ isQuotaMap,
3
+ projectQuota
4
+ } from "./d5a8f1061ee2f114-index.js";
5
+ export const POOL_QUOTA_LABELS = ["primary", "secondary"];
6
+ function isRecord(value) {
7
+ return value != null && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+ function finite(value) {
10
+ return typeof value === "number" && Number.isFinite(value);
11
+ }
12
+ function readingFor(label, window) {
13
+ if (!isRecord(window))
14
+ return;
15
+ const usedPercent = window.usedPercent;
16
+ if (!finite(usedPercent) || usedPercent < 0 || usedPercent > 100)
17
+ return;
18
+ return {
19
+ label,
20
+ usedPercent,
21
+ ...typeof window.resetsAt === "string" ? { resetsAt: window.resetsAt } : {},
22
+ ...finite(window.windowMinutes) && window.windowMinutes > 0 ? { windowMinutes: window.windowMinutes } : {}
23
+ };
24
+ }
25
+ function budgetFor(snapshot) {
26
+ const spend = snapshot.spendControl;
27
+ if (isRecord(spend) && typeof spend.reached === "boolean") {
28
+ const optionalNumber = (key) => finite(spend[key]) ? { [key]: spend[key] } : {};
29
+ return {
30
+ kind: "reading",
31
+ reached: spend.reached,
32
+ ...optionalNumber("remainingPercent"),
33
+ ...optionalNumber("usedPercent"),
34
+ ...optionalNumber("limit"),
35
+ ...optionalNumber("used"),
36
+ ...optionalNumber("remaining"),
37
+ ...typeof spend.resetsAt === "string" ? { resetsAt: spend.resetsAt } : {},
38
+ ...typeof spend.unit === "string" ? { unit: spend.unit } : {}
39
+ };
40
+ }
41
+ if (snapshot.spendControlCleared === !0)
42
+ return { kind: "cleared" };
43
+ return;
44
+ }
45
+ export function observationFromSnapshot(snapshot, checkedAt, complete) {
46
+ if (!isRecord(snapshot) || !finite(checkedAt))
47
+ return;
48
+ const readings = [], coverage = [];
49
+ for (const label of POOL_QUOTA_LABELS) {
50
+ const reading = readingFor(label, snapshot[label]);
51
+ if (reading)
52
+ readings.push(reading);
53
+ else if (complete)
54
+ coverage.push({ label });
55
+ }
56
+ const budget = budgetFor(snapshot), credits = snapshot.resetCreditsAvailable, counters = typeof credits === "number" && Number.isSafeInteger(credits) && credits >= 0 ? { resetCredits: { value: credits, checkedAt } } : void 0;
57
+ if (readings.length === 0 && coverage.length === 0 && !budget && !counters)
58
+ return;
59
+ return {
60
+ checkedAt,
61
+ ...readings.length > 0 ? { readings } : {},
62
+ ...coverage.length > 0 ? { coverage } : {},
63
+ ...budget ? { budget } : {},
64
+ ...counters ? { counters } : {}
65
+ };
66
+ }
67
+ export function windowsFromQuotaMap(map) {
68
+ if (!isQuotaMap(map))
69
+ return;
70
+ const projection = projectQuota(map), out = {};
71
+ for (const label of POOL_QUOTA_LABELS) {
72
+ const limit = projection.limits.find((entry) => entry.label === label && entry.kind === "reading");
73
+ if (!limit || !finite(limit.usedPercent))
74
+ continue;
75
+ const window = {
76
+ usedPercent: limit.usedPercent,
77
+ remainingPercent: 100 - limit.usedPercent,
78
+ checkedAt: limit.checkedAt,
79
+ ...limit.resetsAt !== void 0 ? { resetsAt: limit.resetsAt } : {},
80
+ ...limit.windowMinutes !== void 0 ? { windowMinutes: limit.windowMinutes } : {}
81
+ };
82
+ out[label] = window;
83
+ }
84
+ return out.primary || out.secondary ? out : void 0;
85
+ }
@@ -57,7 +57,6 @@ export function openPoolStore(options) {
57
57
  ],
58
58
  lockDefaults,
59
59
  lockEnv: {
60
- now: options.now ?? Date.now,
61
60
  ...(options.onLockEvent ? { onLockEvent: options.onLockEvent } : {}),
62
61
  ...(options.onLockStep ? { onLockStep: options.onLockStep } : {}),
63
62
  ...(options.onRenewalTimer
@@ -0,0 +1,8 @@
1
+ export class ClaustrumConsumerError extends Error {
2
+ kind;
3
+ constructor(kind, message) {
4
+ super(message);
5
+ this.name = 'ClaustrumConsumerError';
6
+ this.kind = kind;
7
+ }
8
+ }
@@ -0,0 +1,152 @@
1
+ import { resolve } from "node:path";
2
+ import { storeIdentity } from "@cortexkit/claustrum-client";
3
+ // A static import: claustrum-client already loads subc-client whenever
4
+ // /claustrum is imported, so loading it lazily saves nothing, and a dynamic
5
+ // import makes bundlers emit a separate chunk that a running host loses when
6
+ // its dist folder is rebuilt underneath it.
7
+ import { SubcClient } from "@cortexkit/subc-client";
8
+ /** Constructing this transport opens no connection; getUsage opens one per call. */
9
+ export function insulaUsageTransport(options) {
10
+ const identity = options.identity ??
11
+ storeIdentity(options.projectRoot ?? process.cwd(), options.storagePath ?? process.cwd());
12
+ return {
13
+ connectionFile: options.connectionFile,
14
+ async getUsage() {
15
+ let client;
16
+ let expired = false;
17
+ let timer;
18
+ const timeout = new Promise((_resolve, reject) => {
19
+ timer = setTimeout(() => reject(new Error('insula usage timeout')), 3_000);
20
+ timer.unref?.();
21
+ });
22
+ const request = (async () => {
23
+ client = options.connector
24
+ ? await options.connector({
25
+ connectionFile: options.connectionFile,
26
+ handshakeTimeoutMs: Math.min(options.handshakeTimeoutMs ?? 3_000, 3_000),
27
+ })
28
+ : await SubcClient.connect({
29
+ connectionFile: options.connectionFile,
30
+ handshakeTimeoutMs: Math.min(options.handshakeTimeoutMs ?? 3_000, 3_000),
31
+ });
32
+ // A slow connector may resolve after the whole-call deadline. It still
33
+ // owns a socket, which must not survive or open a route after fallback.
34
+ if (expired) {
35
+ client.close();
36
+ throw new Error('insula usage timeout');
37
+ }
38
+ // Managed call() retries warming routes. A single routeOpen/request
39
+ // instead lets the plugin fall back as soon as the daemon refuses.
40
+ const route = await client.routeOpen({ kind: 'management_surface', module_id: 'insula' }, identity);
41
+ return await client.request(route, { method: 'usage.get', params: {} });
42
+ })();
43
+ try {
44
+ return await Promise.race([request, timeout]);
45
+ }
46
+ finally {
47
+ expired = true;
48
+ clearTimeout(timer);
49
+ client?.close();
50
+ }
51
+ },
52
+ };
53
+ }
54
+ const MINUTE = 60_000;
55
+ const MAX_AGE = 4 * MINUTE;
56
+ const sources = new Map();
57
+ // Keep only the minute budget after the last owner closes, so project churn
58
+ // cannot reset the bound. Expired budgets are pruned on the next tick.
59
+ const attempts = new Map();
60
+ function record(value) {
61
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
62
+ }
63
+ export class InsulaQuotaSource {
64
+ transport;
65
+ key;
66
+ #entries = [];
67
+ #pending;
68
+ constructor(transport, key) {
69
+ this.transport = transport;
70
+ this.key = key;
71
+ }
72
+ refresh(now) {
73
+ if (this.#pending)
74
+ return this.#pending;
75
+ for (const [key, time] of attempts) {
76
+ if (now - time >= MINUTE)
77
+ attempts.delete(key);
78
+ }
79
+ const lastAttempt = attempts.get(this.key);
80
+ if (lastAttempt !== undefined && now - lastAttempt < MINUTE)
81
+ return Promise.resolve();
82
+ // Reserve the minute before I/O, so concurrent projects cannot each poll.
83
+ attempts.set(this.key, now);
84
+ this.#pending = (async () => {
85
+ try {
86
+ const reply = await this.transport.getUsage();
87
+ if (!record(reply) ||
88
+ !Array.isArray(reply.result) ||
89
+ reply.error !== undefined)
90
+ throw new Error('unavailable insula usage');
91
+ // Each response is a snapshot, not a delta. Missing accounts fall back;
92
+ // neither pending nor the entry count asserts provider completeness.
93
+ this.#entries = reply.result.filter((entry) => record(entry) && typeof entry.provider === 'string');
94
+ }
95
+ catch {
96
+ // Refusal is absence, even if the previous reading was still young.
97
+ this.#entries = [];
98
+ }
99
+ })().finally(() => {
100
+ this.#pending = undefined;
101
+ });
102
+ return this.#pending;
103
+ }
104
+ current(provider, accountId, now) {
105
+ for (const entry of this.#entries) {
106
+ if (entry.provider !== provider ||
107
+ typeof entry.account !== 'string' ||
108
+ entry.account.trim() !== accountId)
109
+ continue;
110
+ if (entry.stale !== undefined ||
111
+ entry.errorClass !== undefined ||
112
+ !record(entry.usage))
113
+ continue;
114
+ const fetchedAt = typeof entry.fetchedAt === 'string'
115
+ ? Date.parse(entry.fetchedAt)
116
+ : Number.NaN;
117
+ if (!Number.isFinite(fetchedAt) || now - fetchedAt >= MAX_AGE)
118
+ continue;
119
+ return {
120
+ entry,
121
+ context: {
122
+ fetchedAt,
123
+ ...(typeof entry.source === 'string' && { source: entry.source }),
124
+ },
125
+ };
126
+ }
127
+ return undefined;
128
+ }
129
+ }
130
+ export function acquireInsulaQuotaSource(transport, tokenPath) {
131
+ const key = JSON.stringify([
132
+ resolve(transport.connectionFile),
133
+ resolve(tokenPath),
134
+ ]);
135
+ let shared = sources.get(key);
136
+ if (!shared) {
137
+ shared = { source: new InsulaQuotaSource(transport, key), refs: 0 };
138
+ sources.set(key, shared);
139
+ }
140
+ shared.refs++;
141
+ let released = false;
142
+ return {
143
+ source: shared.source,
144
+ release() {
145
+ if (released)
146
+ return;
147
+ released = true;
148
+ if (--shared.refs === 0)
149
+ sources.delete(key);
150
+ },
151
+ };
152
+ }
@@ -1,5 +1,5 @@
1
1
  export { quotaCodec } from "./053cc17b5419261e-codec.js";
2
- export { formatQuota, NO_LIMITS_REPORTED, NO_QUOTA_READING, QUOTA_STALE_AFTER_MS, quotaTextParts, quotaWindowName, quotaWindowParts, } from "./0332701351b3eeec-format.js";
2
+ export { formatQuota, isQuotaStale, NO_LIMITS_REPORTED, NO_QUOTA_READING, QUOTA_STALE_AFTER_MS, quotaTextParts, quotaWindowName, quotaWindowParts, } from "./0332701351b3eeec-format.js";
3
3
  export { ALL_SCOPE, DEFAULT_REQUIRED_LABELS, decodeQuotaMap, emptyQuotaMap, isQuotaMap, } from "./905bffc0220f43d0-map.js";
4
4
  export { isQuotaObservation, mergeQuotaObservation, QuotaCodecError, } from "./4f36d333d081d040-merge.js";
5
5
  export { budgetExhaustedResetAt, futureResetAt, projectQuota, readsExhausted, } from "./39c3cb05738fcf6d-projection.js";
@@ -0,0 +1,72 @@
1
+ import { ClaustrumConsumerError } from "./ca0fccaba1d87af4-errors.js";
2
+ /**
3
+ * The host's own auth slot (OpenCode's stored login for the provider, Pi's
4
+ * equivalent) must hold something, or the host drops the provider. Under
5
+ * custody it holds this placeholder: an OAuth-shaped value that is never a
6
+ * credential. Its empty access token also makes the vault's sealer refuse it,
7
+ * should it ever be offered for import.
8
+ */
9
+ export const CUSTODY_PLACEHOLDER_PREFIX = 'claustrum-tombstone:v1:';
10
+ export function custodyPlaceholderKey(provider) {
11
+ return `${CUSTODY_PLACEHOLDER_PREFIX}${provider}`;
12
+ }
13
+ export function custodyPlaceholder(provider) {
14
+ return {
15
+ type: 'oauth',
16
+ access: '',
17
+ refresh: custodyPlaceholderKey(provider),
18
+ expires: 0,
19
+ };
20
+ }
21
+ function isRecord(value) {
22
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
23
+ }
24
+ export function isCustodyPlaceholderValue(value) {
25
+ return (typeof value === 'string' && value.startsWith(CUSTODY_PLACEHOLDER_PREFIX));
26
+ }
27
+ export function isCustodyPlaceholder(auth, provider) {
28
+ if (!isRecord(auth) || auth.type !== 'oauth')
29
+ return false;
30
+ return auth.refresh === custodyPlaceholderKey(provider);
31
+ }
32
+ export function classifyHostSlot(auth, provider) {
33
+ if (isCustodyPlaceholder(auth, provider))
34
+ return 'placeholder';
35
+ if (isRecord(auth) &&
36
+ auth.type === 'oauth' &&
37
+ typeof auth.refresh === 'string' &&
38
+ auth.refresh.length > 0 &&
39
+ !isCustodyPlaceholderValue(auth.refresh))
40
+ return 'login';
41
+ if (isRecord(auth) &&
42
+ auth.type === 'api' &&
43
+ typeof auth.key === 'string' &&
44
+ auth.key.length > 0)
45
+ return 'login';
46
+ return 'empty';
47
+ }
48
+ /**
49
+ * Check the host slot against the plugin's mode before serving anything.
50
+ *
51
+ * - Custody mode with a real login in the slot fails closed: someone signed in
52
+ * through the host while the vault owns the accounts, and serving either the
53
+ * login or the vault would silently pick one. The plugin surfaces the error
54
+ * and the user chooses (leave custody, or remove the login).
55
+ * - Local mode with the placeholder in the slot also fails: there is no local
56
+ * credential to serve, and the user has to sign in.
57
+ *
58
+ * Returns the slot content when the combination is consistent.
59
+ */
60
+ export function assertHostSlotMatchesMode(input) {
61
+ const content = classifyHostSlot(input.auth, input.provider);
62
+ if (input.mode === 'custody' && content === 'login')
63
+ throw new ClaustrumConsumerError('host-slot-login', `${input.provider} has a host login while its accounts are vault-custodied; refusing to serve until one is removed`);
64
+ if (input.mode === 'local' && content === 'placeholder')
65
+ throw new ClaustrumConsumerError('host-slot-placeholder', `${input.provider} host slot holds the vault placeholder; sign in to use local accounts`);
66
+ return content;
67
+ }
68
+ /** Refuse to run a local token refresh with the placeholder as the refresh token. */
69
+ export function assertNotCustodyPlaceholder(refreshToken, provider) {
70
+ if (isCustodyPlaceholderValue(refreshToken))
71
+ throw new ClaustrumConsumerError('placeholder-refresh', `${provider} credentials are vault-custodied; local token refresh is forbidden`);
72
+ }