@aitne/shared 0.1.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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/dist/advisor-models.d.ts +34 -0
  3. package/dist/advisor-models.d.ts.map +1 -0
  4. package/dist/advisor-models.js +39 -0
  5. package/dist/advisor-models.js.map +1 -0
  6. package/dist/agent-identity.d.ts +11 -0
  7. package/dist/agent-identity.d.ts.map +1 -0
  8. package/dist/agent-identity.js +29 -0
  9. package/dist/agent-identity.js.map +1 -0
  10. package/dist/alerts.d.ts +44 -0
  11. package/dist/alerts.d.ts.map +1 -0
  12. package/dist/alerts.js +12 -0
  13. package/dist/alerts.js.map +1 -0
  14. package/dist/backend-api-key-config.d.ts +337 -0
  15. package/dist/backend-api-key-config.d.ts.map +1 -0
  16. package/dist/backend-api-key-config.js +682 -0
  17. package/dist/backend-api-key-config.js.map +1 -0
  18. package/dist/backend.d.ts +93 -0
  19. package/dist/backend.d.ts.map +1 -0
  20. package/dist/backend.js +22 -0
  21. package/dist/backend.js.map +1 -0
  22. package/dist/branding.d.ts +96 -0
  23. package/dist/branding.d.ts.map +1 -0
  24. package/dist/branding.js +102 -0
  25. package/dist/branding.js.map +1 -0
  26. package/dist/chat-session-scope.d.ts +14 -0
  27. package/dist/chat-session-scope.d.ts.map +1 -0
  28. package/dist/chat-session-scope.js +18 -0
  29. package/dist/chat-session-scope.js.map +1 -0
  30. package/dist/date-utils.d.ts +80 -0
  31. package/dist/date-utils.d.ts.map +1 -0
  32. package/dist/date-utils.js +187 -0
  33. package/dist/date-utils.js.map +1 -0
  34. package/dist/docs-frontmatter.d.ts +51 -0
  35. package/dist/docs-frontmatter.d.ts.map +1 -0
  36. package/dist/docs-frontmatter.js +184 -0
  37. package/dist/docs-frontmatter.js.map +1 -0
  38. package/dist/docs-schema.d.ts +79 -0
  39. package/dist/docs-schema.d.ts.map +1 -0
  40. package/dist/docs-schema.js +135 -0
  41. package/dist/docs-schema.js.map +1 -0
  42. package/dist/editable-config-keys.d.ts +14 -0
  43. package/dist/editable-config-keys.d.ts.map +1 -0
  44. package/dist/editable-config-keys.js +157 -0
  45. package/dist/editable-config-keys.js.map +1 -0
  46. package/dist/exec-with-stdin.d.ts +14 -0
  47. package/dist/exec-with-stdin.d.ts.map +1 -0
  48. package/dist/exec-with-stdin.js +35 -0
  49. package/dist/exec-with-stdin.js.map +1 -0
  50. package/dist/index.d.ts +37 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +49 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/integrations-snapshot.d.ts +183 -0
  55. package/dist/integrations-snapshot.d.ts.map +1 -0
  56. package/dist/integrations-snapshot.js +757 -0
  57. package/dist/integrations-snapshot.js.map +1 -0
  58. package/dist/integrations.d.ts +675 -0
  59. package/dist/integrations.d.ts.map +1 -0
  60. package/dist/integrations.js +1656 -0
  61. package/dist/integrations.js.map +1 -0
  62. package/dist/keychain-helper-client.d.ts +31 -0
  63. package/dist/keychain-helper-client.d.ts.map +1 -0
  64. package/dist/keychain-helper-client.js +105 -0
  65. package/dist/keychain-helper-client.js.map +1 -0
  66. package/dist/log-entry.d.ts +14 -0
  67. package/dist/log-entry.d.ts.map +1 -0
  68. package/dist/log-entry.js +2 -0
  69. package/dist/log-entry.js.map +1 -0
  70. package/dist/management-domains.d.ts +369 -0
  71. package/dist/management-domains.d.ts.map +1 -0
  72. package/dist/management-domains.js +499 -0
  73. package/dist/management-domains.js.map +1 -0
  74. package/dist/process-key.d.ts +67 -0
  75. package/dist/process-key.d.ts.map +1 -0
  76. package/dist/process-key.js +366 -0
  77. package/dist/process-key.js.map +1 -0
  78. package/dist/schemas.d.ts +267 -0
  79. package/dist/schemas.d.ts.map +1 -0
  80. package/dist/schemas.js +271 -0
  81. package/dist/schemas.js.map +1 -0
  82. package/dist/secret-client-factory.d.ts +16 -0
  83. package/dist/secret-client-factory.d.ts.map +1 -0
  84. package/dist/secret-client-factory.js +111 -0
  85. package/dist/secret-client-factory.js.map +1 -0
  86. package/dist/secret-client-file.d.ts +51 -0
  87. package/dist/secret-client-file.d.ts.map +1 -0
  88. package/dist/secret-client-file.js +160 -0
  89. package/dist/secret-client-file.js.map +1 -0
  90. package/dist/secret-client-linux.d.ts +26 -0
  91. package/dist/secret-client-linux.d.ts.map +1 -0
  92. package/dist/secret-client-linux.js +63 -0
  93. package/dist/secret-client-linux.js.map +1 -0
  94. package/dist/secret-client-windows.d.ts +37 -0
  95. package/dist/secret-client-windows.d.ts.map +1 -0
  96. package/dist/secret-client-windows.js +82 -0
  97. package/dist/secret-client-windows.js.map +1 -0
  98. package/dist/secret-redaction.d.ts +3 -0
  99. package/dist/secret-redaction.d.ts.map +1 -0
  100. package/dist/secret-redaction.js +31 -0
  101. package/dist/secret-redaction.js.map +1 -0
  102. package/dist/skill-curation/decision-language.d.ts +6 -0
  103. package/dist/skill-curation/decision-language.d.ts.map +1 -0
  104. package/dist/skill-curation/decision-language.js +38 -0
  105. package/dist/skill-curation/decision-language.js.map +1 -0
  106. package/dist/skill-curation/schemas.d.ts +461 -0
  107. package/dist/skill-curation/schemas.d.ts.map +1 -0
  108. package/dist/skill-curation/schemas.js +211 -0
  109. package/dist/skill-curation/schemas.js.map +1 -0
  110. package/dist/types.d.ts +204 -0
  111. package/dist/types.d.ts.map +1 -0
  112. package/dist/types.js +54 -0
  113. package/dist/types.js.map +1 -0
  114. package/package.json +50 -0
