agentfootprint 9.7.0 → 9.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 (104) hide show
  1. package/dist/adapters/identity/vault.js +336 -0
  2. package/dist/adapters/identity/vault.js.map +1 -0
  3. package/dist/adapters/observability/audit.js +2 -28
  4. package/dist/adapters/observability/audit.js.map +1 -1
  5. package/dist/adapters/observability/file.js +307 -0
  6. package/dist/adapters/observability/file.js.map +1 -0
  7. package/dist/adapters/observability/githubBugReporter.js +461 -0
  8. package/dist/adapters/observability/githubBugReporter.js.map +1 -0
  9. package/dist/adapters/observability/githubDeviceSignIn.js +263 -0
  10. package/dist/adapters/observability/githubDeviceSignIn.js.map +1 -0
  11. package/dist/esm/adapters/identity/vault.d.ts +146 -0
  12. package/dist/esm/adapters/identity/vault.js +332 -0
  13. package/dist/esm/adapters/identity/vault.js.map +1 -0
  14. package/dist/esm/adapters/observability/audit.js +1 -27
  15. package/dist/esm/adapters/observability/audit.js.map +1 -1
  16. package/dist/esm/adapters/observability/file.d.ts +145 -0
  17. package/dist/esm/adapters/observability/file.js +303 -0
  18. package/dist/esm/adapters/observability/file.js.map +1 -0
  19. package/dist/esm/adapters/observability/githubBugReporter.d.ts +154 -0
  20. package/dist/esm/adapters/observability/githubBugReporter.js +457 -0
  21. package/dist/esm/adapters/observability/githubBugReporter.js.map +1 -0
  22. package/dist/esm/adapters/observability/githubDeviceSignIn.d.ts +131 -0
  23. package/dist/esm/adapters/observability/githubDeviceSignIn.js +259 -0
  24. package/dist/esm/adapters/observability/githubDeviceSignIn.js.map +1 -0
  25. package/dist/esm/identity.d.ts +1 -0
  26. package/dist/esm/identity.js +4 -0
  27. package/dist/esm/identity.js.map +1 -1
  28. package/dist/esm/lib/bug-report/build.d.ts +103 -0
  29. package/dist/esm/lib/bug-report/build.js +648 -0
  30. package/dist/esm/lib/bug-report/build.js.map +1 -0
  31. package/dist/esm/lib/bug-report/index.d.ts +14 -0
  32. package/dist/esm/lib/bug-report/index.js +13 -0
  33. package/dist/esm/lib/bug-report/index.js.map +1 -0
  34. package/dist/esm/lib/bug-report/transcript.d.ts +61 -0
  35. package/dist/esm/lib/bug-report/transcript.js +124 -0
  36. package/dist/esm/lib/bug-report/transcript.js.map +1 -0
  37. package/dist/esm/lib/bug-report/types.d.ts +206 -0
  38. package/dist/esm/lib/bug-report/types.js +12 -0
  39. package/dist/esm/lib/bug-report/types.js.map +1 -0
  40. package/dist/esm/lib/bug-report/zip.d.ts +71 -0
  41. package/dist/esm/lib/bug-report/zip.js +202 -0
  42. package/dist/esm/lib/bug-report/zip.js.map +1 -0
  43. package/dist/esm/lib/libraryVersion.d.ts +23 -0
  44. package/dist/esm/lib/libraryVersion.js +46 -0
  45. package/dist/esm/lib/libraryVersion.js.map +1 -0
  46. package/dist/esm/lib/trace-toolpack/openRecording.d.ts +17 -0
  47. package/dist/esm/lib/trace-toolpack/openRecording.js +8 -2
  48. package/dist/esm/lib/trace-toolpack/openRecording.js.map +1 -1
  49. package/dist/esm/observability-providers.d.ts +6 -1
  50. package/dist/esm/observability-providers.js +16 -1
  51. package/dist/esm/observability-providers.js.map +1 -1
  52. package/dist/esm/observe.d.ts +1 -0
  53. package/dist/esm/observe.js +6 -0
  54. package/dist/esm/observe.js.map +1 -1
  55. package/dist/identity.js +6 -1
  56. package/dist/identity.js.map +1 -1
  57. package/dist/lib/bug-report/build.js +656 -0
  58. package/dist/lib/bug-report/build.js.map +1 -0
  59. package/dist/lib/bug-report/index.js +18 -0
  60. package/dist/lib/bug-report/index.js.map +1 -0
  61. package/dist/lib/bug-report/transcript.js +128 -0
  62. package/dist/lib/bug-report/transcript.js.map +1 -0
  63. package/dist/lib/bug-report/types.js +13 -0
  64. package/dist/lib/bug-report/types.js.map +1 -0
  65. package/dist/lib/bug-report/zip.js +207 -0
  66. package/dist/lib/bug-report/zip.js.map +1 -0
  67. package/dist/lib/libraryVersion.js +51 -0
  68. package/dist/lib/libraryVersion.js.map +1 -0
  69. package/dist/lib/trace-toolpack/openRecording.js +9 -2
  70. package/dist/lib/trace-toolpack/openRecording.js.map +1 -1
  71. package/dist/observability-providers.js +20 -2
  72. package/dist/observability-providers.js.map +1 -1
  73. package/dist/observe.js +14 -6
  74. package/dist/observe.js.map +1 -1
  75. package/dist/types/adapters/identity/vault.d.ts +147 -0
  76. package/dist/types/adapters/identity/vault.d.ts.map +1 -0
  77. package/dist/types/adapters/observability/audit.d.ts.map +1 -1
  78. package/dist/types/adapters/observability/file.d.ts +146 -0
  79. package/dist/types/adapters/observability/file.d.ts.map +1 -0
  80. package/dist/types/adapters/observability/githubBugReporter.d.ts +155 -0
  81. package/dist/types/adapters/observability/githubBugReporter.d.ts.map +1 -0
  82. package/dist/types/adapters/observability/githubDeviceSignIn.d.ts +132 -0
  83. package/dist/types/adapters/observability/githubDeviceSignIn.d.ts.map +1 -0
  84. package/dist/types/identity.d.ts +1 -0
  85. package/dist/types/identity.d.ts.map +1 -1
  86. package/dist/types/lib/bug-report/build.d.ts +104 -0
  87. package/dist/types/lib/bug-report/build.d.ts.map +1 -0
  88. package/dist/types/lib/bug-report/index.d.ts +15 -0
  89. package/dist/types/lib/bug-report/index.d.ts.map +1 -0
  90. package/dist/types/lib/bug-report/transcript.d.ts +62 -0
  91. package/dist/types/lib/bug-report/transcript.d.ts.map +1 -0
  92. package/dist/types/lib/bug-report/types.d.ts +207 -0
  93. package/dist/types/lib/bug-report/types.d.ts.map +1 -0
  94. package/dist/types/lib/bug-report/zip.d.ts +72 -0
  95. package/dist/types/lib/bug-report/zip.d.ts.map +1 -0
  96. package/dist/types/lib/libraryVersion.d.ts +24 -0
  97. package/dist/types/lib/libraryVersion.d.ts.map +1 -0
  98. package/dist/types/lib/trace-toolpack/openRecording.d.ts +17 -0
  99. package/dist/types/lib/trace-toolpack/openRecording.d.ts.map +1 -1
  100. package/dist/types/observability-providers.d.ts +6 -1
  101. package/dist/types/observability-providers.d.ts.map +1 -1
  102. package/dist/types/observe.d.ts +1 -0
  103. package/dist/types/observe.d.ts.map +1 -1
  104. package/package.json +1 -1
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ /**
3
+ * githubDeviceSignIn — let the REPORTER sign in, so the issue is filed as them.
4
+ *
5
+ * import { githubDeviceSignIn } from 'agentfootprint/observe';
6
+ *
7
+ * const signIn = await githubDeviceSignIn({ clientId: 'Iv1.0123456789abcdef' });
8
+ * show(`Go to ${signIn.verificationUri} and enter ${signIn.userCode}`);
9
+ * const { token, login } = await signIn.completed; // resolves on authorize
10
+ *
11
+ * GitHub's OAuth **device flow**, spoken as three plain `fetch` calls and
12
+ * nothing else: request a code, show it to the human, poll until they approve.
13
+ * Zero dependencies, and it runs in a browser as well as on a server — there is
14
+ * no client secret in this flow, which is exactly why it is the one that works
15
+ * from a page.
16
+ *
17
+ * ## Why this exists beside a server token
18
+ *
19
+ * A server-side PAT files every report as the application. That is right for an
20
+ * automated "Report a problem" button: the app owns the repo, the app owns the
21
+ * token. It is wrong when the value is ATTRIBUTION — a field tester filing
22
+ * upstream should appear as themselves, so a maintainer can ask them a
23
+ * follow-up question and so their report counts as theirs.
24
+ *
25
+ * | | server PAT | device sign-in |
26
+ * |---|---|---|
27
+ * | Who the issue is from | the application | the reporter |
28
+ * | Where the token lives | server environment | the reporter's session, in memory |
29
+ * | Human steps | none | one: enter a code, approve |
30
+ * | Right for | in-app "report a problem" | field testers filing upstream |
31
+ *
32
+ * The token this returns is handed to {@link githubBugReporter} as `token`,
33
+ * with no special-casing anywhere: a token is a token.
34
+ *
35
+ * ## Keep it in memory, for the session only
36
+ *
37
+ * **Never `localStorage`, never a cookie, never a log line.** A device-flow
38
+ * token is a live credential for the account that approved it; persisting it in
39
+ * a browser turns one XSS into a lasting account compromise. Hold it in a
40
+ * variable, use it, drop it when the tab closes.
41
+ *
42
+ * ## Scopes are coarse here, and that is GitHub's design
43
+ *
44
+ * The device flow issues a CLASSIC OAuth token, and classic scopes are coarse:
45
+ * `public_repo` (the default here) grants write across every public repository
46
+ * the account can reach, and `repo` grants it across private ones too. There is
47
+ * no fine-grained equivalent in this flow. That trade buys attribution — the
48
+ * issue is really from that person — and it is the reason the default stops at
49
+ * `public_repo`: filing an issue needs no more, and the token disappears with
50
+ * the session. Where least privilege matters more than attribution, use a
51
+ * fine-grained PAT on a server instead (see {@link githubBugReporter}).
52
+ *
53
+ * ## The collaborator caveat, stated rather than discovered
54
+ *
55
+ * A reporter who signs in as themselves can file an issue on a public repo, and
56
+ * can commit evidence ONLY to a repository they can write to. Pointing
57
+ * `evidenceRepo` at a private repo the reporter is not a collaborator on will
58
+ * fail with a 404 (GitHub hides private repos from tokens that cannot see
59
+ * them). Either add the reporter as a collaborator, or let them attach the zip
60
+ * to the issue by hand — `exportBugReport` gives them the file either way.
61
+ *
62
+ * ## Secrecy
63
+ *
64
+ * The token appears in no message this module can produce. The three failure
65
+ * shapes of the flow — the human denied it, the code expired, the poll was
66
+ * aborted — are named plainly, with GitHub's `error_description` only, never a
67
+ * response body or a request.
68
+ */
69
+ Object.defineProperty(exports, "__esModule", { value: true });
70
+ exports.githubDeviceSignIn = void 0;
71
+ /** The endpoints, so a GitHub Enterprise Server deployment can move them. */
72
+ const DEFAULT_AUTH_BASE = 'https://github.com';
73
+ const DEFAULT_API_BASE = 'https://api.github.com';
74
+ const defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
75
+ if (signal?.aborted) {
76
+ reject(abortedError());
77
+ return;
78
+ }
79
+ const timer = setTimeout(() => {
80
+ signal?.removeEventListener('abort', onAbort);
81
+ resolve();
82
+ }, ms);
83
+ const onAbort = () => {
84
+ clearTimeout(timer);
85
+ reject(abortedError());
86
+ };
87
+ signal?.addEventListener('abort', onAbort, { once: true });
88
+ });
89
+ const abortedError = () => new Error('githubDeviceSignIn: sign-in was cancelled before the code was approved.');
90
+ /**
91
+ * Start a device-flow sign-in.
92
+ *
93
+ * Resolves as soon as GitHub hands back a code — that is the point, because
94
+ * the human cannot approve a code they have not been shown. The waiting happens
95
+ * on the returned `completed` promise.
96
+ *
97
+ * @throws TypeError when `clientId` is missing (naming where it comes from).
98
+ * @throws Error naming the status when GitHub refuses to issue a code.
99
+ */
100
+ async function githubDeviceSignIn(options) {
101
+ if (typeof options?.clientId !== 'string' || options.clientId.trim() === '') {
102
+ throw new TypeError('githubDeviceSignIn: `clientId` is required. Create an OAuth App once under your ' +
103
+ 'organisation (Settings → Developer settings → OAuth Apps), tick "Enable Device ' +
104
+ 'Flow", and pass its Client ID here. It is public by design — the device flow has no ' +
105
+ 'client secret, which is why it can run in a browser.');
106
+ }
107
+ const authBase = (options.authBase ?? DEFAULT_AUTH_BASE).replace(/\/+$/, '');
108
+ const apiBase = (options.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, '');
109
+ const doFetch = options._fetch ?? ((...args) => fetch(...args));
110
+ const sleep = options._sleep ?? defaultSleep;
111
+ const scopes = options.scopes ?? ['public_repo'];
112
+ const start = await postForm({
113
+ doFetch,
114
+ url: `${authBase}/login/device/code`,
115
+ what: 'request a device code',
116
+ form: { client_id: options.clientId, scope: scopes.join(' ') },
117
+ });
118
+ const deviceCode = stringField(start, 'device_code');
119
+ const userCode = stringField(start, 'user_code');
120
+ const verificationUri = stringField(start, 'verification_uri');
121
+ if (!deviceCode || !userCode || !verificationUri) {
122
+ throw new Error(`githubDeviceSignIn: GitHub did not return a device code${errorSuffix(start)}. Check that ` +
123
+ 'the OAuth App exists and has Device Flow enabled.');
124
+ }
125
+ const expiresIn = numberField(start, 'expires_in') ?? 900;
126
+ const interval = numberField(start, 'interval') ?? 5;
127
+ const completed = pollForToken({
128
+ doFetch,
129
+ sleep,
130
+ authBase,
131
+ apiBase,
132
+ clientId: options.clientId,
133
+ deviceCode,
134
+ interval,
135
+ expiresIn,
136
+ ...(options.signal !== undefined && { signal: options.signal }),
137
+ });
138
+ // Nobody has to await `completed` for polling to run; make sure an unawaited
139
+ // rejection is not an unhandled one in the meantime.
140
+ completed.catch(() => undefined);
141
+ return { userCode, verificationUri, expiresIn, interval, completed };
142
+ }
143
+ exports.githubDeviceSignIn = githubDeviceSignIn;
144
+ async function pollForToken(args) {
145
+ let waitSeconds = args.interval;
146
+ const deadline = Date.now() + args.expiresIn * 1000;
147
+ for (;;) {
148
+ if (args.signal?.aborted)
149
+ throw abortedError();
150
+ await args.sleep(waitSeconds * 1000, args.signal);
151
+ if (Date.now() > deadline) {
152
+ throw new Error(`githubDeviceSignIn: the code expired after ${args.expiresIn}s without being ` +
153
+ 'approved. Start the sign-in again to get a fresh one.');
154
+ }
155
+ const body = await postForm({
156
+ doFetch: args.doFetch,
157
+ url: `${args.authBase}/login/oauth/access_token`,
158
+ what: 'exchange the device code',
159
+ form: {
160
+ client_id: args.clientId,
161
+ device_code: args.deviceCode,
162
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
163
+ },
164
+ });
165
+ const token = stringField(body, 'access_token');
166
+ if (token) {
167
+ const scope = stringField(body, 'scope') ?? '';
168
+ const login = await readLogin(args.doFetch, args.apiBase, token);
169
+ return {
170
+ token,
171
+ tokenType: stringField(body, 'token_type') ?? 'bearer',
172
+ scopes: scope ? scope.split(/[\s,]+/).filter(Boolean) : [],
173
+ ...(login !== undefined && { login }),
174
+ };
175
+ }
176
+ const error = stringField(body, 'error');
177
+ switch (error) {
178
+ case 'authorization_pending':
179
+ // The human has not finished yet. This is the normal answer.
180
+ break;
181
+ case 'slow_down':
182
+ // GitHub's own back-pressure: it tells us the new interval, and polling
183
+ // faster than it asked gets the flow rate-limited out entirely.
184
+ waitSeconds = numberField(body, 'interval') ?? waitSeconds + 5;
185
+ break;
186
+ case 'expired_token':
187
+ throw new Error('githubDeviceSignIn: the code expired before it was approved. Start the sign-in ' +
188
+ 'again to get a fresh one.');
189
+ case 'access_denied':
190
+ throw new Error('githubDeviceSignIn: the sign-in was denied on GitHub, so no token was issued. ' +
191
+ 'Nothing has been filed.');
192
+ default:
193
+ throw new Error(`githubDeviceSignIn: GitHub refused the device code exchange${errorSuffix(body)}.`);
194
+ }
195
+ }
196
+ }
197
+ /** Attribution: which login will the issue read as? Never fatal — a token that
198
+ * cannot read `/user` can still file an issue. */
199
+ async function readLogin(doFetch, apiBase, token) {
200
+ try {
201
+ const res = await doFetch(`${apiBase}/user`, {
202
+ headers: {
203
+ authorization: `Bearer ${token}`,
204
+ accept: 'application/vnd.github+json',
205
+ 'x-github-api-version': '2022-11-28',
206
+ },
207
+ });
208
+ if (!res.ok)
209
+ return undefined;
210
+ const body = (await res.json());
211
+ return typeof body?.login === 'string' ? body.login : undefined;
212
+ }
213
+ catch {
214
+ return undefined;
215
+ }
216
+ }
217
+ /**
218
+ * One `application/x-www-form-urlencoded` POST that asks for JSON back.
219
+ *
220
+ * The device flow's endpoints answer form-encoded by default; `accept: json` is
221
+ * what makes them speak JSON. A thrown fetch is re-wrapped so no implementation
222
+ * can put the request — which carries the device code, and later the token — in
223
+ * the message.
224
+ */
225
+ async function postForm(args) {
226
+ let res;
227
+ try {
228
+ res = await args.doFetch(args.url, {
229
+ method: 'POST',
230
+ headers: { accept: 'application/json', 'content-type': 'application/x-www-form-urlencoded' },
231
+ body: new URLSearchParams(args.form).toString(),
232
+ });
233
+ }
234
+ catch (err) {
235
+ throw new Error(`githubDeviceSignIn: could not reach GitHub to ${args.what} ` +
236
+ `(${err instanceof Error ? err.name || 'network error' : 'network error'}).`);
237
+ }
238
+ let body;
239
+ try {
240
+ body = await res.json();
241
+ }
242
+ catch {
243
+ body = undefined;
244
+ }
245
+ const record = typeof body === 'object' && body !== null ? body : {};
246
+ // A 4xx/5xx with no parsed error field is all we may say: the response body
247
+ // of this endpoint can echo the request.
248
+ if (!res.ok && typeof record.error !== 'string') {
249
+ throw new Error(`githubDeviceSignIn: GitHub answered ${res.status} when asked to ${args.what}.`);
250
+ }
251
+ return record;
252
+ }
253
+ const stringField = (body, key) => typeof body[key] === 'string' && body[key] !== '' ? body[key] : undefined;
254
+ const numberField = (body, key) => typeof body[key] === 'number' ? body[key] : undefined;
255
+ /** GitHub's `error` / `error_description` — the only response text we echo. */
256
+ function errorSuffix(body) {
257
+ const code = stringField(body, 'error');
258
+ const description = stringField(body, 'error_description');
259
+ if (!code && !description)
260
+ return '';
261
+ return ` (${[code, description].filter(Boolean).join(': ').slice(0, 200)})`;
262
+ }
263
+ //# sourceMappingURL=githubDeviceSignIn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"githubDeviceSignIn.js","sourceRoot":"","sources":["../../../src/adapters/observability/githubDeviceSignIn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * vaultCredentials — a {@link CredentialProvider} over a HashiCorp-Vault-compatible
3
+ * KV v2 secret store, spoken as plain HTTP.
4
+ *
5
+ * import { vaultCredentials } from 'agentfootprint/security';
6
+ *
7
+ * const credentials = vaultCredentials({
8
+ * address: 'https://vault.internal:8200', // https, or say `allowHttp` out loud
9
+ * mount: 'secret', // KV v2 mount, default 'secret'
10
+ * paths: { github: 'ci/github' }, // service → path INSIDE the mount
11
+ * }); // token: VAULT_TOKEN, or `token`
12
+ *
13
+ * Zero dependencies and no SDK: one `GET` per resolution through the runtime's
14
+ * own `fetch`. Vault's HTTP API is small, stable and the thing every
15
+ * Vault-compatible store (OpenBao, and the Vault-API modes of several managed
16
+ * stores) implements — so the adapter that speaks HTTP works against more
17
+ * backends than the adapter that imports one vendor's client.
18
+ *
19
+ * ## V1 is deliberately one shape, and says so by name
20
+ *
21
+ * | Axis | V1 | Anything else |
22
+ * |---|---|---|
23
+ * | Auth | a **token** (`token` option, else `VAULT_TOKEN`) | AppRole / Kubernetes / JWT / AWS IAM login are **refused by name**, naming the option that would carry them |
24
+ * | Secret engine | **KV v2** (`<mount>/data/<path>`, the `data.data` envelope) | a KV v1 mount is refused by name once the response shape gives it away |
25
+ * | Leases / renewal | **none** — every `getCredential` re-reads the secret | a lease-aware provider is a different object, and the library's model since 9.7.0 is re-resolve-per-call |
26
+ *
27
+ * That is not modesty, it is the honest edge: an auth method the author cannot
28
+ * exercise against a real cluster would be a guess wearing an adapter's clothes.
29
+ * Each refusal names the option it would arrive on, so "tell us your auth shape"
30
+ * is a field report rather than an issue title.
31
+ *
32
+ * ## Field → credential kind
33
+ *
34
+ * A KV v2 read returns `{ data: { data: { …your fields… }, metadata: {…} } }`.
35
+ * The inner object is mapped to a {@link Credential} by the FIRST rule that
36
+ * matches, so a secret written the ordinary way needs no configuration:
37
+ *
38
+ * | Fields present | Becomes | Header it applies |
39
+ * |---|---|---|
40
+ * | `token` | `bearer(token)` | `authorization: Bearer …` |
41
+ * | `api_key` \| `apiKey` \| `key` | `apiKey(value, header ?? 'x-api-key')` | that header |
42
+ * | `username` + `password` | `basic(username, password)` | `authorization: Basic …` |
43
+ * | `headers` (an object of strings) | `headers(map)` | all of them |
44
+ *
45
+ * A secret matching none of them is refused — naming the PATH and the four
46
+ * shapes, never the secret. `toCredential` is the seam for a shop whose fields
47
+ * are named otherwise; it sees the secret and returns a `Credential`, and
48
+ * returning `undefined` falls back to the table above.
49
+ *
50
+ * ## Secrecy (the 8.6.0 two-clause law, applied here)
51
+ *
52
+ * A thrown message reaches the model as a tool result AND rides
53
+ * `agentfootprint.credential.failed`. So every error this adapter raises names
54
+ * **the service, the path and the HTTP status, and nothing from the response
55
+ * body or the token**. Nothing here logs, and no secret value, no `X-Vault-Token`
56
+ * header and no field name from the payload appears in any message it can throw
57
+ * — pinned by a grep-shaped test over every failure path. The credential it
58
+ * returns hides its own secret fields (non-enumerable) and carries `toHeaders`,
59
+ * so `structuredClone` rejects it and it cannot enter tracked scope by accident.
60
+ *
61
+ * @example Dev → prod is the same two lines
62
+ * ```ts
63
+ * // dev
64
+ * const credentials = staticTokens({ github: 'ghp_dev_xxx' });
65
+ * // prod — the tool code does not change
66
+ * const credentials = vaultCredentials({ address: process.env.VAULT_ADDR! });
67
+ * Agent.create({ provider, model, credentials }).build();
68
+ * ```
69
+ */
70
+ import type { Credential, CredentialProvider } from '../../identity/types.js';
71
+ /** The base options every form shares. */
72
+ interface VaultCredentialsBase {
73
+ /** Vault's base URL, e.g. `https://vault.internal:8200` — **required**, and
74
+ * **https** unless {@link VaultCredentialsBase.allowHttp} says otherwise. No
75
+ * `VAULT_ADDR` fallback: an agent that silently picks up an address from the
76
+ * environment is an agent that reads a different vault when the environment
77
+ * changes under it. Name it. */
78
+ readonly address: string;
79
+ /** The Vault token. Falls back to `VAULT_TOKEN` (the variable every Vault
80
+ * tool already sets). This is a secret: it is sent as `X-Vault-Token` and
81
+ * appears in no message this adapter can throw. */
82
+ readonly token?: string;
83
+ /** Auth method. **`'token'` is the only one V1 implements.** Anything else is
84
+ * refused at construction, by name, with what it would take — see
85
+ * {@link vaultCredentials}. */
86
+ readonly auth?: 'token';
87
+ /** KV v2 mount point. Default `'secret'` (Vault's own default for the KV v2
88
+ * engine). The read URL is `<address>/v1/<mount>/data/<path>`. */
89
+ readonly mount?: string;
90
+ /** Vault Enterprise / HCP namespace, sent as `X-Vault-Namespace`. Omit for
91
+ * open-source Vault and OpenBao, which have no namespaces. */
92
+ readonly namespace?: string;
93
+ /** Map the secret's fields to a {@link Credential} yourself. Returns
94
+ * `undefined` to fall back to the built-in table (`token` / `api_key` /
95
+ * `username`+`password` / `headers`). The seam for a shop whose field names
96
+ * are its own — and the reason this adapter does not need an option per
97
+ * spelling. **Never log or return the fields from here**; they are the
98
+ * secret. */
99
+ readonly toCredential?: (secret: Readonly<Record<string, unknown>>, service: string) => Credential | undefined;
100
+ /** Header name for the `api_key` shape when the secret does not carry its own
101
+ * `header` field. Default `'x-api-key'`. */
102
+ readonly apiKeyHeader?: string;
103
+ /** Request timeout in ms. Default 5000 — a credential resolution sits in
104
+ * front of a tool call, so a hung vault must fail rather than hang a run. */
105
+ readonly timeoutMs?: number;
106
+ /** Allow a plain-`http://` address. **Refused unless you set this**, because
107
+ * the Vault token travels in a request header: over plaintext HTTP, anyone
108
+ * on the path reads a token that can usually read every secret it can reach.
109
+ * Set it only for a loopback dev server (`http://127.0.0.1:8200`). */
110
+ readonly allowHttp?: boolean;
111
+ /** Stable provider id (default `'vault'`). Shows up in "which provider vended
112
+ * this". */
113
+ readonly id?: string;
114
+ /** Test seam — inject `fetch`. Bypasses the network entirely. */
115
+ readonly _fetch?: typeof fetch;
116
+ }
117
+ /**
118
+ * How a `service` becomes a path inside the mount. Three arms, and they
119
+ * EXCLUDE each other — two spellings of one rule can disagree, so the type
120
+ * refuses the pair and so does the constructor.
121
+ */
122
+ type VaultPathMapping = {
123
+ /** `service → path inside the mount`, the {@link staticTokens} shape one
124
+ * level up: the same literal map, holding a path instead of a token. An
125
+ * unknown service is refused by name, listing the known ones. */
126
+ readonly paths: Readonly<Record<string, string>>;
127
+ readonly resolve?: never;
128
+ } | {
129
+ /** `service → path`, computed. Return `undefined` to refuse a service.
130
+ * For the convention-driven shop: ``(s) => `agents/${s}` ``. */
131
+ readonly resolve: (service: string) => string | undefined;
132
+ readonly paths?: never;
133
+ } | {
134
+ /** Neither: the **service id IS the path** under the mount, so
135
+ * `service: 'github'` reads `<mount>/data/github`. */
136
+ readonly paths?: undefined;
137
+ readonly resolve?: undefined;
138
+ };
139
+ export type VaultCredentialsOptions = VaultCredentialsBase & VaultPathMapping;
140
+ /**
141
+ * Build a {@link CredentialProvider} that reads KV v2 secrets from a
142
+ * Vault-compatible store. See {@link VaultCredentialsOptions} for the
143
+ * per-option contract and this module's docstring for the V1 boundary.
144
+ */
145
+ export declare function vaultCredentials(options: VaultCredentialsOptions): CredentialProvider;
146
+ export {};