@agents-ensemble/core 0.7.0 → 0.9.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 (112) hide show
  1. package/dist/conductor/conductor-auth.d.ts +36 -6
  2. package/dist/conductor/conductor-auth.d.ts.map +1 -1
  3. package/dist/conductor/conductor-auth.js +59 -7
  4. package/dist/conductor/conductor-auth.js.map +1 -1
  5. package/dist/conductor/conductor-pi-auth.d.ts +75 -0
  6. package/dist/conductor/conductor-pi-auth.d.ts.map +1 -0
  7. package/dist/conductor/conductor-pi-auth.js +656 -0
  8. package/dist/conductor/conductor-pi-auth.js.map +1 -0
  9. package/dist/conductor/conductor-session-driver.d.ts +2 -0
  10. package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
  11. package/dist/conductor/conductor-session-driver.js +18 -1
  12. package/dist/conductor/conductor-session-driver.js.map +1 -1
  13. package/dist/conductor/conductor-session.d.ts.map +1 -1
  14. package/dist/conductor/conductor-session.js +69 -7
  15. package/dist/conductor/conductor-session.js.map +1 -1
  16. package/dist/conductor/conductor-tool-pi-adapter.d.ts +6 -8
  17. package/dist/conductor/conductor-tool-pi-adapter.d.ts.map +1 -1
  18. package/dist/conductor/conductor-tool-pi-adapter.js +5 -8
  19. package/dist/conductor/conductor-tool-pi-adapter.js.map +1 -1
  20. package/dist/conductor/index.d.ts +7 -8
  21. package/dist/conductor/index.d.ts.map +1 -1
  22. package/dist/conductor/index.js +4 -4
  23. package/dist/conductor/index.js.map +1 -1
  24. package/dist/conductor/list-conductor-models.d.ts +7 -2
  25. package/dist/conductor/list-conductor-models.d.ts.map +1 -1
  26. package/dist/conductor/list-conductor-models.js +9 -1
  27. package/dist/conductor/list-conductor-models.js.map +1 -1
  28. package/dist/conductor/pi-conductor-agent.d.ts +23 -6
  29. package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
  30. package/dist/conductor/pi-conductor-agent.js +317 -130
  31. package/dist/conductor/pi-conductor-agent.js.map +1 -1
  32. package/dist/conductor/pi-resource-loader.d.ts +7 -4
  33. package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
  34. package/dist/conductor/pi-resource-loader.js +6 -4
  35. package/dist/conductor/pi-resource-loader.js.map +1 -1
  36. package/dist/conductor/session/dispatch-hold.d.ts +14 -0
  37. package/dist/conductor/session/dispatch-hold.d.ts.map +1 -1
  38. package/dist/conductor/session/dispatch-hold.js +30 -0
  39. package/dist/conductor/session/dispatch-hold.js.map +1 -1
  40. package/dist/conductor/session/events/index.d.ts +1 -1
  41. package/dist/conductor/session/events/index.d.ts.map +1 -1
  42. package/dist/conductor/session/events/index.js.map +1 -1
  43. package/dist/conductor/session/events/session-log-event-groups.d.ts +2 -2
  44. package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
  45. package/dist/conductor/session/events/session-log-event-groups.js +1 -0
  46. package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
  47. package/dist/conductor/session/events/session-log-event.d.ts +10 -1
  48. package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
  49. package/dist/conductor/session/format-session-event.js +3 -0
  50. package/dist/conductor/session/format-session-event.js.map +1 -1
  51. package/dist/conductor/session/session-logger.d.ts.map +1 -1
  52. package/dist/conductor/session/session-logger.js +1 -0
  53. package/dist/conductor/session/session-logger.js.map +1 -1
  54. package/dist/github/fetch-github-updates.d.ts.map +1 -1
  55. package/dist/github/fetch-github-updates.js +109 -80
  56. package/dist/github/fetch-github-updates.js.map +1 -1
  57. package/dist/github/github-client.d.ts.map +1 -1
  58. package/dist/github/github-client.js +8 -0
  59. package/dist/github/github-client.js.map +1 -1
  60. package/dist/github/github-monitor-cursor.d.ts +19 -8
  61. package/dist/github/github-monitor-cursor.d.ts.map +1 -1
  62. package/dist/github/github-monitor-cursor.js +8 -52
  63. package/dist/github/github-monitor-cursor.js.map +1 -1
  64. package/dist/github/github-monitor.d.ts +2 -0
  65. package/dist/github/github-monitor.d.ts.map +1 -1
  66. package/dist/github/github-monitor.js +13 -0
  67. package/dist/github/github-monitor.js.map +1 -1
  68. package/dist/github/github-test-fixtures.d.ts +9 -0
  69. package/dist/github/github-test-fixtures.d.ts.map +1 -1
  70. package/dist/github/github-test-fixtures.js +6 -0
  71. package/dist/github/github-test-fixtures.js.map +1 -1
  72. package/dist/github/github-update-types.d.ts +10 -0
  73. package/dist/github/github-update-types.d.ts.map +1 -1
  74. package/dist/github/index.d.ts +3 -1
  75. package/dist/github/index.d.ts.map +1 -1
  76. package/dist/github/index.js +1 -0
  77. package/dist/github/index.js.map +1 -1
  78. package/dist/github/register-github-watch-tool.d.ts +5 -0
  79. package/dist/github/register-github-watch-tool.d.ts.map +1 -1
  80. package/dist/github/register-github-watch-tool.js +30 -24
  81. package/dist/github/register-github-watch-tool.js.map +1 -1
  82. package/dist/github/unregister-github-watch-tool.d.ts +11 -0
  83. package/dist/github/unregister-github-watch-tool.d.ts.map +1 -0
  84. package/dist/github/unregister-github-watch-tool.js +52 -0
  85. package/dist/github/unregister-github-watch-tool.js.map +1 -0
  86. package/dist/mcp/load-mcp-config.d.ts +8 -0
  87. package/dist/mcp/load-mcp-config.d.ts.map +1 -1
  88. package/dist/mcp/load-mcp-config.js +24 -0
  89. package/dist/mcp/load-mcp-config.js.map +1 -1
  90. package/dist/prompt/modules/ensemble/base-module.d.ts.map +1 -1
  91. package/dist/prompt/modules/ensemble/base-module.js +2 -1
  92. package/dist/prompt/modules/ensemble/base-module.js.map +1 -1
  93. package/dist/prompt/modules/ensemble/conductor-base-module.d.ts.map +1 -1
  94. package/dist/prompt/modules/ensemble/conductor-base-module.js +4 -1
  95. package/dist/prompt/modules/ensemble/conductor-base-module.js.map +1 -1
  96. package/dist/runtime/worker-session.d.ts +5 -0
  97. package/dist/runtime/worker-session.d.ts.map +1 -1
  98. package/dist/runtime/worker-session.js +5 -1
  99. package/dist/runtime/worker-session.js.map +1 -1
  100. package/dist/session/session-sidecar.d.ts +6 -0
  101. package/dist/session/session-sidecar.d.ts.map +1 -1
  102. package/dist/session/session-sidecar.js +17 -0
  103. package/dist/session/session-sidecar.js.map +1 -1
  104. package/package.json +4 -4
  105. package/dist/conductor/pi-conductor-session.d.ts +0 -20
  106. package/dist/conductor/pi-conductor-session.d.ts.map +0 -1
  107. package/dist/conductor/pi-conductor-session.js +0 -60
  108. package/dist/conductor/pi-conductor-session.js.map +0 -1
  109. package/dist/conductor/pi-mcp-bridge.d.ts +0 -65
  110. package/dist/conductor/pi-mcp-bridge.d.ts.map +0 -1
  111. package/dist/conductor/pi-mcp-bridge.js +0 -259
  112. package/dist/conductor/pi-mcp-bridge.js.map +0 -1