@@ -0,0 +1,682 @@
1
+ /**
2
+ * Per-backend provider auth configuration.
3
+ *
4
+ * Direct API keys (Anthropic / OpenAI / Google) are the long-standing path,
5
+ * but Claude Code's SDK also supports cloud-hosted Anthropic deployments,
6
+ * and Gemini CLI supports Vertex AI:
7
+ *
8
+ * Claude Code:
9
+ * - Amazon Bedrock β†’ CLAUDE_CODE_USE_BEDROCK=1 + AWS creds (access key /
10
+ * bearer token / profile) + AWS_REGION
11
+ * - Google Vertex AI β†’ CLAUDE_CODE_USE_VERTEX=1 + ANTHROPIC_VERTEX_PROJECT_ID +
12
+ * CLOUD_ML_REGION (creds via Application Default
13
+ * Credentials chain or GOOGLE_APPLICATION_CREDENTIALS file)
14
+ * - Microsoft Foundry β†’ CLAUDE_CODE_USE_FOUNDRY=1 + ANTHROPIC_FOUNDRY_RESOURCE
15
+ * (or ANTHROPIC_FOUNDRY_BASE_URL) + optional
16
+ * ANTHROPIC_FOUNDRY_API_KEY (Entra ID auto-fallback)
17
+ *
18
+ * Gemini CLI:
19
+ * - Vertex AI β†’ GOOGLE_GENAI_USE_VERTEXAI=true + GOOGLE_CLOUD_PROJECT +
20
+ * GOOGLE_CLOUD_LOCATION (auth via ADC / service account
21
+ * file / Vertex API key)
22
+ *
23
+ * Codex CLI's Azure OpenAI mode requires a `~/.codex/config.toml` file and is
24
+ * NOT exposed through this surface β€” env-var mirroring is insufficient to
25
+ * configure it. Codex stays direct-API-key only here.
26
+ *
27
+ * The daemon stores the chosen provider + its credentials as a single JSON
28
+ * blob in the OS keychain (`backend.<id>.api_key`). At startup and on every
29
+ * UI mutation the daemon mirrors the active provider's env vars into
30
+ * `process.env`, so the unchanged Claude SDK / Codex CLI / Gemini CLI
31
+ * subprocesses pick them up via the same inherited-env path.
32
+ *
33
+ * Backwards compatibility: legacy entries written before this feature were
34
+ * raw strings (the API key itself). `parseBackendApiKeyConfig` accepts both
35
+ * the new JSON shape and the legacy raw-string form.
36
+ *
37
+ * Env-var spec sources (verified 2026-05):
38
+ * - https://code.claude.com/docs/en/amazon-bedrock
39
+ * - https://code.claude.com/docs/en/google-vertex-ai
40
+ * - https://code.claude.com/docs/en/microsoft-foundry
41
+ * - https://geminicli.com/docs/get-started/authentication/
42
+ */
43
+ import { z } from "zod";
44
+ // ── Provider IDs per backend ─────────────────────────────────────────
45
+ export const CLAUDE_API_KEY_PROVIDERS = [
46
+ "anthropic",
47
+ "bedrock",
48
+ "vertex",
49
+ "foundry",
50
+ ];
51
+ export const CODEX_API_KEY_PROVIDERS = ["openai", "azure-openai"];
52
+ export const GEMINI_API_KEY_PROVIDERS = ["google", "gemini-vertex"];
53
+ export const API_KEY_PROVIDERS_BY_BACKEND = {
54
+ claude: CLAUDE_API_KEY_PROVIDERS,
55
+ codex: CODEX_API_KEY_PROVIDERS,
56
+ gemini: GEMINI_API_KEY_PROVIDERS,
57
+ };
58
+ export function defaultApiKeyProvider(backendId) {
59
+ return API_KEY_PROVIDERS_BY_BACKEND[backendId][0];
60
+ }
61
+ export function isApiKeyProviderForBackend(backendId, provider) {
62
+ return API_KEY_PROVIDERS_BY_BACKEND[backendId].includes(provider);
63
+ }
64
+ // ── Discriminated config union ───────────────────────────────────────
65
+ export const anthropicApiKeyConfigSchema = z.object({
66
+ provider: z.literal("anthropic"),
67
+ apiKey: z.string().min(1),
68
+ });
69
+ /**
70
+ * Bedrock supports three documented auth paths:
71
+ * - `access_key` β€” AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (+ optional
72
+ * AWS_SESSION_TOKEN for STS / Identity Center)
73
+ * - `bearer_token` β€” AWS_BEARER_TOKEN_BEDROCK (Bedrock API key β€” simplest)
74
+ * - `profile` β€” AWS_PROFILE (delegates to ~/.aws/config / ~/.aws/credentials)
75
+ *
76
+ * AWS_REGION is required by Claude Code regardless of the auth mode (the
77
+ * docs explicitly say `Claude Code does not read from the .aws config
78
+ * file for this setting`). `bedrockBaseUrl` is an optional override for
79
+ * VPC endpoints / gateways.
80
+ */
81
+ const bedrockAccessKeySchema = z.object({
82
+ authMode: z.literal("access_key"),
83
+ awsAccessKeyId: z.string().min(1),
84
+ awsSecretAccessKey: z.string().min(1),
85
+ awsSessionToken: z.string().min(1).optional(),
86
+ });
87
+ const bedrockBearerTokenSchema = z.object({
88
+ authMode: z.literal("bearer_token"),
89
+ awsBearerTokenBedrock: z.string().min(1),
90
+ });
91
+ const bedrockProfileSchema = z.object({
92
+ authMode: z.literal("profile"),
93
+ awsProfile: z.string().min(1),
94
+ });
95
+ /**
96
+ * Per-provider model pinning fields. Setting any of these mirrors to the
97
+ * `ANTHROPIC_DEFAULT_OPUS_MODEL` / `ANTHROPIC_DEFAULT_SONNET_MODEL` /
98
+ * `ANTHROPIC_DEFAULT_HAIKU_MODEL` env vars so the `opus`/`sonnet`/`haiku`
99
+ * aliases resolve to the cloud-native model ID instead of the latest
100
+ * version (which may not be enabled in the customer's account, causing
101
+ * a 404 or fallback at startup). The Anthropic docs explicitly call this
102
+ * out as required for production deployments.
103
+ *
104
+ * The field shape is shared across Bedrock / Vertex / Foundry β€” only the
105
+ * recommended *defaults* differ per cloud (see
106
+ * `RECOMMENDED_PINNED_MODELS_BY_PROVIDER`).
107
+ */
108
+ const pinnedModelsSchema = z.object({
109
+ defaultOpusModel: z.string().min(1).optional(),
110
+ defaultSonnetModel: z.string().min(1).optional(),
111
+ defaultHaikuModel: z.string().min(1).optional(),
112
+ });
113
+ /**
114
+ * Recommended model IDs per cloud. The dashboard pre-fills the pinning
115
+ * fields with these values, but the operator can edit. Update these
116
+ * alongside Anthropic's docs when a new model rolls out across all clouds.
117
+ */
118
+ export const RECOMMENDED_PINNED_MODELS_BY_PROVIDER = {
119
+ bedrock: {
120
+ defaultOpusModel: "us.anthropic.claude-opus-4-7",
121
+ defaultSonnetModel: "us.anthropic.claude-sonnet-4-6",
122
+ defaultHaikuModel: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
123
+ },
124
+ vertex: {
125
+ defaultOpusModel: "claude-opus-4-7",
126
+ defaultSonnetModel: "claude-sonnet-4-6",
127
+ defaultHaikuModel: "claude-haiku-4-5@20251001",
128
+ },
129
+ foundry: {
130
+ defaultOpusModel: "claude-opus-4-7",
131
+ defaultSonnetModel: "claude-sonnet-4-6",
132
+ defaultHaikuModel: "claude-haiku-4-5",
133
+ },
134
+ };
135
+ /**
136
+ * Bedrock-specific extras: Mantle endpoint flag + optional overrides.
137
+ * Mantle is an alternate Bedrock endpoint that uses the native Anthropic
138
+ * API shape; setting both `CLAUDE_CODE_USE_BEDROCK=1` and
139
+ * `CLAUDE_CODE_USE_MANTLE=1` is supported (Claude Code routes per-model).
140
+ */
141
+ const bedrockExtrasSchema = z.object({
142
+ bedrockBaseUrl: z.string().url().optional(),
143
+ // Mantle endpoint
144
+ useMantle: z.boolean().optional(),
145
+ mantleBaseUrl: z.string().url().optional(),
146
+ // Skip client-side auth (for gateways that inject AWS creds server-side)
147
+ skipMantleAuth: z.boolean().optional(),
148
+ // Per-model region override for the Haiku-class small/fast model
149
+ smallFastModelAwsRegion: z.string().min(1).optional(),
150
+ });
151
+ export const bedrockApiKeyConfigSchema = z
152
+ .discriminatedUnion("authMode", [
153
+ bedrockAccessKeySchema,
154
+ bedrockBearerTokenSchema,
155
+ bedrockProfileSchema,
156
+ ])
157
+ .and(z.object({
158
+ provider: z.literal("bedrock"),
159
+ awsRegion: z.string().min(1),
160
+ }))
161
+ .and(bedrockExtrasSchema)
162
+ .and(pinnedModelsSchema);
163
+ export const vertexApiKeyConfigSchema = z
164
+ .object({
165
+ provider: z.literal("vertex"),
166
+ projectId: z.string().min(1),
167
+ region: z.string().min(1),
168
+ // Optional file path for a service-account key file. Mirrors to
169
+ // GOOGLE_APPLICATION_CREDENTIALS. When omitted, the SDK uses Application
170
+ // Default Credentials (e.g. `gcloud auth application-default login`).
171
+ // The previous `serviceAccountJson` (inline JSON) field was removed β€”
172
+ // Anthropic's SDK does NOT read inline JSON; the env var is a file path.
173
+ credentialsFile: z.string().min(1).optional(),
174
+ vertexBaseUrl: z.string().url().optional(),
175
+ })
176
+ .and(pinnedModelsSchema);
177
+ /**
178
+ * Foundry needs *one* of `resource` (the Azure resource name; daemon
179
+ * routes to https://<resource>.services.ai.azure.com/anthropic) OR
180
+ * `baseUrl` (the full URL). API key is **optional** β€” when omitted,
181
+ * Claude Code uses the Azure DefaultAzureCredential chain (e.g. `az
182
+ * login` / managed identity).
183
+ */
184
+ export const foundryApiKeyConfigSchema = z
185
+ .object({
186
+ provider: z.literal("foundry"),
187
+ resource: z.string().min(1).optional(),
188
+ baseUrl: z.string().url().optional(),
189
+ apiKey: z.string().min(1).optional(),
190
+ })
191
+ .and(pinnedModelsSchema)
192
+ .refine((v) => Boolean(v.resource) !== Boolean(v.baseUrl), {
193
+ message: "Foundry config requires exactly one of `resource` or `baseUrl`.",
194
+ });
195
+ export const openaiApiKeyConfigSchema = z.object({
196
+ provider: z.literal("openai"),
197
+ apiKey: z.string().min(1),
198
+ });
199
+ /**
200
+ * Codex CLI on Azure OpenAI. Codex CLI requires a `[model_providers.azure]`
201
+ * block in `config.toml` β€” env vars alone are insufficient. The daemon
202
+ * works around this by writing a managed `config.toml` to
203
+ * `<PA_DATA_DIR>/codex-home/config.toml` and pointing `CODEX_HOME` at
204
+ * that directory for spawned codex subprocesses, leaving the operator's
205
+ * personal `~/.codex/` configuration untouched.
206
+ *
207
+ * Required: `resource` (Azure resource name) and `apiKey` (mirrored to
208
+ * `AZURE_OPENAI_API_KEY`). Optional: `apiVersion` (defaults to the latest
209
+ * preview version Codex docs recommend) and `deploymentName` β€” when set,
210
+ * Codex's `model` setting is pinned to this deployment.
211
+ *
212
+ * **Known limitation: `--model` flag override.** The daemon's CodexCore
213
+ * spawns Codex with `--model <id>` where `<id>` is the per-process
214
+ * model from the registry (e.g. `gpt-5-codex`). On Azure, Codex treats
215
+ * the model argument as the *deployment name*, so the operator MUST
216
+ * name their Azure deployment to match the model IDs the daemon's
217
+ * routing uses. Renaming the deployment in Azure is the supported fix;
218
+ * a future round may plumb the active provider into CodexCore so the
219
+ * daemon can rewrite `--model` automatically.
220
+ */
221
+ export const DEFAULT_AZURE_OPENAI_API_VERSION = "2025-04-01-preview";
222
+ export const azureOpenAiApiKeyConfigSchema = z.object({
223
+ provider: z.literal("azure-openai"),
224
+ resource: z.string().min(1),
225
+ apiKey: z.string().min(1),
226
+ apiVersion: z.string().min(1).optional(),
227
+ deploymentName: z.string().min(1).optional(),
228
+ });
229
+ export const googleApiKeyConfigSchema = z.object({
230
+ provider: z.literal("google"),
231
+ apiKey: z.string().min(1),
232
+ });
233
+ /**
234
+ * Gemini CLI on Vertex AI. Three auth paths:
235
+ * - `adc` β€” Application Default Credentials (gcloud)
236
+ * - `service_account` β€” GOOGLE_APPLICATION_CREDENTIALS file path
237
+ * - `api_key` β€” GOOGLE_API_KEY (Vertex API key, distinct from
238
+ * the direct `google` provider's GEMINI_API_KEY)
239
+ *
240
+ * `GOOGLE_CLOUD_PROJECT` + `GOOGLE_CLOUD_LOCATION` are required for all
241
+ * three modes. `GOOGLE_GENAI_USE_VERTEXAI=true` flips the SDK to Vertex.
242
+ */
243
+ const geminiVertexAdcSchema = z.object({ authMode: z.literal("adc") });
244
+ const geminiVertexServiceAccountSchema = z.object({
245
+ authMode: z.literal("service_account"),
246
+ credentialsFile: z.string().min(1),
247
+ });
248
+ const geminiVertexApiKeySchema = z.object({
249
+ authMode: z.literal("api_key"),
250
+ apiKey: z.string().min(1),
251
+ });
252
+ export const geminiVertexApiKeyConfigSchema = z
253
+ .discriminatedUnion("authMode", [
254
+ geminiVertexAdcSchema,
255
+ geminiVertexServiceAccountSchema,
256
+ geminiVertexApiKeySchema,
257
+ ])
258
+ .and(z.object({
259
+ provider: z.literal("gemini-vertex"),
260
+ projectId: z.string().min(1),
261
+ location: z.string().min(1),
262
+ }));
263
+ export const backendApiKeyConfigSchema = z.union([
264
+ anthropicApiKeyConfigSchema,
265
+ bedrockApiKeyConfigSchema,
266
+ vertexApiKeyConfigSchema,
267
+ foundryApiKeyConfigSchema,
268
+ openaiApiKeyConfigSchema,
269
+ azureOpenAiApiKeyConfigSchema,
270
+ googleApiKeyConfigSchema,
271
+ geminiVertexApiKeyConfigSchema,
272
+ ]);
273
+ // ── Env var management ───────────────────────────────────────────────
274
+ /**
275
+ * Every env var the daemon may set or clear when mirroring auth state for a
276
+ * backend. Returned as a stable list so `backend-api-key-env.ts` can snapshot
277
+ * the operator's shell values once at startup and restore them on UI clear.
278
+ *
279
+ * The list is the **superset** across providers: switching from Anthropic to
280
+ * Bedrock must clear `ANTHROPIC_API_KEY` and set `CLAUDE_CODE_USE_BEDROCK=1`
281
+ * + AWS_*, so both must appear here even though they are never set together.
282
+ */
283
+ export function getManagedApiKeyEnvVars(backendId) {
284
+ switch (backendId) {
285
+ case "claude":
286
+ return [
287
+ "ANTHROPIC_API_KEY",
288
+ // Bedrock β€” covers all three auth modes
289
+ "CLAUDE_CODE_USE_BEDROCK",
290
+ "AWS_REGION",
291
+ "AWS_ACCESS_KEY_ID",
292
+ "AWS_SECRET_ACCESS_KEY",
293
+ "AWS_SESSION_TOKEN",
294
+ "AWS_BEARER_TOKEN_BEDROCK",
295
+ "AWS_PROFILE",
296
+ "ANTHROPIC_BEDROCK_BASE_URL",
297
+ // Bedrock Mantle endpoint
298
+ "CLAUDE_CODE_USE_MANTLE",
299
+ "ANTHROPIC_BEDROCK_MANTLE_BASE_URL",
300
+ "CLAUDE_CODE_SKIP_MANTLE_AUTH",
301
+ "ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION",
302
+ // Vertex
303
+ "CLAUDE_CODE_USE_VERTEX",
304
+ "ANTHROPIC_VERTEX_PROJECT_ID",
305
+ "CLOUD_ML_REGION",
306
+ "GOOGLE_APPLICATION_CREDENTIALS",
307
+ "ANTHROPIC_VERTEX_BASE_URL",
308
+ // Foundry
309
+ "CLAUDE_CODE_USE_FOUNDRY",
310
+ "ANTHROPIC_FOUNDRY_RESOURCE",
311
+ "ANTHROPIC_FOUNDRY_BASE_URL",
312
+ "ANTHROPIC_FOUNDRY_API_KEY",
313
+ // Cloud-only model pinning (shared across Bedrock / Vertex / Foundry)
314
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
315
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
316
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
317
+ ];
318
+ case "codex":
319
+ return [
320
+ "OPENAI_API_KEY",
321
+ // Azure OpenAI β€” daemon points CODEX_HOME at a managed config.toml
322
+ // dir so the user's `~/.codex/` config is untouched.
323
+ "AZURE_OPENAI_API_KEY",
324
+ "CODEX_HOME",
325
+ ];
326
+ case "gemini":
327
+ return [
328
+ "GEMINI_API_KEY",
329
+ "GOOGLE_API_KEY",
330
+ // Vertex AI on Gemini CLI
331
+ "GOOGLE_GENAI_USE_VERTEXAI",
332
+ "GOOGLE_CLOUD_PROJECT",
333
+ "GOOGLE_CLOUD_LOCATION",
334
+ "GOOGLE_APPLICATION_CREDENTIALS",
335
+ ];
336
+ }
337
+ }
338
+ /**
339
+ * Map a partial set of pinned model fields to their `ANTHROPIC_DEFAULT_*_MODEL`
340
+ * env vars. Skips any field the operator left unset so the cloud's built-in
341
+ * default takes over (matching the docs' fallback semantics).
342
+ */
343
+ function pinnedModelEnvAssignments(config) {
344
+ const out = {};
345
+ if (config.defaultOpusModel) {
346
+ out.ANTHROPIC_DEFAULT_OPUS_MODEL = config.defaultOpusModel;
347
+ }
348
+ if (config.defaultSonnetModel) {
349
+ out.ANTHROPIC_DEFAULT_SONNET_MODEL = config.defaultSonnetModel;
350
+ }
351
+ if (config.defaultHaikuModel) {
352
+ out.ANTHROPIC_DEFAULT_HAIKU_MODEL = config.defaultHaikuModel;
353
+ }
354
+ return out;
355
+ }
356
+ /**
357
+ * Build the `config.toml` Codex CLI consumes when the daemon points
358
+ * `CODEX_HOME` at the managed directory.
359
+ *
360
+ * Per OpenAI Codex docs (verified 2026-05), the wire_api must be
361
+ * `responses` and the API version must be supplied as a `query_params`
362
+ * entry. Codex resolves the API key by reading `env_key` against
363
+ * `process.env`.
364
+ *
365
+ * **TOML layout matters.** `model_provider` (and `model`, when set) MUST
366
+ * appear *before* the `[model_providers.azure]` table β€” once a `[section]`
367
+ * header opens, all subsequent keys belong to that section until the next
368
+ * header. Emitting `model_provider = "azure"` after the section header
369
+ * would silently nest it as `model_providers.azure.model_provider`,
370
+ * leaving the top-level `model_provider` unset and Codex routing through
371
+ * the OpenAI default. Tested with `smol-toml`.
372
+ */
373
+ export function buildCodexAzureConfigToml(config) {
374
+ const apiVersion = config.apiVersion ?? DEFAULT_AZURE_OPENAI_API_VERSION;
375
+ const baseUrl = `https://${config.resource}.openai.azure.com/openai/v1`;
376
+ const lines = [
377
+ "# Generated by aitne β€” do not edit by hand.",
378
+ "# Source: dashboard provider-auth panel.",
379
+ "",
380
+ // Top-level keys FIRST. Keep these before any [section] header.
381
+ `model_provider = "azure"`,
382
+ ];
383
+ if (config.deploymentName) {
384
+ // Pin the active model to the operator's deployment name. Note:
385
+ // when the daemon's CodexCore invokes codex with `--model <id>`,
386
+ // the CLI flag overrides this. The operator should name their
387
+ // Azure deployment to match the model IDs the daemon's process
388
+ // routing maps to (see /settings/models).
389
+ lines.push(`model = "${config.deploymentName}"`);
390
+ }
391
+ lines.push("", "[model_providers.azure]", `name = "Azure OpenAI"`, `base_url = "${baseUrl}"`, `env_key = "AZURE_OPENAI_API_KEY"`, `query_params = { api-version = "${apiVersion}" }`, `wire_api = "responses"`, "");
392
+ return lines.join("\n");
393
+ }
394
+ /**
395
+ * Resolve which env vars to write for a given provider config. Returns a map
396
+ * of env-var name β†’ value. Env vars omitted from the map should be cleared
397
+ * (or restored to their captured shell value) by the caller.
398
+ *
399
+ * Note on `GOOGLE_APPLICATION_CREDENTIALS`: this is the Google standard env
400
+ * var name and expects a **file path** (not inline JSON). The earlier
401
+ * iteration of this code wrote `GOOGLE_APPLICATION_CREDENTIALS_JSON` with
402
+ * inline JSON, which the Anthropic SDK and gcloud SDK both ignore. Operators
403
+ * who want service-account-based auth supply the file path; operators who
404
+ * want ADC leave it blank and use `gcloud auth application-default login`.
405
+ *
406
+ * Note on `CODEX_HOME` (Azure OpenAI): this map does NOT include it. The
407
+ * daemon owns the managed config.toml directory path and adds `CODEX_HOME`
408
+ * separately when materializing the assignment β€” see the daemon-side
409
+ * `materializeCodexAzureConfig` helper.
410
+ */
411
+ export function getApiKeyEnvAssignments(config) {
412
+ switch (config.provider) {
413
+ case "anthropic":
414
+ return { ANTHROPIC_API_KEY: config.apiKey };
415
+ case "bedrock": {
416
+ const base = {
417
+ CLAUDE_CODE_USE_BEDROCK: "1",
418
+ AWS_REGION: config.awsRegion,
419
+ ...(config.bedrockBaseUrl
420
+ ? { ANTHROPIC_BEDROCK_BASE_URL: config.bedrockBaseUrl }
421
+ : {}),
422
+ // Mantle endpoint (set alongside CLAUDE_CODE_USE_BEDROCK β€” they're
423
+ // not mutually exclusive: setting both lets Claude Code route per
424
+ // model, sending Mantle-format IDs to Mantle and Invoke-API IDs
425
+ // to standard Bedrock).
426
+ ...(config.useMantle ? { CLAUDE_CODE_USE_MANTLE: "1" } : {}),
427
+ ...(config.mantleBaseUrl
428
+ ? { ANTHROPIC_BEDROCK_MANTLE_BASE_URL: config.mantleBaseUrl }
429
+ : {}),
430
+ ...(config.skipMantleAuth ? { CLAUDE_CODE_SKIP_MANTLE_AUTH: "1" } : {}),
431
+ ...(config.smallFastModelAwsRegion
432
+ ? {
433
+ ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION: config.smallFastModelAwsRegion,
434
+ }
435
+ : {}),
436
+ ...pinnedModelEnvAssignments(config),
437
+ };
438
+ switch (config.authMode) {
439
+ case "access_key":
440
+ return {
441
+ ...base,
442
+ AWS_ACCESS_KEY_ID: config.awsAccessKeyId,
443
+ AWS_SECRET_ACCESS_KEY: config.awsSecretAccessKey,
444
+ ...(config.awsSessionToken
445
+ ? { AWS_SESSION_TOKEN: config.awsSessionToken }
446
+ : {}),
447
+ };
448
+ case "bearer_token":
449
+ return {
450
+ ...base,
451
+ AWS_BEARER_TOKEN_BEDROCK: config.awsBearerTokenBedrock,
452
+ };
453
+ case "profile":
454
+ return { ...base, AWS_PROFILE: config.awsProfile };
455
+ /* v8 ignore next 6 */
456
+ }
457
+ // Exhaustiveness guard β€” TS verifies all authMode branches are covered.
458
+ const _exhaustive = config;
459
+ void _exhaustive;
460
+ return base;
461
+ }
462
+ case "vertex":
463
+ return {
464
+ CLAUDE_CODE_USE_VERTEX: "1",
465
+ ANTHROPIC_VERTEX_PROJECT_ID: config.projectId,
466
+ CLOUD_ML_REGION: config.region,
467
+ ...(config.credentialsFile
468
+ ? { GOOGLE_APPLICATION_CREDENTIALS: config.credentialsFile }
469
+ : {}),
470
+ ...(config.vertexBaseUrl
471
+ ? { ANTHROPIC_VERTEX_BASE_URL: config.vertexBaseUrl }
472
+ : {}),
473
+ ...pinnedModelEnvAssignments(config),
474
+ };
475
+ case "foundry":
476
+ return {
477
+ CLAUDE_CODE_USE_FOUNDRY: "1",
478
+ ...(config.resource
479
+ ? { ANTHROPIC_FOUNDRY_RESOURCE: config.resource }
480
+ : {}),
481
+ ...(config.baseUrl
482
+ ? { ANTHROPIC_FOUNDRY_BASE_URL: config.baseUrl }
483
+ : {}),
484
+ ...(config.apiKey
485
+ ? { ANTHROPIC_FOUNDRY_API_KEY: config.apiKey }
486
+ : {}),
487
+ ...pinnedModelEnvAssignments(config),
488
+ };
489
+ case "openai":
490
+ return { OPENAI_API_KEY: config.apiKey };
491
+ case "azure-openai":
492
+ // CODEX_HOME is owned by the daemon (it points at the managed
493
+ // config.toml directory). The daemon's Codex Azure materializer
494
+ // computes the path and adds CODEX_HOME alongside this map; the
495
+ // pure shared layer just provides the env vars Codex CLI itself
496
+ // needs at runtime to fetch credentials.
497
+ return {
498
+ AZURE_OPENAI_API_KEY: config.apiKey,
499
+ };
500
+ case "google":
501
+ // Direct Gemini API β€” populates BOTH aliases so the SDK reads a
502
+ // consistent value. Notably this does NOT set the Vertex flag.
503
+ return {
504
+ GEMINI_API_KEY: config.apiKey,
505
+ GOOGLE_API_KEY: config.apiKey,
506
+ };
507
+ case "gemini-vertex": {
508
+ const base = {
509
+ GOOGLE_GENAI_USE_VERTEXAI: "true",
510
+ GOOGLE_CLOUD_PROJECT: config.projectId,
511
+ GOOGLE_CLOUD_LOCATION: config.location,
512
+ };
513
+ switch (config.authMode) {
514
+ case "adc":
515
+ return base;
516
+ case "service_account":
517
+ return {
518
+ ...base,
519
+ GOOGLE_APPLICATION_CREDENTIALS: config.credentialsFile,
520
+ };
521
+ case "api_key":
522
+ // Vertex API-key auth uses GOOGLE_API_KEY (not GEMINI_API_KEY).
523
+ return { ...base, GOOGLE_API_KEY: config.apiKey };
524
+ /* v8 ignore next 6 */
525
+ }
526
+ // Exhaustiveness guard β€” TS verifies all authMode branches are covered.
527
+ const _exhaustive = config;
528
+ void _exhaustive;
529
+ return base;
530
+ }
531
+ }
532
+ }
533
+ // ── Parse / serialize ────────────────────────────────────────────────
534
+ /**
535
+ * Decode the raw keychain string for a backend into a typed config.
536
+ *
537
+ * Three accepted forms (highest priority first):
538
+ * 1. JSON-encoded `BackendApiKeyConfig` β€” the new format.
539
+ * 2. Legacy raw API key (non-JSON string) β€” promoted to the backend's
540
+ * default direct provider (anthropic / openai / google).
541
+ * 3. `null` / blank β€” no config.
542
+ */
543
+ export function parseBackendApiKeyConfig(backendId, raw) {
544
+ if (!raw)
545
+ return null;
546
+ const trimmed = raw.trim();
547
+ if (!trimmed)
548
+ return null;
549
+ // New format: JSON-encoded discriminated union.
550
+ if (trimmed.startsWith("{")) {
551
+ try {
552
+ const parsed = backendApiKeyConfigSchema.safeParse(JSON.parse(trimmed));
553
+ if (parsed.success
554
+ && isApiKeyProviderForBackend(backendId, parsed.data.provider)) {
555
+ return parsed.data;
556
+ }
557
+ return null;
558
+ }
559
+ catch {
560
+ return null;
561
+ }
562
+ }
563
+ // Legacy raw API key β€” promote to the default direct provider for the
564
+ // backend. This keeps existing keychain entries working byte-for-byte.
565
+ switch (backendId) {
566
+ case "claude":
567
+ return { provider: "anthropic", apiKey: trimmed };
568
+ case "codex":
569
+ return { provider: "openai", apiKey: trimmed };
570
+ case "gemini":
571
+ return { provider: "google", apiKey: trimmed };
572
+ }
573
+ }
574
+ /** Encode a typed config back into the JSON form stored in the keychain. */
575
+ export function serializeBackendApiKeyConfig(config) {
576
+ return JSON.stringify(config);
577
+ }
578
+ // ── Format validation ────────────────────────────────────────────────
579
+ const ANTHROPIC_KEY_PATTERN = /^sk-ant-[A-Za-z0-9_-]{20,}$/;
580
+ const OPENAI_KEY_PATTERN = /^sk-[A-Za-z0-9_-]{30,}$/;
581
+ const GEMINI_KEY_PATTERN = /^AIza[0-9A-Za-z_-]{35}$/;
582
+ export function isPlausibleAnthropicApiKey(value) {
583
+ return ANTHROPIC_KEY_PATTERN.test(value.trim());
584
+ }
585
+ export function isPlausibleOpenAiApiKey(value) {
586
+ const trimmed = value.trim();
587
+ if (/^sk-ant-/.test(trimmed))
588
+ return false;
589
+ return OPENAI_KEY_PATTERN.test(trimmed);
590
+ }
591
+ export function isPlausibleGeminiApiKey(value) {
592
+ return GEMINI_KEY_PATTERN.test(value.trim());
593
+ }
594
+ /**
595
+ * Best-effort format check on a populated config. Returns null when the
596
+ * shape looks plausible, or a human-readable hint when something obvious
597
+ * is wrong. Server-side probes are still authoritative β€” this catches
598
+ * cheap typos before the keychain write.
599
+ */
600
+ export function validateBackendApiKeyConfigFormat(backendId, config) {
601
+ if (!isApiKeyProviderForBackend(backendId, config.provider)) {
602
+ return `Provider "${config.provider}" is not valid for the ${backendId} backend.`;
603
+ }
604
+ switch (config.provider) {
605
+ case "anthropic":
606
+ if (!isPlausibleAnthropicApiKey(config.apiKey)) {
607
+ return "Expected an Anthropic API key beginning with `sk-ant-…`.";
608
+ }
609
+ return null;
610
+ case "openai":
611
+ if (!isPlausibleOpenAiApiKey(config.apiKey)) {
612
+ return "Expected an OpenAI API key beginning with `sk-…` (not `sk-ant-…`).";
613
+ }
614
+ return null;
615
+ case "azure-openai":
616
+ if (!config.resource.trim() || /\s/.test(config.resource)) {
617
+ return "Azure resource name looks malformed (no spaces; e.g. `my-foundry-resource`).";
618
+ }
619
+ if (!config.apiKey.trim()) {
620
+ return "Azure OpenAI API key is required.";
621
+ }
622
+ return null;
623
+ case "google":
624
+ if (!isPlausibleGeminiApiKey(config.apiKey)) {
625
+ return "Expected a Google API key beginning with `AIza…` (39 chars total).";
626
+ }
627
+ return null;
628
+ case "bedrock": {
629
+ if (config.awsRegion.length < 2 || /\s/.test(config.awsRegion)) {
630
+ return "AWS region looks malformed (e.g. expected `us-east-1`).";
631
+ }
632
+ if (config.authMode === "access_key") {
633
+ if (!/^[A-Z0-9]{16,}$/.test(config.awsAccessKeyId)) {
634
+ return "AWS access key id should be uppercase alphanumeric (β‰₯ 16 chars, e.g. `AKIA…` or `ASIA…`).";
635
+ }
636
+ }
637
+ return null;
638
+ }
639
+ case "vertex":
640
+ if (config.projectId.length < 4 || /\s/.test(config.projectId)) {
641
+ return "GCP project id looks malformed.";
642
+ }
643
+ if (config.region.length < 2 || /\s/.test(config.region)) {
644
+ return "Vertex region looks malformed (e.g. `us-east5`, `global`, or `eu`).";
645
+ }
646
+ return null;
647
+ case "foundry": {
648
+ if (!config.resource && !config.baseUrl) {
649
+ return "Foundry config requires either a resource name or a base URL.";
650
+ }
651
+ if (config.resource && config.baseUrl) {
652
+ return "Foundry config: set EITHER `resource` OR `baseUrl`, not both.";
653
+ }
654
+ if (config.baseUrl) {
655
+ try {
656
+ const url = new URL(config.baseUrl);
657
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
658
+ return "Foundry base URL must be an http(s) URL.";
659
+ }
660
+ }
661
+ catch {
662
+ return "Foundry base URL is not a valid URL.";
663
+ }
664
+ }
665
+ return null;
666
+ }
667
+ case "gemini-vertex": {
668
+ if (config.projectId.length < 4 || /\s/.test(config.projectId)) {
669
+ return "GCP project id looks malformed.";
670
+ }
671
+ if (config.location.length < 2 || /\s/.test(config.location)) {
672
+ return "GCP location looks malformed (e.g. `us-central1`).";
673
+ }
674
+ if (config.authMode === "api_key"
675
+ && !isPlausibleGeminiApiKey(config.apiKey)) {
676
+ return "Vertex API key should start with `AIza…` (39 chars total).";
677
+ }
678
+ return null;
679
+ }
680
+ }
681
+ }
682
+ //# sourceMappingURL=backend-api-key-config.js.map