@myagentroam/node 0.10.61 → 0.10.63

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.
@@ -0,0 +1,271 @@
1
+ import { closeImageCredentialProviders } from './runtime-model-factory.js';
2
+ import { nodeLog } from '../../operational.js';
3
+ import { createSessionRuntime, reconfigureSessionRuntime, releaseSessionRuntime, resolveMarAgentHostRuntimeConfiguration, sessionHostFingerprint, HOST_CONFIGURATION_CATEGORIES } from './session-runtime.js';
4
+ export class MarAgentSessionRuntimeRegistry {
5
+ resources;
6
+ promote;
7
+ releaseComputerSession;
8
+ #entries = new Map();
9
+ #disposed = false;
10
+ #disposePromise;
11
+ constructor(resources, promote, releaseComputerSession) {
12
+ this.resources = resources;
13
+ this.promote = promote;
14
+ this.releaseComputerSession = releaseComputerSession;
15
+ }
16
+ findRuntime(alias) {
17
+ const entry = this.#entries.get(alias);
18
+ return entry?.release === undefined ? entry?.runtime : undefined;
19
+ }
20
+ acquire(input, lifetime) {
21
+ if (this.#disposed)
22
+ return Promise.reject(new Error('MAR_AGENT_RUN_CONTROLLER_DISPOSED'));
23
+ const aliases = new Set([input.sessionId, input.externalSessionId ?? input.sessionId]);
24
+ const entries = new Set([...aliases].flatMap((alias) => {
25
+ const entry = this.#entries.get(alias);
26
+ return entry === undefined ? [] : [entry];
27
+ }));
28
+ if (entries.size > 1)
29
+ return Promise.reject(new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE'));
30
+ const entry = entries.values().next().value ?? {
31
+ aliases: new Set(),
32
+ users: new Set()
33
+ };
34
+ if (entry.release !== undefined) {
35
+ const release = entry.release;
36
+ if (release.reason !== 'idle_expired')
37
+ return Promise.reject(new Error('MAR_AGENT_SESSION_RUNTIME_RELEASED'));
38
+ return release.result.then(() => {
39
+ if (release.reason !== 'idle_expired')
40
+ throw new Error('MAR_AGENT_SESSION_RUNTIME_RELEASED');
41
+ return this.acquire(input, lifetime);
42
+ });
43
+ }
44
+ if (entry.current !== undefined || entry.acquisition !== undefined)
45
+ return Promise.reject(new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE'));
46
+ for (const alias of aliases)
47
+ this.#addAlias(entry, alias);
48
+ this.#clearTimer(entry);
49
+ entry.current = lifetime;
50
+ entry.users.add(lifetime);
51
+ void lifetime.settled.then(() => entry.users.delete(lifetime));
52
+ // Publish the acquisition before any dependency can synchronously reenter.
53
+ const acquisition = Promise.resolve().then(async () => {
54
+ let uncommitted;
55
+ try {
56
+ this.#assertUsable(entry, lifetime);
57
+ const configuration = await resolveMarAgentHostRuntimeConfiguration(input.secretEnvironment);
58
+ this.#assertUsable(entry, lifetime);
59
+ const fingerprint = sessionHostFingerprint(input, configuration);
60
+ const existing = entry.runtime;
61
+ if (existing !== undefined && existing.fingerprint.value === fingerprint.value) {
62
+ await reconfigureSessionRuntime(existing, input, this.resources.modelFactory);
63
+ this.#assertUsable(entry, lifetime);
64
+ nodeLog('runner.mar-agent.runtime.reused', {
65
+ runtimeId: existing.runtimeId,
66
+ sessionId: existing.session.id,
67
+ runId: input.runId
68
+ });
69
+ }
70
+ else {
71
+ let nativeId = input.externalSessionId;
72
+ if (existing !== undefined) {
73
+ nativeId = existing.session.id;
74
+ await Promise.all([...entry.users].filter((user) => user !== lifetime).map((user) => user.settled));
75
+ this.#assertUsable(entry, lifetime);
76
+ delete entry.runtime;
77
+ await this.#destroyRuntime(existing, entry.aliases, 'host_configuration_changed', undefined, HOST_CONFIGURATION_CATEGORIES.filter((category) => existing.fingerprint.categories[category] !== fingerprint.categories[category]));
78
+ this.#assertUsable(entry, lifetime);
79
+ }
80
+ const request = nativeId === undefined ? input : { ...input, externalSessionId: nativeId };
81
+ let created;
82
+ created = await createSessionRuntime(request, nativeId ?? input.sessionId, fingerprint, configuration, this.resources, () => created !== undefined && entry.runtime === created && entry.release === undefined
83
+ ? entry.current?.runId
84
+ : undefined);
85
+ uncommitted = created;
86
+ this.#addAlias(entry, uncommitted.session.id);
87
+ nodeLog('runner.mar-agent.runtime.created', {
88
+ runtimeId: uncommitted.runtimeId,
89
+ sessionId: uncommitted.session.id,
90
+ runId: input.runId
91
+ });
92
+ if (input.externalSessionId === undefined)
93
+ this.promote(input.sessionId, uncommitted.session.id);
94
+ this.#assertUsable(entry, lifetime);
95
+ entry.runtime = uncommitted;
96
+ uncommitted = undefined;
97
+ }
98
+ const runtime = entry.runtime;
99
+ return {
100
+ runtime,
101
+ assertUsable: () => this.#assertUsable(entry, lifetime),
102
+ terminal: () => this.#terminal(entry, lifetime)
103
+ };
104
+ }
105
+ catch (error) {
106
+ if (uncommitted !== undefined) {
107
+ nodeLog('runner.mar-agent.runtime.releasing', {
108
+ runtimeId: uncommitted.runtimeId,
109
+ sessionId: uncommitted.session.id,
110
+ reason: 'admission_released',
111
+ runId: input.runId
112
+ });
113
+ // No Execution was bound: do not release another Entry's Session resources
114
+ // if alias registration failed because the SDK returned an existing identity.
115
+ try {
116
+ await releaseSessionRuntime(uncommitted);
117
+ }
118
+ catch {
119
+ this.#logCleanupFailure(uncommitted.session.id);
120
+ }
121
+ finally {
122
+ await closeImageCredentialProviders(uncommitted.models);
123
+ }
124
+ }
125
+ entry.users.delete(lifetime);
126
+ if (entry.current === lifetime)
127
+ delete entry.current;
128
+ throw error;
129
+ }
130
+ finally {
131
+ delete entry.acquisition;
132
+ if (entry.release === undefined && entry.current === undefined) {
133
+ if (entry.runtime === undefined)
134
+ this.#remove(entry);
135
+ else
136
+ this.#schedule(entry);
137
+ }
138
+ }
139
+ });
140
+ entry.acquisition = acquisition;
141
+ return acquisition;
142
+ }
143
+ releaseSession(alias) {
144
+ const existing = this.#entries.get(alias);
145
+ if (existing?.release !== undefined) {
146
+ existing.release.reason = 'session_released';
147
+ return existing.release.result;
148
+ }
149
+ if (this.#disposed)
150
+ return this.#disposePromise;
151
+ const entry = existing ?? { aliases: new Set([alias]), users: new Set() };
152
+ if (existing === undefined)
153
+ this.#entries.set(alias, entry);
154
+ return this.#release(entry, 'session_released');
155
+ }
156
+ dispose() {
157
+ if (this.#disposePromise !== undefined)
158
+ return this.#disposePromise;
159
+ this.#disposed = true;
160
+ this.#disposePromise = Promise.resolve().then(async () => {
161
+ const results = await Promise.allSettled([...new Set(this.#entries.values())].map((entry) => this.#release(entry, 'controller_disposed')));
162
+ const failure = results.find((result) => result.status === 'rejected');
163
+ if (failure?.status === 'rejected')
164
+ throw failure.reason;
165
+ });
166
+ return this.#disposePromise;
167
+ }
168
+ #assertUsable(entry, lifetime) {
169
+ lifetime.signal.throwIfAborted();
170
+ if (this.#disposed)
171
+ throw new Error('MAR_AGENT_RUN_CONTROLLER_DISPOSED');
172
+ if (entry.release !== undefined || entry.current !== lifetime)
173
+ throw new Error('MAR_AGENT_SESSION_RUNTIME_RELEASED');
174
+ }
175
+ #addAlias(entry, alias) {
176
+ const existing = this.#entries.get(alias);
177
+ if (existing !== undefined && existing !== entry)
178
+ throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
179
+ entry.aliases.add(alias);
180
+ this.#entries.set(alias, entry);
181
+ }
182
+ #terminal(entry, lifetime) {
183
+ if (entry.current !== lifetime)
184
+ return;
185
+ delete entry.current;
186
+ this.#schedule(entry);
187
+ }
188
+ #clearTimer(entry) {
189
+ if (entry.idleTimer === undefined)
190
+ return;
191
+ clearTimeout(entry.idleTimer);
192
+ delete entry.idleTimer;
193
+ }
194
+ #schedule(entry) {
195
+ if (entry.runtime === undefined ||
196
+ entry.current !== undefined ||
197
+ entry.acquisition !== undefined ||
198
+ entry.release !== undefined ||
199
+ entry.idleTimer !== undefined ||
200
+ this.#disposed)
201
+ return;
202
+ const timer = setTimeout(() => {
203
+ if (entry.idleTimer !== timer || entry.current !== undefined || entry.release !== undefined)
204
+ return;
205
+ const sessionId = entry.runtime?.session.id;
206
+ void this.#release(entry, 'idle_expired').catch(() => this.#logCleanupFailure(sessionId));
207
+ }, this.resources.resourceIdleTtlMs);
208
+ entry.idleTimer = timer;
209
+ timer.unref();
210
+ }
211
+ #release(entry, reason) {
212
+ if (entry.release !== undefined)
213
+ return entry.release.result;
214
+ this.#clearTimer(entry);
215
+ const runId = entry.current?.runId;
216
+ const result = Promise.resolve().then(async () => {
217
+ try {
218
+ for (const user of entry.users)
219
+ user.cancel();
220
+ await entry.acquisition?.catch(() => undefined);
221
+ await Promise.all([...entry.users].map((user) => user.settled));
222
+ const runtime = entry.runtime;
223
+ delete entry.runtime;
224
+ if (runtime !== undefined)
225
+ await this.#destroyRuntime(runtime, entry.aliases, reason, runId);
226
+ else
227
+ await this.#releaseComputerAliases(entry.aliases);
228
+ }
229
+ finally {
230
+ this.#remove(entry);
231
+ }
232
+ });
233
+ entry.release = { reason, result };
234
+ return result;
235
+ }
236
+ async #releaseComputerAliases(aliases) {
237
+ const results = await Promise.allSettled([...aliases].map((alias) => this.releaseComputerSession(alias)));
238
+ const failure = results.find((result) => result.status === 'rejected');
239
+ if (failure?.status === 'rejected')
240
+ throw failure.reason;
241
+ }
242
+ async #destroyRuntime(runtime, aliases, reason, runId, changedCategories = []) {
243
+ nodeLog('runner.mar-agent.runtime.releasing', {
244
+ runtimeId: runtime.runtimeId,
245
+ sessionId: runtime.session.id,
246
+ reason,
247
+ ...(runId === undefined ? {} : { runId }),
248
+ ...(changedCategories.length === 0 ? {} : { changedCategories })
249
+ });
250
+ const results = await Promise.allSettled([
251
+ this.#releaseComputerAliases(new Set([...aliases, runtime.session.id]))
252
+ ]);
253
+ results.push(...(await Promise.allSettled([releaseSessionRuntime(runtime)])));
254
+ const failures = results.filter((result) => result.status === 'rejected');
255
+ if (failures.length > 1)
256
+ this.#logCleanupFailure(runtime.session.id);
257
+ if (failures[0]?.status === 'rejected')
258
+ throw failures[0].reason;
259
+ }
260
+ #remove(entry) {
261
+ for (const alias of entry.aliases)
262
+ if (this.#entries.get(alias) === entry)
263
+ this.#entries.delete(alias);
264
+ }
265
+ #logCleanupFailure(sessionId) {
266
+ nodeLog('runner.mar-agent.runtime.cleanup_failed', {
267
+ sessionId,
268
+ code: 'MAR_AGENT_RUNTIME_CLEANUP_FAILED'
269
+ });
270
+ }
271
+ }
@@ -0,0 +1,282 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { homedir } from 'node:os';
3
+ import { access, constants } from 'node:fs/promises';
4
+ import { LocalAgentHost } from '@myagentroam/agent';
5
+ import { marAgentRuntimeCompactConfigurationSchema } from '@myagentroam/protocol';
6
+ import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
7
+ import { nodeLog } from '../../operational.js';
8
+ import { BUNDLED_RIPGREP_VERSION, bundledRipgrepPath, prependBundledRipgrepToPath } from '../../ripgrep-binary.js';
9
+ import { canonicalJson } from '../../util/canonical-json.js';
10
+ import { compactRuntimeConfigurations, marAgentCompactExtensionSource, updateMarAgentCompactState } from './compact-extensions.js';
11
+ import { MarAgentConversationNormalizer } from './conversation-normalizer.js';
12
+ import { sdkModelConfigurations } from './runtime-model-factory.js';
13
+ export const HOST_CONFIGURATION_CATEGORIES = [
14
+ 'cwd',
15
+ 'mcpServers',
16
+ 'environment',
17
+ 'guaranteedCommands'
18
+ ];
19
+ export async function createSessionRuntime(input, sessionKey, fingerprint, hostConfiguration, dependencies, currentRunId) {
20
+ const sharedMcpServers = input.mcpServers.filter((server) => !isExecutionScopedMcp(server));
21
+ const mcpLease = await dependencies.mcpClients.acquire(sharedMcpServers, input.cwd, {
22
+ // Native Session IDs are assigned after the first lease; do not let that promotion
23
+ // change a confirmed actor's discovery identity when its warm runtime is recreated.
24
+ authorizationScope: input.secretEnvironment?.['MAR_USERNAME'] ? input.cwd : sessionKey
25
+ });
26
+ let agent;
27
+ let models = [];
28
+ let releaseEnvironment;
29
+ try {
30
+ releaseEnvironment = dependencies.retainEnvironment?.(input.secretEnvironment ?? {});
31
+ const mcp = mcpLease.pool;
32
+ const localHost = new LocalAgentHost({
33
+ modelUsage: (usage) => {
34
+ const runId = currentRunId();
35
+ if (runId !== undefined)
36
+ dependencies.modelUsage(runId, usage);
37
+ },
38
+ mode: 'node',
39
+ homeDirectory: homedir(),
40
+ workspace: input.cwd,
41
+ environment: hostConfiguration.environment,
42
+ guaranteedCommands: hostConfiguration.guaranteedCommands,
43
+ question: async (request, signal) => {
44
+ const runId = currentRunId();
45
+ return runId === undefined ? {} : dependencies.question(runId, request, signal);
46
+ },
47
+ approval: dependencies.approval
48
+ ? (request, signal) => {
49
+ const runId = currentRunId();
50
+ if (runId === undefined)
51
+ return Promise.resolve('REJECTED');
52
+ return dependencies.approval(runId, request, signal);
53
+ }
54
+ : undefined,
55
+ mcpCatalog: () => mcp.catalog(),
56
+ callMcp: async (call, signal, context) => mcp.call(call.name, call.arguments, signal, context?.expectedTool),
57
+ resourceIdleTtlMs: dependencies.resourceIdleTtlMs
58
+ });
59
+ models = dependencies.modelFactory.create(compactRuntimeConfigurations(input.models, input.compact), {
60
+ ...(input.runtimeScopeId === undefined ? {} : { runtimeScopeId: input.runtimeScopeId }),
61
+ executionId: input.runId
62
+ });
63
+ const compactState = { revision: 0 };
64
+ updateMarAgentCompactState(compactState, input.compact, models, input.models.map((model) => model.id));
65
+ agent = await dependencies.createAgent({
66
+ mode: 'node',
67
+ host: localHost,
68
+ models: sdkModelConfigurations(models.filter((model) => input.models.some((candidate) => candidate.id === model.id))),
69
+ defaultModelId: input.model.id,
70
+ runtimeExtensions: marAgentCompactExtensionSource(compactState),
71
+ ...(input.personalInstructions ? { systemInstruction: input.personalInstructions } : {})
72
+ });
73
+ const customTitle = dependencies.customTitle(input.sessionId) ?? undefined;
74
+ const session = input.externalSessionId
75
+ ? await agent.openSession(input.externalSessionId)
76
+ : await agent.createSession({
77
+ workspace: input.cwd,
78
+ ...(customTitle === undefined ? {} : { title: customTitle })
79
+ });
80
+ const runtime = {
81
+ runtimeId: randomUUID(),
82
+ fingerprint,
83
+ agent,
84
+ session,
85
+ host: localHost,
86
+ normalizer: new MarAgentConversationNormalizer(),
87
+ mcpLease,
88
+ releaseEnvironment,
89
+ automaticTitleEnabled: input.externalSessionId === undefined,
90
+ models,
91
+ compactState,
92
+ modelFingerprint: sessionModelFingerprint(input),
93
+ personalInstructions: input.personalInstructions ?? ''
94
+ };
95
+ return runtime;
96
+ }
97
+ catch (error) {
98
+ for (const cleanup of [
99
+ () => agent?.dispose(),
100
+ () => closeModelCredentialProviders(models),
101
+ () => mcpLease.release(),
102
+ () => releaseEnvironment?.()
103
+ ]) {
104
+ try {
105
+ await cleanup();
106
+ }
107
+ catch {
108
+ nodeLog('runner.mar-agent.runtime.cleanup_failed', {
109
+ sessionId: sessionKey,
110
+ code: 'MAR_AGENT_RUNTIME_CLEANUP_FAILED',
111
+ stage: 'creation_rollback'
112
+ });
113
+ }
114
+ }
115
+ throw error;
116
+ }
117
+ }
118
+ export async function reconfigureSessionRuntime(existing, input, modelFactory) {
119
+ const modelFingerprint = sessionModelFingerprint(input);
120
+ const configurations = compactRuntimeConfigurations(input.models, input.compact);
121
+ if (existing.modelFingerprint !== modelFingerprint ||
122
+ configurations.some((model) => model.credential?.type === 'RUNTIME_TICKET' ||
123
+ model.imageGeneration?.credential?.type === 'RUNTIME_TICKET')) {
124
+ const models = modelFactory.create(configurations, {
125
+ ...(input.runtimeScopeId === undefined ? {} : { runtimeScopeId: input.runtimeScopeId }),
126
+ executionId: input.runId
127
+ });
128
+ try {
129
+ await existing.agent.reconfigureModels({
130
+ models: sdkModelConfigurations(models.filter((model) => input.models.some((candidate) => candidate.id === model.id))),
131
+ defaultModelId: input.model.id
132
+ });
133
+ existing.models = models;
134
+ existing.modelFingerprint = modelFingerprint;
135
+ }
136
+ catch (error) {
137
+ await closeModelCredentialProviders(models);
138
+ throw error;
139
+ }
140
+ }
141
+ const personalInstructions = input.personalInstructions ?? (input.mode === 'compact' ? existing.personalInstructions : '');
142
+ if (existing.personalInstructions !== personalInstructions) {
143
+ existing.agent.setSystemInstruction(personalInstructions || undefined);
144
+ existing.personalInstructions = personalInstructions;
145
+ }
146
+ updateMarAgentCompactState(existing.compactState, input.compact, existing.models, input.models.map((model) => model.id));
147
+ }
148
+ export async function releaseSessionRuntime(runtime) {
149
+ const failures = [];
150
+ for (const cleanup of [
151
+ () => runtime.agent.dispose(),
152
+ () => runtime.mcpLease.release(),
153
+ () => runtime.releaseEnvironment?.()
154
+ ]) {
155
+ try {
156
+ await cleanup();
157
+ }
158
+ catch (error) {
159
+ failures.push(error);
160
+ }
161
+ }
162
+ if (failures.length > 1)
163
+ nodeLog('runner.mar-agent.runtime.cleanup_failed', {
164
+ sessionId: runtime.session.id,
165
+ code: 'MAR_AGENT_RUNTIME_CLEANUP_FAILED',
166
+ stage: 'resource_release'
167
+ });
168
+ if (failures.length > 0)
169
+ throw failures[0];
170
+ }
171
+ export function isExecutionScopedMcp(server) {
172
+ return (server.lifecycle === 'EXECUTION' ||
173
+ (server.transport === 'STDIO' && server.environment?.['MAR_CTL_USER_TOKEN'] !== undefined));
174
+ }
175
+ export function sessionHostFingerprint(input, hostConfiguration) {
176
+ const configuration = {
177
+ cwd: input.cwd,
178
+ mcpServers: input.mcpServers.filter((server) => !isExecutionScopedMcp(server)),
179
+ environment: Object.entries(hostConfiguration.environment)
180
+ .filter((entry) => entry[1] !== undefined)
181
+ .sort(([left], [right]) => left.localeCompare(right)),
182
+ guaranteedCommands: Object.entries(hostConfiguration.guaranteedCommands).sort(([left], [right]) => left.localeCompare(right))
183
+ };
184
+ const digest = (value) => createHash('sha256').update(canonicalJson(value)).digest('hex');
185
+ return {
186
+ value: digest(configuration),
187
+ // Only fixed category names leave this comparison; never log these value fingerprints.
188
+ categories: {
189
+ cwd: digest(configuration.cwd),
190
+ mcpServers: digest(configuration.mcpServers),
191
+ environment: digest(configuration.environment),
192
+ guaranteedCommands: digest(configuration.guaranteedCommands)
193
+ }
194
+ };
195
+ }
196
+ function sessionModelFingerprint(input) {
197
+ const compact = marAgentRuntimeCompactConfigurationSchema.safeParse(input.compact);
198
+ return createHash('sha256')
199
+ .update(JSON.stringify({
200
+ defaultModelId: input.model.id,
201
+ compact: compact.success
202
+ ? {
203
+ model: runtimeModelFingerprint(compact.data.model),
204
+ effort: compact.data.reasoningEffort
205
+ }
206
+ : null,
207
+ models: input.models.map(runtimeModelFingerprint)
208
+ }))
209
+ .digest('hex');
210
+ }
211
+ export async function resolveMarAgentHostRuntimeConfiguration(secretEnvironment = {}) {
212
+ const environment = nodeExecutionEnvironment({
213
+ ...secretEnvironment,
214
+ MAR_CTL_CONTEXT_REQUIRED: '1'
215
+ });
216
+ // CLI credentials are bound to each execution, never retained as Host configuration.
217
+ delete environment['MAR_CTL_USER_TOKEN'];
218
+ for (const key of [
219
+ 'MAR_CTL_NODE_ID',
220
+ 'MAR_CTL_WORKSPACE_ID',
221
+ 'MAR_CTL_SESSION_ID',
222
+ 'MAR_CTL_RUN_ID',
223
+ 'MAR_CTL_RUNNER'
224
+ ])
225
+ delete environment[key];
226
+ const ripgrep = await availableBundledRipgrep();
227
+ return {
228
+ environment: ripgrep === undefined ? environment : prependBundledRipgrepToPath(environment, ripgrep),
229
+ guaranteedCommands: ripgrep === undefined ? {} : { rg: BUNDLED_RIPGREP_VERSION }
230
+ };
231
+ }
232
+ async function availableBundledRipgrep() {
233
+ try {
234
+ const executable = bundledRipgrepPath();
235
+ await access(executable, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
236
+ return executable;
237
+ }
238
+ catch {
239
+ return undefined;
240
+ }
241
+ }
242
+ function runtimeModelFingerprint(model) {
243
+ const { apiKey, credential, imageGeneration, ...configuration } = model;
244
+ return {
245
+ ...configuration,
246
+ ...(imageGeneration === undefined
247
+ ? {}
248
+ : {
249
+ imageGeneration: {
250
+ ...imageGeneration,
251
+ credential: runtimeCredentialFingerprint(imageGeneration.credential)
252
+ }
253
+ }),
254
+ ...(apiKey === undefined
255
+ ? {}
256
+ : { apiKeyHash: createHash('sha256').update(apiKey).digest('hex') }),
257
+ credential: runtimeCredentialFingerprint(credential)
258
+ };
259
+ }
260
+ function runtimeCredentialFingerprint(credential) {
261
+ return credential?.type === 'INLINE_API_KEY'
262
+ ? {
263
+ type: credential.type,
264
+ valueHash: createHash('sha256').update(credential.value).digest('hex')
265
+ }
266
+ : credential === undefined
267
+ ? undefined
268
+ : {
269
+ type: credential.type,
270
+ credentialId: credential.credentialId
271
+ };
272
+ }
273
+ async function closeModelCredentialProviders(models) {
274
+ await Promise.allSettled([
275
+ ...new Set(models.flatMap((model) => [
276
+ ...(model.credentialProvider === undefined ? [] : [model.credentialProvider]),
277
+ ...(model.imageGeneration?.credentialProvider === undefined
278
+ ? []
279
+ : [model.imageGeneration.credentialProvider])
280
+ ]))
281
+ ].map((provider) => provider.close()));
282
+ }