@@ -0,0 +1,656 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { ModelRegistry, ModelRuntime, } from '@earendil-works/pi-coding-agent';
5
+ import { findEnvKeys, getEnvApiKey, getModels, getProviders, } from '@earendil-works/pi-ai/compat';
6
+ import { loadPiResources, resolvePiResourceRoots, } from './pi-resource-loader.js';
7
+ /** Create the Pi 1.x model runtime and registry for the resolved resource roots. */
8
+ export async function createPiConductorAuthContext(options, resources) {
9
+ const roots = resolvePiResourceRoots(options);
10
+ const authPath = join(roots.agentDir, 'auth.json');
11
+ const modelsPath = join(roots.agentDir, 'models.json');
12
+ const resolvedResources = resources ?? (await loadPiResources(options));
13
+ const modelRuntime = await createPiModelRuntime(options, resolvedResources);
14
+ return {
15
+ agentDir: roots.agentDir,
16
+ authPath,
17
+ modelsPath,
18
+ modelRuntime,
19
+ modelRegistry: new ModelRegistry(modelRuntime),
20
+ };
21
+ }
22
+ /** Resolve the provider used by `ensemble auth` from an explicit/default model. */
23
+ export async function resolvePiConductorProvider(options) {
24
+ const resources = await loadPiResources(options);
25
+ const context = await createPiConductorAuthContext(options, resources);
26
+ return resolvePiConductorProviderFromResources(options, resources, context);
27
+ }
28
+ /** Login to a Pi provider. API-key login persists only in the user auth file. */
29
+ export async function loginPiConductor(options) {
30
+ const resources = await loadPiResources(options);
31
+ const context = await createPiConductorAuthContext(options, resources);
32
+ const provider = await resolveConductorProviderOrThrow(options, resources, context);
33
+ const oauthProvider = Boolean(context.modelRuntime.getProvider(provider)?.auth.oauth);
34
+ if (oauthProvider) {
35
+ if (!options.oauthCallbacks) {
36
+ throw new Error(`Pi provider "${provider}" uses OAuth. Run this command from a TTY so the OAuth login flow can interact with you.`);
37
+ }
38
+ await context.modelRuntime.login(provider, 'oauth', toPiAuthInteraction(options.oauthCallbacks));
39
+ return {
40
+ backend: 'pi',
41
+ provider,
42
+ method: 'oauth',
43
+ authPath: context.authPath,
44
+ };
45
+ }
46
+ const apiKey = options.apiKey?.trim();
47
+ if (!apiKey) {
48
+ throw new Error(`Pi provider "${provider}" requires an API key. Pass a secret API-key prompt value or set ${formatPiProviderEnvHint(provider)}.`);
49
+ }
50
+ await writePiApiKey(context.authPath, provider, apiKey);
51
+ await context.modelRuntime.setRuntimeApiKey(provider, apiKey);
52
+ return {
53
+ backend: 'pi',
54
+ provider,
55
+ method: 'api_key',
56
+ authPath: context.authPath,
57
+ };
58
+ }
59
+ /** Whether the selected Pi provider exposes Pi's native OAuth login flow. */
60
+ export async function isPiConductorOAuthProvider(options) {
61
+ const resources = await loadPiResources(options);
62
+ const context = await createPiConductorAuthContext(options, resources);
63
+ const provider = await resolveConductorProviderOrThrow(options, resources, context);
64
+ return Boolean(context.modelRuntime.getProvider(provider)?.auth.oauth);
65
+ }
66
+ /** Remove a Pi provider's user-layer credential. */
67
+ export async function logoutPiConductor(options) {
68
+ const resources = await loadPiResources(options);
69
+ const context = await createPiConductorAuthContext(options, resources);
70
+ const provider = await resolveConductorProviderOrThrow(options, resources, context);
71
+ await context.modelRuntime.logout(provider);
72
+ return {
73
+ backend: 'pi',
74
+ provider,
75
+ authPath: context.authPath,
76
+ };
77
+ }
78
+ /** Return non-secret Pi auth metadata for the selected/default provider. */
79
+ export async function getPiConductorAuthStatus(options) {
80
+ const resources = await loadPiResources(options);
81
+ const context = await createPiConductorAuthContext(options, resources);
82
+ const providers = await resolveStatusProviders(options, resources, context);
83
+ return {
84
+ backend: 'pi',
85
+ authPath: context.authPath,
86
+ providers: providers.map((provider) => getPiProviderAuthStatus(provider, context, resources, options.env ?? process.env)),
87
+ };
88
+ }
89
+ /** List only Pi models whose provider has usable configured auth. */
90
+ export async function listPiConductorModels(options = { cwd: process.cwd() }) {
91
+ const resources = await loadPiResources(options);
92
+ const context = await createPiConductorAuthContext(options, resources);
93
+ const requestedProvider = normalizeProvider(options.provider) ?? providerFromModelId(options.modelId);
94
+ const entries = new Map();
95
+ // Filter the complete Pi 1.x runtime catalog through the merged
96
+ // project-over-user auth view before listing models.
97
+ for (const model of context.modelRegistry.getAll()) {
98
+ if (requestedProvider && model.provider !== requestedProvider)
99
+ continue;
100
+ if (!hasPiProviderAuth(model.provider, context, resources, options.env ?? process.env)) {
101
+ continue;
102
+ }
103
+ addPiModelEntry(entries, model);
104
+ }
105
+ // The runtime is built from the merged models resource, so custom models are
106
+ // already included in the registry above.
107
+ const result = [...entries.values()].sort((left, right) => left.id.localeCompare(right.id));
108
+ if (result.length === 0) {
109
+ const providerHint = requestedProvider ? ` for provider "${requestedProvider}"` : '';
110
+ throw new Error(`No authenticated Pi models are available${providerHint}. ` +
111
+ `Run ensemble auth login --provider <provider>, set provider credentials in ${context.agentDir}, ` +
112
+ 'or configure a provider environment variable.');
113
+ }
114
+ return result;
115
+ }
116
+ /** Resolve a key while retaining project-over-user auth precedence. */
117
+ export async function resolvePiConductorApiKey(options) {
118
+ const env = options.env ?? process.env;
119
+ if (options.explicitApiKey !== undefined)
120
+ return options.explicitApiKey;
121
+ // A project auth file intentionally remains above the user auth layer. When
122
+ // both roots point at the same file, ModelRuntime is the canonical reader so
123
+ // OAuth refresh is not bypassed by the resource-layer compatibility parser.
124
+ if (options.resources.roots.agentDir !== options.resources.roots.projectDir) {
125
+ const projectEntry = options.resources.authLayers.at(-1)?.[options.provider];
126
+ if (isPiOAuthAuthEntry(projectEntry)) {
127
+ throw new Error(formatProjectOAuthAuthError(options.provider));
128
+ }
129
+ const projectKey = resolvePiAuthEntry(projectEntry, env);
130
+ if (projectKey !== undefined)
131
+ return projectKey;
132
+ }
133
+ // ModelRuntime owns the Pi 1.x credential store, OAuth refresh, and locking.
134
+ // Resolve it on every request instead of capturing the key at construction.
135
+ try {
136
+ const runtimeAuth = await options.modelRuntime.getAuth(options.provider);
137
+ if (runtimeAuth?.auth.apiKey !== undefined)
138
+ return runtimeAuth.auth.apiKey;
139
+ }
140
+ catch {
141
+ // A models.json command or environment reference can be unavailable in
142
+ // the injected environment. Continue through the ensemble fallback chain
143
+ // so its resolved map remains authoritative for this request.
144
+ }
145
+ const settingsKey = resolvePiSettingsApiKey(options.resources.settings, options.provider, env);
146
+ if (settingsKey !== undefined)
147
+ return settingsKey;
148
+ const modelsKey = resolvePiModelsApiKey(options.resources.models.providers[options.provider], env);
149
+ if (modelsKey !== undefined)
150
+ return modelsKey;
151
+ return getEnvApiKey(options.provider, toProviderEnv(env));
152
+ }
153
+ /** Return whether a provider has configured auth without exposing its value. */
154
+ export function hasPiProviderAuth(provider, context, resources, env = process.env) {
155
+ const status = getPiProviderAuthStatus(provider, context, resources, env);
156
+ return status.configured;
157
+ }
158
+ /** Synchronous readiness check used by CLI/e2e guards before async loading. */
159
+ export function hasPiConductorAuth(options) {
160
+ const env = options.env ?? process.env;
161
+ const roots = resolvePiResourceRoots(options);
162
+ const userAuth = readPiJsonSync(join(roots.agentDir, 'auth.json'));
163
+ const projectAuth = readPiJsonSync(join(roots.projectDir, 'auth.json'));
164
+ const settings = readPiSettingsSync(roots);
165
+ const models = readPiModelsSync(roots);
166
+ const selection = resolvePiConductorSelectionSync(options, settings, models, userAuth);
167
+ if (selection.provider) {
168
+ return hasPiProviderAuthSync({
169
+ provider: selection.provider,
170
+ roots,
171
+ userAuth,
172
+ projectAuth,
173
+ settings,
174
+ models,
175
+ env,
176
+ });
177
+ }
178
+ // A configured model that cannot be mapped to one provider is not a
179
+ // usable conductor selection, even if another provider happens to have
180
+ // credentials.
181
+ if (selection.modelId)
182
+ return false;
183
+ return hasAnyPiConductorAuthSync({
184
+ roots,
185
+ userAuth,
186
+ projectAuth,
187
+ settings,
188
+ models,
189
+ env,
190
+ });
191
+ }
192
+ /**
193
+ * Mirror PiConductorAgent's synchronous provider/model selection for guards.
194
+ * The async agent receives the same merged settings and model layers.
195
+ */
196
+ function resolvePiConductorSelectionSync(options, settings, models, userAuth) {
197
+ const requestedModel = normalizeModelId(options.modelId);
198
+ const configuredModel = normalizeModelId(firstString(settings.defaultModel, settings.model, settings.modelId));
199
+ const selectedModel = requestedModel ?? configuredModel;
200
+ let provider = normalizeProvider(options.provider);
201
+ let modelId = selectedModel;
202
+ if (selectedModel?.includes('/')) {
203
+ const separator = selectedModel.indexOf('/');
204
+ if (!provider) {
205
+ provider = normalizeProvider(selectedModel.slice(0, separator));
206
+ }
207
+ modelId = normalizeModelId(selectedModel.slice(separator + 1));
208
+ }
209
+ if (!provider)
210
+ provider = normalizeProvider(settings.defaultProvider);
211
+ if (!provider && modelId) {
212
+ const matches = new Set();
213
+ for (const model of knownPiModelsSync(models, userAuth)) {
214
+ if (model.id === modelId)
215
+ matches.add(model.provider);
216
+ }
217
+ for (const [candidate, config] of Object.entries(models.providers)) {
218
+ if (config.models?.some((model) => model.id === modelId)) {
219
+ matches.add(candidate);
220
+ }
221
+ }
222
+ if (matches.size === 1)
223
+ provider = [...matches][0];
224
+ }
225
+ return {
226
+ ...(provider ? { provider } : {}),
227
+ ...(modelId ? { modelId } : {}),
228
+ };
229
+ }
230
+ function hasPiProviderAuthSync(options) {
231
+ const projectEntry = options.projectAuth?.[options.provider];
232
+ if (options.roots.agentDir !== options.roots.projectDir &&
233
+ isPiOAuthAuthEntry(projectEntry)) {
234
+ return false;
235
+ }
236
+ if (resolvePiAuthEntry(projectEntry, options.env) !== undefined)
237
+ return true;
238
+ if (resolvePiAuthEntry(options.userAuth?.[options.provider], options.env) !== undefined) {
239
+ return true;
240
+ }
241
+ if (resolvePiSettingsApiKey(options.settings, options.provider, options.env) !== undefined) {
242
+ return true;
243
+ }
244
+ if (resolvePiModelsApiKey(options.models.providers[options.provider], options.env) !== undefined) {
245
+ return true;
246
+ }
247
+ return getEnvApiKey(options.provider, toProviderEnv(options.env)) !== undefined;
248
+ }
249
+ function hasAnyPiConductorAuthSync(options) {
250
+ const providers = new Set([
251
+ ...Object.keys(options.userAuth ?? {}),
252
+ ...knownPiModelsSync(options.models, options.userAuth).map((model) => model.provider),
253
+ ...Object.keys(options.models.providers),
254
+ ...Object.keys(options.projectAuth ?? {}),
255
+ ...getProviders(),
256
+ ]);
257
+ for (const provider of providers) {
258
+ if (hasPiProviderAuthSync({ ...options, provider }))
259
+ return true;
260
+ }
261
+ if (typeof options.settings.apiKey === 'string' ||
262
+ (isRecord(options.settings.apiKeys) &&
263
+ Object.values(options.settings.apiKeys).some((entry) => typeof entry === 'string'))) {
264
+ return true;
265
+ }
266
+ return false;
267
+ }
268
+ function readPiSettingsSync(roots) {
269
+ return mergePiRecords(readPiJsonSync(join(roots.agentDir, 'settings.json')) ?? {}, readPiJsonSync(join(roots.projectDir, 'settings.json')) ?? {});
270
+ }
271
+ function readPiModelsSync(roots) {
272
+ const merged = mergePiRecords(readPiJsonSync(join(roots.agentDir, 'models.json')) ?? {}, readPiJsonSync(join(roots.projectDir, 'models.json')) ?? {});
273
+ return {
274
+ providers: isRecord(merged.providers)
275
+ ? merged.providers
276
+ : {},
277
+ };
278
+ }
279
+ async function resolveConductorProviderOrThrow(options, resources, context) {
280
+ const provider = resolvePiConductorProviderFromResources(options, resources, context);
281
+ if (provider)
282
+ return provider;
283
+ throw new Error(`Pi provider is not configured. Set defaultProvider in ${join(context.agentDir, 'settings.json')} or pass --provider <id>.`);
284
+ }
285
+ function resolvePiConductorProviderFromResources(options, resources, context) {
286
+ const explicit = normalizeProvider(options.provider);
287
+ if (explicit)
288
+ return explicit;
289
+ const requestedModel = normalizeModelId(options.modelId);
290
+ const configuredModel = normalizeModelId(firstString(resources.settings.defaultModel, resources.settings.model, resources.settings.modelId));
291
+ const selectedModel = requestedModel ?? configuredModel;
292
+ if (selectedModel?.includes('/')) {
293
+ return selectedModel.slice(0, selectedModel.indexOf('/'));
294
+ }
295
+ const configuredProvider = normalizeProvider(resources.settings.defaultProvider);
296
+ if (configuredProvider)
297
+ return configuredProvider;
298
+ if (selectedModel) {
299
+ const matches = new Set();
300
+ for (const model of context.modelRegistry.getAll()) {
301
+ if (model.id === selectedModel)
302
+ matches.add(model.provider);
303
+ }
304
+ for (const [provider, config] of Object.entries(resources.models.providers)) {
305
+ if (config.models?.some((model) => model.id === selectedModel)) {
306
+ matches.add(provider);
307
+ }
308
+ }
309
+ if (matches.size === 1)
310
+ return [...matches][0];
311
+ }
312
+ return '';
313
+ }
314
+ async function resolveStatusProviders(options, resources, context) {
315
+ const explicit = normalizeProvider(options.provider);
316
+ if (explicit)
317
+ return [explicit];
318
+ const configured = resolvePiConductorProviderFromResources(options, resources, context);
319
+ if (configured)
320
+ return [configured];
321
+ const providers = new Set([
322
+ ...(await context.modelRuntime.listCredentials()).map((entry) => entry.providerId),
323
+ ...context.modelRegistry.getAll().map((model) => model.provider),
324
+ ...Object.keys(resources.models.providers),
325
+ ]);
326
+ return [...providers].sort();
327
+ }
328
+ function getPiProviderAuthStatus(provider, context, resources, env) {
329
+ if (resources.roots.agentDir !== resources.roots.projectDir) {
330
+ const projectEntry = resources.authLayers.at(-1)?.[provider];
331
+ if (isPiOAuthAuthEntry(projectEntry)) {
332
+ return {
333
+ provider,
334
+ configured: false,
335
+ source: 'project_oauth_unsupported',
336
+ };
337
+ }
338
+ const projectKey = resolvePiAuthEntry(projectEntry, env);
339
+ if (projectKey !== undefined) {
340
+ return { provider, configured: true, source: 'project_stored' };
341
+ }
342
+ }
343
+ const registryStatus = context.modelRuntime.getProviderAuthStatus(provider);
344
+ if (registryStatus.configured || registryStatus.source === 'stored') {
345
+ return normalizePiAuthStatus(provider, registryStatus, true);
346
+ }
347
+ const settingsKey = resolvePiSettingsApiKey(resources.settings, provider, env);
348
+ if (settingsKey !== undefined) {
349
+ return { provider, configured: true, source: 'settings_json' };
350
+ }
351
+ const modelsKey = resolvePiModelsApiKey(resources.models.providers[provider], env);
352
+ if (modelsKey !== undefined) {
353
+ return { provider, configured: true, source: 'models_json_key' };
354
+ }
355
+ const envKey = getEnvApiKey(provider, toProviderEnv(env));
356
+ if (envKey !== undefined) {
357
+ return {
358
+ provider,
359
+ configured: true,
360
+ source: 'environment',
361
+ label: formatPiProviderEnvHint(provider),
362
+ };
363
+ }
364
+ return normalizePiAuthStatus(provider, registryStatus, false);
365
+ }
366
+ function normalizePiAuthStatus(provider, status, configured) {
367
+ return {
368
+ provider,
369
+ configured,
370
+ ...(status.source ? { source: status.source } : {}),
371
+ ...(status.label ? { label: status.label } : {}),
372
+ };
373
+ }
374
+ function addPiModelEntry(entries, model) {
375
+ const id = `${model.provider}/${model.id}`;
376
+ entries.set(id, {
377
+ id,
378
+ displayName: model.name || model.id,
379
+ provider: model.provider,
380
+ });
381
+ }
382
+ function addPiCustomModelEntry(entries, provider, model) {
383
+ const id = `${provider}/${model.id}`;
384
+ entries.set(id, {
385
+ id,
386
+ displayName: model.name ?? model.id,
387
+ provider,
388
+ });
389
+ }
390
+ function resolvePiAuthEntry(entry, env) {
391
+ if (typeof entry === 'string')
392
+ return expandEnvReference(entry, env);
393
+ if (!isRecord(entry))
394
+ return undefined;
395
+ if (isPiOAuthAuthEntry(entry))
396
+ return undefined;
397
+ for (const key of ['key', 'apiKey', 'token', 'accessToken', 'access']) {
398
+ const value = entry[key];
399
+ if (typeof value === 'string')
400
+ return expandEnvReference(value, env);
401
+ }
402
+ return undefined;
403
+ }
404
+ function isPiOAuthAuthEntry(entry) {
405
+ return isRecord(entry) && entry.type === 'oauth';
406
+ }
407
+ function formatProjectOAuthAuthError(provider) {
408
+ return (`Project Pi OAuth credentials for provider "${provider}" are not supported in ` +
409
+ 'project .ensemble/pi/auth.json because that layer cannot refresh them safely. ' +
410
+ `Run ensemble auth login --provider ${provider} to store the credential in the user Pi runtime.`);
411
+ }
412
+ function resolvePiSettingsApiKey(settings, provider, env) {
413
+ if (typeof settings.apiKey === 'string') {
414
+ return expandEnvReference(settings.apiKey, env);
415
+ }
416
+ if (!isRecord(settings.apiKeys))
417
+ return undefined;
418
+ const value = settings.apiKeys[provider];
419
+ return typeof value === 'string' ? expandEnvReference(value, env) : undefined;
420
+ }
421
+ function resolvePiModelsApiKey(providerConfig, env) {
422
+ return providerConfig?.apiKey
423
+ ? expandEnvReference(providerConfig.apiKey, env)
424
+ : undefined;
425
+ }
426
+ function expandEnvReference(value, env) {
427
+ if (value.startsWith('!'))
428
+ return undefined;
429
+ const variable = /^\$\{([^}]+)\}$/.exec(value) ?? /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);
430
+ return variable ? env[variable[1]] : value;
431
+ }
432
+ function toProviderEnv(env) {
433
+ return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === 'string'));
434
+ }
435
+ /** Build the Pi 1.x runtime from the already-resolved ensemble resource view. */
436
+ async function createPiModelRuntime(options, resources) {
437
+ const roots = resolvePiResourceRoots(options);
438
+ const runtime = await ModelRuntime.create({
439
+ authPath: join(roots.agentDir, 'auth.json'),
440
+ // The ensemble resource loader has already merged user and project models.
441
+ // Avoid letting ModelRuntime read an unmerged second view of models.json.
442
+ modelsPath: null,
443
+ allowModelNetwork: false,
444
+ refreshOnCreate: false,
445
+ });
446
+ const env = options.env ?? process.env;
447
+ for (const [provider, config] of Object.entries(resources.models.providers)) {
448
+ runtime.registerProvider(provider, toPiRuntimeProviderConfig(config, env));
449
+ }
450
+ if (roots.agentDir !== roots.projectDir) {
451
+ for (const [provider, entry] of Object.entries(resources.authLayers.at(-1) ?? {})) {
452
+ if (isPiOAuthAuthEntry(entry))
453
+ continue;
454
+ const key = resolvePiAuthEntry(entry, env);
455
+ if (key !== undefined)
456
+ await runtime.setRuntimeApiKey(provider, key);
457
+ }
458
+ }
459
+ if (isRecord(resources.settings.apiKeys)) {
460
+ for (const [provider, value] of Object.entries(resources.settings.apiKeys)) {
461
+ if (typeof value !== 'string')
462
+ continue;
463
+ const key = expandEnvReference(value, env);
464
+ if (key !== undefined)
465
+ await runtime.setRuntimeApiKey(provider, key);
466
+ }
467
+ }
468
+ await runtime.refresh({ allowNetwork: false });
469
+ return runtime;
470
+ }
471
+ function toPiRuntimeProviderConfig(config, env) {
472
+ const models = config.models?.map((model) => ({
473
+ type: 'chat',
474
+ id: model.id,
475
+ name: model.name ?? model.id,
476
+ ...(model.api ?? config.api ? { api: model.api ?? config.api } : {}),
477
+ ...(model.baseUrl ?? config.baseUrl
478
+ ? { baseUrl: model.baseUrl ?? config.baseUrl }
479
+ : {}),
480
+ input: model.input ?? ['text'],
481
+ reasoning: model.reasoning ?? false,
482
+ ...(model.thinkingLevelMap ? { thinkingLevelMap: model.thinkingLevelMap } : {}),
483
+ cost: model.cost ?? emptyPiModelCost(),
484
+ contextWindow: model.contextWindow ?? 128000,
485
+ maxTokens: model.maxTokens ?? 16384,
486
+ ...(model.headers ? { headers: model.headers } : {}),
487
+ ...(model.compat ? { compat: model.compat } : {}),
488
+ }));
489
+ return {
490
+ ...(config.name ? { name: config.name } : {}),
491
+ ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
492
+ ...(config.apiKey
493
+ ? { apiKey: expandEnvReference(config.apiKey, env) ?? undefined }
494
+ : {}),
495
+ ...(config.api ? { api: config.api } : {}),
496
+ ...(config.headers ? { headers: config.headers } : {}),
497
+ ...(config.authHeader !== undefined ? { authHeader: config.authHeader } : {}),
498
+ ...(models ? { models } : {}),
499
+ };
500
+ }
501
+ async function writePiApiKey(authPath, provider, apiKey) {
502
+ await mkdir(dirname(authPath), { recursive: true });
503
+ let auth = {};
504
+ try {
505
+ const parsed = JSON.parse(await readFile(authPath, 'utf8'));
506
+ if (isRecord(parsed))
507
+ auth = parsed;
508
+ }
509
+ catch (error) {
510
+ if (!isRecord(error) || error.code !== 'ENOENT')
511
+ throw error;
512
+ }
513
+ auth[provider] = { type: 'api_key', key: apiKey };
514
+ await writeFile(authPath, `${JSON.stringify(auth, null, 2)}\n`, 'utf8');
515
+ }
516
+ function toPiAuthInteraction(callbacks) {
517
+ return {
518
+ signal: callbacks.signal,
519
+ prompt: async (prompt) => {
520
+ if (prompt.type === 'select') {
521
+ const selected = await callbacks.onSelect({
522
+ message: prompt.message,
523
+ options: prompt.options.map((option) => ({
524
+ id: option.id,
525
+ label: option.label,
526
+ })),
527
+ });
528
+ if (selected === undefined)
529
+ throw new Error('OAuth login was cancelled.');
530
+ return selected;
531
+ }
532
+ if (prompt.type === 'manual_code' && callbacks.onManualCodeInput) {
533
+ return callbacks.onManualCodeInput();
534
+ }
535
+ return callbacks.onPrompt({
536
+ message: prompt.message,
537
+ ...(prompt.placeholder ? { placeholder: prompt.placeholder } : {}),
538
+ });
539
+ },
540
+ notify: (event) => {
541
+ if (event.type === 'auth_url') {
542
+ callbacks.onAuth({ url: event.url, instructions: event.instructions });
543
+ }
544
+ else if (event.type === 'device_code') {
545
+ callbacks.onDeviceCode({
546
+ userCode: event.userCode,
547
+ verificationUri: event.verificationUri,
548
+ ...(event.intervalSeconds !== undefined
549
+ ? { intervalSeconds: event.intervalSeconds }
550
+ : {}),
551
+ ...(event.expiresInSeconds !== undefined
552
+ ? { expiresInSeconds: event.expiresInSeconds }
553
+ : {}),
554
+ });
555
+ }
556
+ else if (callbacks.onProgress) {
557
+ callbacks.onProgress(event.message);
558
+ }
559
+ },
560
+ };
561
+ }
562
+ function knownPiModelsSync(models, _userAuth) {
563
+ const result = [];
564
+ for (const provider of getProviders()) {
565
+ try {
566
+ for (const model of getModels(provider) ?? [])
567
+ result.push(model);
568
+ }
569
+ catch {
570
+ // A static provider catalog failure must not make the sync auth guard fail.
571
+ }
572
+ }
573
+ for (const [provider, config] of Object.entries(models.providers)) {
574
+ for (const model of config.models ?? []) {
575
+ result.push({ provider, id: model.id, name: model.name });
576
+ }
577
+ }
578
+ return result;
579
+ }
580
+ function emptyPiModelCost() {
581
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
582
+ }
583
+ function normalizeProvider(value) {
584
+ if (typeof value !== 'string')
585
+ return undefined;
586
+ const normalized = value.trim();
587
+ return normalized && normalized !== 'default' && normalized !== 'auto'
588
+ ? normalized
589
+ : undefined;
590
+ }
591
+ function normalizeModelId(value) {
592
+ if (typeof value !== 'string')
593
+ return undefined;
594
+ const normalized = value.trim();
595
+ return normalized && normalized !== 'default' && normalized !== 'auto'
596
+ ? normalized
597
+ : undefined;
598
+ }
599
+ function providerFromModelId(value) {
600
+ const modelId = normalizeModelId(value);
601
+ if (!modelId?.includes('/'))
602
+ return undefined;
603
+ return normalizeProvider(modelId.slice(0, modelId.indexOf('/')));
604
+ }
605
+ function firstString(...values) {
606
+ for (const value of values) {
607
+ if (typeof value !== 'string')
608
+ continue;
609
+ const normalized = value.trim();
610
+ if (normalized && normalized !== 'default' && normalized !== 'auto')
611
+ return normalized;
612
+ }
613
+ return undefined;
614
+ }
615
+ /** Return the concrete environment variables recognised by Pi for a provider. */
616
+ export function formatPiProviderEnvHint(provider) {
617
+ // `findEnvKeys` normally reports only variables that are currently set. A
618
+ // truthy probe lets the SDK expose its existing provider mapping even for a
619
+ // missing credential, which is the case where this recovery hint is used.
620
+ const envKeys = findEnvKeys(provider, PI_ENV_HINT_PROBE);
621
+ if (envKeys)
622
+ return envKeys.join(' / ');
623
+ if (provider === 'google-vertex') {
624
+ return 'GOOGLE_APPLICATION_CREDENTIALS / GOOGLE_CLOUD_PROJECT / GOOGLE_CLOUD_LOCATION';
625
+ }
626
+ if (provider === 'amazon-bedrock') {
627
+ return ('AWS_PROFILE / AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY / ' +
628
+ 'AWS_BEARER_TOKEN_BEDROCK');
629
+ }
630
+ return `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}_API_KEY`;
631
+ }
632
+ const PI_ENV_HINT_PROBE = new Proxy({}, { get: (_target, property) => (typeof property === 'string' ? 'configured' : undefined) });
633
+ function isRecord(value) {
634
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
635
+ }
636
+ function readPiJsonSync(path) {
637
+ try {
638
+ const value = JSON.parse(readFileSync(path, 'utf8'));
639
+ return isRecord(value) ? value : undefined;
640
+ }
641
+ catch {
642
+ return undefined;
643
+ }
644
+ }
645
+ function mergePiRecords(base, override) {
646
+ const merged = { ...base };
647
+ for (const [key, value] of Object.entries(override)) {
648
+ const previous = merged[key];
649
+ merged[key] =
650
+ isRecord(previous) && isRecord(value)
651
+ ? mergePiRecords(previous, value)
652
+ : value;
653
+ }
654
+ return merged;
655
+ }
656
+ //# sourceMappingURL=conductor-pi-auth.js.map