@agentproto/auth 0.1.0-alpha.0 → 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.
package/README.md CHANGED
@@ -144,6 +144,91 @@ Swap this module for libsecret (Linux) / Credential Manager (Windows) to run
144
144
  elsewhere. `resolveAccount(account, server)` expands the `{server}` template in a
145
145
  `tokenStore.account` spec.
146
146
 
147
+ ## Credential store
148
+
149
+ Flow engines never touch a concrete backend — they depend on the
150
+ `CredentialStore` interface, so the same `pat` / `service-auth` flow runs
151
+ against the macOS Keychain in a CLI, an in-memory map in a test, or an
152
+ encrypted file on a headless host.
153
+
154
+ ```ts
155
+ interface CredentialStore {
156
+ read(ref: StoreRef): Promise<StoredCredential | undefined>
157
+ write(ref: StoreRef, cred: StoredCredential): Promise<void>
158
+ delete?(ref: StoreRef): Promise<void> // optional — backend permitting
159
+ }
160
+
161
+ interface StoreRef { path: string; account?: string } // where it lives
162
+ interface StoredCredential { // what's stored
163
+ value: string
164
+ kind: "pat" | "assertion" | "oat"
165
+ expiresAt?: string
166
+ metadata?: Record<string, unknown>
167
+ }
168
+ ```
169
+
170
+ `resolveStoreRef(tokenStore, server)` maps a provider's `tokenStore` spec
171
+ (expanding the `{server}` template) to a `StoreRef` — so a token **written**
172
+ under a ref resolves under the **same** ref on read.
173
+
174
+ | Backend | Use |
175
+ | --- | --- |
176
+ | `KeychainStore` | macOS `security` CLI — the interactive-CLI default |
177
+ | `MemoryStore` | ephemeral, for tests |
178
+ | `FileStore` | AES-256-GCM at rest; key from `AGENTPROTO_STORE_KEY`, **no plaintext fallback** — for headless hosts with no Keychain |
179
+
180
+ A host can implement the interface over its own vault (Guilde backs it with an
181
+ AAD-bound, per-user secrets service). The flow engine is none the wiser.
182
+
183
+ ## Credential broker — `resolveHeaders`
184
+
185
+ The broker is the "Agent.pw parity" surface: give it a **path**, get back
186
+ ready-to-use HTTP auth headers, with a *fresh* token resolved at call-time. It
187
+ turns a stored credential (or a full flow run) into
188
+ `{ Authorization: "Bearer …" }` without the caller knowing which.
189
+
190
+ ```ts
191
+ import { CredentialBroker } from "@agentproto/auth"
192
+
193
+ const broker = new CredentialBroker({ store, getProvider: getAuthProvider })
194
+
195
+ const headers = await broker.resolveHeaders({
196
+ path: "guilde", // "<providerId>" or "<providerId>/<account>"
197
+ server: "https://api.guilde.work", // optional; defaults to provider.apiBase
198
+ signal, // optional AbortSignal
199
+ })
200
+ // → { Authorization: "Bearer oat_…" }
201
+ ```
202
+
203
+ `resolveHeaders`:
204
+
205
+ 1. parses `path` → `providerId` (+ optional `account`) and resolves the
206
+ provider via `getProvider`; **throws** on an unknown provider;
207
+ 2. reads the store at the resolved ref — if a stored bearer credential is
208
+ still **fresh** (past a 60s skew), returns it directly;
209
+ 3. otherwise runs the provider's flow (`runAuthFlow`, threading the same
210
+ `store` + `signal`) — for `service-auth`, that's the assertion →
211
+ `jwt-bearer` exchange — and returns the minted token;
212
+ 4. throws if the flow yields no usable access token.
213
+
214
+ **Fresh-per-call, no long-lived header cache.** This is the headless/cron
215
+ payoff: a scheduled agent re-resolves a valid `Authorization` at connect-time
216
+ with no human re-auth, as long as a durable credential (a PAT, or a
217
+ `service-auth` assertion) sits in the store.
218
+
219
+ > **Runnable example:** `src/__tests__/broker.example.test.ts` is a
220
+ > zero-mock, copy-pasteable wire-up (provider → store → broker →
221
+ > `resolveHeaders`) you can lift verbatim.
222
+
223
+ ### Feeding an MCP transport
224
+
225
+ `@agentproto/secrets/exposure` declares a **structural** `McpHeaderResolver`
226
+ (`resolveHeaders({ path, server?, signal? })`) that `CredentialBroker`
227
+ satisfies with no adapter — so a connector can declare an `mcp-header`
228
+ exposure and have `resolveMcpHeaderExposure(exposure, broker)` lay brokered
229
+ headers onto the transport. `secrets` keeps **zero dependency** on `auth`
230
+ through that structural seam. See `@agentproto/secrets`' README.
231
+
147
232
  ## API surface
148
233
 
149
234
  | Export | Purpose |
@@ -154,6 +239,9 @@ elsewhere. `resolveAccount(account, server)` expands the `{server}` template in
154
239
  | `discoverEndpoints` / `DiscoveryError` | two-hop `.well-known` discovery |
155
240
  | `runAuthFlow` | resolve → discover → dispatch |
156
241
  | `FLOW_ENGINES` | registered flow engines (`pat`, `service-auth`) |
242
+ | `CredentialBroker` / `CredentialBrokerOptions` | path → fresh `resolveHeaders` |
243
+ | `CredentialStore` / `StoreRef` / `StoredCredential` | pluggable-store interface + shapes |
244
+ | `KeychainStore` / `MemoryStore` / `FileStore` / `resolveStoreRef` | built-in backends + ref resolver |
157
245
  | `readKeychainToken` / `writeKeychainToken` / `resolveAccount` | Keychain helpers |
158
246
  | `guildeAuthProvider` / `BUILTIN_AUTH_PROVIDERS` | shipped builtins |
159
247
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,44 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ /**
4
+ * CredentialStore — pluggable backend for persisting AIP-50 credentials.
5
+ *
6
+ * Flow engines read/write through this interface instead of hard-coding a
7
+ * specific backend. `StoreRef.path` is the storage slot key — the macOS
8
+ * Keychain "service" name today, a Map key for `MemoryStore`, a JSON object
9
+ * key for `FileStore`.
10
+ */
11
+ /** A persisted credential value plus the bookkeeping needed to use it again
12
+ * without re-running the auth flow. */
13
+ interface StoredCredential {
14
+ /** The raw credential string — a PAT, an identity assertion JWT, … */
15
+ value: string;
16
+ /** What `value` is. Mirrors `FlowResult.tokenKind`. */
17
+ kind: "pat" | "assertion" | "oat" | "daemon";
18
+ /** ISO 8601 expiry, when the credential has one. */
19
+ expiresAt?: string;
20
+ /** Backend- or flow-specific extras that don't fit `value`/`kind`/`expiresAt`. */
21
+ metadata?: Record<string, unknown>;
22
+ }
23
+ /** Where a credential lives, independent of backend. */
24
+ interface StoreRef {
25
+ /** Storage slot key — a Keychain service name, a file-store map key, … */
26
+ path: string;
27
+ /** Sub-slot within `path` (a Keychain account, …). Defaults to `path`
28
+ * itself when a backend needs *some* account and none was resolved. */
29
+ account?: string;
30
+ }
31
+ /** A pluggable credential backend. Flow engines depend on this interface,
32
+ * never on a concrete backend — see `KeychainStore`, `MemoryStore`, `FileStore`. */
33
+ interface CredentialStore {
34
+ /** Read the credential at `ref`, or `undefined` if none is stored. */
35
+ read(ref: StoreRef): Promise<StoredCredential | undefined>;
36
+ /** Write (or overwrite) the credential at `ref`. */
37
+ write(ref: StoreRef, cred: StoredCredential): Promise<void>;
38
+ /** Remove the credential at `ref`, if the backend supports deletion. */
39
+ delete?(ref: StoreRef): Promise<void>;
40
+ }
41
+
3
42
  /**
4
43
  * AIP-50 auth-provider doctype — types.
5
44
  *
@@ -14,14 +53,19 @@ import { z } from 'zod';
14
53
  *
15
54
  * `id-jag` is reserved for a future agentproto-as-IdP scenario.
16
55
  */
56
+
17
57
  /** Discriminated union of all supported auth flow ids. */
18
- type FlowId = "pat" | "service-auth";
19
- /** Where a credential is stored in the platform Keychain. */
58
+ type FlowId = "pat" | "service-auth" | "device-code";
59
+ /** Where a credential is stored, backend-agnostic. */
20
60
  interface TokenStoreSpec {
21
- /** macOS Keychain service name (or equivalent on other platforms). */
61
+ /** macOS Keychain service name (or equivalent on other platforms).
62
+ * Back-compat alias for `path` — used when `path` is omitted. */
22
63
  keychain: string;
23
- /** Keychain account. The literal `{server}` is substituted with the resolved
24
- * server URL. Defaults to the resolved server URL when omitted. */
64
+ /** Storage slot key, passed to `CredentialStore` as `StoreRef.path`.
65
+ * Defaults to `keychain` when omitted. */
66
+ path?: string;
67
+ /** Storage account/sub-slot. The literal `{server}` is substituted with the
68
+ * resolved server URL. Defaults to the resolved server URL when omitted. */
25
69
  account?: string;
26
70
  }
27
71
  /** PAT flow: read existing Keychain token or prompt for one interactively. */
@@ -44,7 +88,23 @@ interface ServiceAuthConfig {
44
88
  * and MUST NOT be persisted; the claim_token is held in memory only. */
45
89
  tokenStore: TokenStoreSpec;
46
90
  }
47
- type AuthConfig = PATAuthConfig | ServiceAuthConfig;
91
+ /** device-code flow: RFC 8628 device-authorization ceremony. Discovery
92
+ * (/.well-known/) determines the actual endpoints at runtime; the static
93
+ * fields here are used when discovery fails. Unlike service-auth, the stored
94
+ * credential IS the durable access token (pat-class persistence), with an
95
+ * optional refresh_token riding in `metadata`. */
96
+ interface DeviceCodeAuthConfig {
97
+ flow: "device-code";
98
+ /** OAuth client id sent to the device-authorization endpoint. Default: "agentproto-cli". */
99
+ clientId?: string;
100
+ /** Optional scope requested at the device-authorization endpoint. */
101
+ scope?: string;
102
+ /** Optional human-readable label for the device, shown to the approving user. */
103
+ deviceLabel?: string;
104
+ /** Keychain destination for the durable access token. */
105
+ tokenStore: TokenStoreSpec;
106
+ }
107
+ type AuthConfig = PATAuthConfig | ServiceAuthConfig | DeviceCodeAuthConfig;
48
108
  /** AIP-19 companion: the provision endpoints on the server side. */
49
109
  interface InstallConfig {
50
110
  /** URL path for the seal-key endpoint (e.g. "/api/v1/connectors/seal-key"). */
@@ -64,6 +124,13 @@ interface AuthProviderDefinition {
64
124
  auth: AuthConfig;
65
125
  /** Optional AIP-19 provision target. */
66
126
  install?: InstallConfig;
127
+ /** Optional audience this provider's credentials are scoped to (recommended
128
+ * values: "tunnel" | "api" | "mcp"; free-form strings are allowed). Folded
129
+ * into the store key so credentials for different audiences don't collide.
130
+ * This is defense-in-depth only — it is NOT a security boundary; the
131
+ * normative isolation is server-side (grant-type dispatch + scope/`aud`
132
+ * validation). Absent `audience` = today's behavior, unchanged. */
133
+ audience?: string;
67
134
  }
68
135
  type AuthProviderHandle = Readonly<AuthProviderDefinition>;
69
136
  /** Resolved endpoint set from the two-hop auth.md discovery chain. */
@@ -78,10 +145,14 @@ interface DiscoveredEndpoints {
78
145
  tokenEndpoint: string;
79
146
  /** Full revocation endpoint URL, if present. */
80
147
  revocationEndpoint?: string;
81
- /** Full identity endpoint URL (POST /agent/identity). */
82
- identityEndpoint: string;
148
+ /** Full identity endpoint URL (POST /agent/identity). Absent when discovery
149
+ * resolved via the agentproto-host.json fallback, which doesn't carry
150
+ * auth.md identity metadata. */
151
+ identityEndpoint?: string;
83
152
  /** Full claim endpoint URL (POST /agent/identity/claim), if present. */
84
153
  claimEndpoint?: string;
154
+ /** Full device-authorization endpoint URL (RFC 8628), if present. */
155
+ deviceAuthorizationEndpoint?: string;
85
156
  /** Supported identity types from agent_auth.identity_types_supported. */
86
157
  identityTypesSupported: string[];
87
158
  /** Supported grant types from AS metadata. */
@@ -99,12 +170,23 @@ interface FlowResult {
99
170
  /** ISO 8601 expiry of the identity assertion. */
100
171
  assertionExpires?: string;
101
172
  /** Access token to use for this invocation — `pat` returns the stored/typed
102
- * key; `service-auth` returns the freshly minted/refreshed `oat_*`. */
173
+ * key; `service-auth` returns the freshly minted/refreshed `oat_*`;
174
+ * `device-code` returns the durable `gdt_*`. */
103
175
  accessToken?: string;
104
176
  /** What `accessToken` is: `pat` (personal access key), `oat` (service-auth
105
- * access token). `assertion` is reserved for a future flow that returns a
106
- * bare assertion without an access token. */
107
- tokenKind: "pat" | "assertion" | "oat";
177
+ * access token), `daemon` (device-code access token). `assertion` is
178
+ * reserved for a future flow that returns a bare assertion without an
179
+ * access token. */
180
+ tokenKind: "pat" | "assertion" | "oat" | "daemon";
181
+ /** device-code flow: refresh token, when the AS issued one. */
182
+ refreshToken?: string;
183
+ /** device-code flow: granted scope, when the AS reports one. */
184
+ scope?: string;
185
+ /** device-code flow: subject identifier, when the AS reports one. */
186
+ subject?: string;
187
+ /** device-code flow: an id usable to revoke this credential later, when the
188
+ * AS issues one. */
189
+ revocationId?: string;
108
190
  }
109
191
  interface FlowRunOptions {
110
192
  /** Resolved server URL for this invocation. May differ from provider.apiBase
@@ -113,6 +195,23 @@ interface FlowRunOptions {
113
195
  /** Force re-authentication even if a stored credential is found. */
114
196
  force?: boolean;
115
197
  signal?: AbortSignal;
198
+ /** Credential backend to read/write through. Defaults to a `KeychainStore`
199
+ * when omitted — existing callers keep today's Keychain-only behavior. */
200
+ store?: CredentialStore;
201
+ /** Whether a device/claim ceremony should best-effort open the verification
202
+ * URL in a browser. Default `true` (omitted = open) — existing callers are
203
+ * unaffected. Set `false` for headless/remote sessions; the verification
204
+ * URL and user code are still printed to stderr so the ceremony can be
205
+ * completed manually. */
206
+ openBrowser?: boolean;
207
+ /** device-code flow only: attempt only the cached/refresh path (fresh
208
+ * credential, or `grant_type=refresh_token` on an expired one) and NEVER
209
+ * fall through to an interactive device-authorization ceremony. Throws
210
+ * `CeremonyRequiredError` when no fresh or refreshable credential is
211
+ * available. For callers (e.g. a headless `serve` boot) that must not
212
+ * block on user interaction. Default `false` (omitted = ceremony allowed,
213
+ * today's behavior). Ignored by other flows. */
214
+ refreshOnly?: boolean;
116
215
  }
117
216
  /** A flow engine implements one auth protocol. Dispatch by provider.auth.flow —
118
217
  * no if/switch chains at call sites. */
@@ -142,12 +241,14 @@ declare const defineAuthProvider: (def: AuthProviderDefinition) => Readonly<Auth
142
241
 
143
242
  declare const tokenStoreSpecSchema: z.ZodObject<{
144
243
  keychain: z.ZodString;
244
+ path: z.ZodOptional<z.ZodString>;
145
245
  account: z.ZodOptional<z.ZodString>;
146
246
  }, z.core.$strict>;
147
247
  declare const authConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
148
248
  flow: z.ZodLiteral<"pat">;
149
249
  tokenStore: z.ZodObject<{
150
250
  keychain: z.ZodString;
251
+ path: z.ZodOptional<z.ZodString>;
151
252
  account: z.ZodOptional<z.ZodString>;
152
253
  }, z.core.$strict>;
153
254
  }, z.core.$strict>, z.ZodObject<{
@@ -156,6 +257,17 @@ declare const authConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
156
257
  loginHint: z.ZodOptional<z.ZodString>;
157
258
  tokenStore: z.ZodObject<{
158
259
  keychain: z.ZodString;
260
+ path: z.ZodOptional<z.ZodString>;
261
+ account: z.ZodOptional<z.ZodString>;
262
+ }, z.core.$strict>;
263
+ }, z.core.$strict>, z.ZodObject<{
264
+ flow: z.ZodLiteral<"device-code">;
265
+ clientId: z.ZodOptional<z.ZodString>;
266
+ scope: z.ZodOptional<z.ZodString>;
267
+ deviceLabel: z.ZodOptional<z.ZodString>;
268
+ tokenStore: z.ZodObject<{
269
+ keychain: z.ZodString;
270
+ path: z.ZodOptional<z.ZodString>;
159
271
  account: z.ZodOptional<z.ZodString>;
160
272
  }, z.core.$strict>;
161
273
  }, z.core.$strict>], "flow">;
@@ -171,6 +283,7 @@ declare const authProviderFrontmatterSchema: z.ZodObject<{
171
283
  flow: z.ZodLiteral<"pat">;
172
284
  tokenStore: z.ZodObject<{
173
285
  keychain: z.ZodString;
286
+ path: z.ZodOptional<z.ZodString>;
174
287
  account: z.ZodOptional<z.ZodString>;
175
288
  }, z.core.$strict>;
176
289
  }, z.core.$strict>, z.ZodObject<{
@@ -179,6 +292,17 @@ declare const authProviderFrontmatterSchema: z.ZodObject<{
179
292
  loginHint: z.ZodOptional<z.ZodString>;
180
293
  tokenStore: z.ZodObject<{
181
294
  keychain: z.ZodString;
295
+ path: z.ZodOptional<z.ZodString>;
296
+ account: z.ZodOptional<z.ZodString>;
297
+ }, z.core.$strict>;
298
+ }, z.core.$strict>, z.ZodObject<{
299
+ flow: z.ZodLiteral<"device-code">;
300
+ clientId: z.ZodOptional<z.ZodString>;
301
+ scope: z.ZodOptional<z.ZodString>;
302
+ deviceLabel: z.ZodOptional<z.ZodString>;
303
+ tokenStore: z.ZodObject<{
304
+ keychain: z.ZodString;
305
+ path: z.ZodOptional<z.ZodString>;
182
306
  account: z.ZodOptional<z.ZodString>;
183
307
  }, z.core.$strict>;
184
308
  }, z.core.$strict>], "flow">;
@@ -186,6 +310,7 @@ declare const authProviderFrontmatterSchema: z.ZodObject<{
186
310
  sealKey: z.ZodString;
187
311
  secretBacked: z.ZodString;
188
312
  }, z.core.$strict>>;
313
+ audience: z.ZodOptional<z.ZodString>;
189
314
  }, z.core.$strict>;
190
315
  type AuthProviderFrontmatter = z.infer<typeof authProviderFrontmatterSchema>;
191
316
 
@@ -221,12 +346,18 @@ declare function listAuthProviders(): AuthProviderHandle[];
221
346
  declare function listAuthProviderIds(): string[];
222
347
 
223
348
  /**
224
- * Two-hop discovery per the auth.md standard (AIP-50 §Discovery algorithm).
349
+ * Two-hop discovery per the auth.md standard (AIP-50 §Discovery algorithm),
350
+ * with a second strategy for hosts that don't speak auth.md.
225
351
  *
226
- * Hop 1: GET {apiBase}/.well-known/oauth-protected-resource (PRM, RFC 8414)
227
- * extract authorization_servers[0] as authServerBase
228
- * Hop 2: GET {authServerBase}/.well-known/oauth-authorization-server (AS metadata)
229
- * extract token_endpoint + agent_auth block
352
+ * Strategy 1 (OAuth PRM chain):
353
+ * Hop 1: GET {apiBase}/.well-known/oauth-protected-resource (PRM, RFC 8414)
354
+ * extract authorization_servers[0] as authServerBase
355
+ * Hop 2: GET {authServerBase}/.well-known/oauth-authorization-server (AS metadata)
356
+ * → extract token_endpoint + agent_auth block
357
+ *
358
+ * Strategy 2 (agentproto-host.json), tried when strategy 1 fails:
359
+ * GET {apiBase}/.well-known/agentproto-host.json
360
+ * → extract device_authorization_endpoint + token_endpoint
230
361
  *
231
362
  * Callers MUST catch DiscoveryError and fall back to static manifest config —
232
363
  * discovery failures are common (server predates auth.md) and MUST NOT prevent
@@ -271,6 +402,34 @@ declare function runAuthFlow(provider: AuthProviderHandle, opts: RunFlowOptions)
271
402
 
272
403
  declare const FLOW_ENGINES: Readonly<Record<string, FlowEngine>>;
273
404
 
405
+ /**
406
+ * device-code flow engine — plain RFC 8628 device-authorization ceremony.
407
+ *
408
+ * Protocol:
409
+ * POST {device_authorization_endpoint} { client_id, scope?, device_label? }
410
+ * → device_code + user_code + verification_uri(_complete) + expires_in
411
+ * User approves at verification_uri in browser.
412
+ * Poll POST {token_endpoint} with grant_type=urn:ietf:params:oauth:grant-type:device_code
413
+ * until approved / denied / expired.
414
+ * On success: store the access_token as the durable credential (kind:"daemon")
415
+ * — unlike service-auth, the access token IS the thing that's persisted;
416
+ * refresh_token/scope/subject/revocation_id ride in `metadata`.
417
+ *
418
+ * Subsequent runs: a fresh stored credential (or one with no expiry) short-
419
+ * circuits the ceremony. An expired credential with a stored refresh_token is
420
+ * exchanged via `grant_type=refresh_token`; on failure (or when no
421
+ * refresh_token was stored) a new ceremony starts.
422
+ */
423
+
424
+ /** Thrown by the device-code engine when `opts.refreshOnly` is set and no
425
+ * fresh or refreshable credential is available — the only remaining path
426
+ * would be an interactive ceremony, which `refreshOnly` callers explicitly
427
+ * opt out of. Callers (e.g. `serve` boot on a headless host) catch this to
428
+ * fall back to their own non-interactive behavior instead of hanging. */
429
+ declare class CeremonyRequiredError extends Error {
430
+ constructor(message: string);
431
+ }
432
+
274
433
  /**
275
434
  * Keychain helpers — read/write a token in the platform Keychain.
276
435
  *
@@ -285,6 +444,102 @@ declare function readKeychainToken(service: string, account: string): Promise<st
285
444
  /** Write a token to the Keychain (-U updates in place). */
286
445
  declare function writeKeychainToken(service: string, account: string, token: string): Promise<void>;
287
446
 
447
+ /**
448
+ * KeychainStore — `CredentialStore` backed by the platform Keychain.
449
+ *
450
+ * Wraps the low-level `token-store.ts` helpers (macOS `security` CLI today).
451
+ * The Keychain only holds one opaque string per entry, so `kind`/`expiresAt`/
452
+ * `metadata` are packed into a small JSON envelope that IS that string.
453
+ *
454
+ * Back-compat: an entry written before this store existed (or by any other
455
+ * tool) is a bare token string, not an envelope. `read` falls back to
456
+ * treating the whole string as the value with kind `"pat"` — the only flow
457
+ * that pre-dates this store — rather than failing on non-JSON content.
458
+ */
459
+
460
+ declare class KeychainStore implements CredentialStore {
461
+ read(ref: StoreRef): Promise<StoredCredential | undefined>;
462
+ write(ref: StoreRef, cred: StoredCredential): Promise<void>;
463
+ }
464
+
465
+ /**
466
+ * MemoryStore — in-process `CredentialStore` backed by a `Map`.
467
+ *
468
+ * Ephemeral: gone when the process exits. Used for tests and short-lived
469
+ * tooling that shouldn't touch a real backend.
470
+ */
471
+
472
+ declare class MemoryStore implements CredentialStore {
473
+ private readonly entries;
474
+ read(ref: StoreRef): Promise<StoredCredential | undefined>;
475
+ write(ref: StoreRef, cred: StoredCredential): Promise<void>;
476
+ delete(ref: StoreRef): Promise<void>;
477
+ }
478
+
479
+ /**
480
+ * FileStore — encrypted-JSON-file `CredentialStore`.
481
+ *
482
+ * Every entry is sealed independently with AES-256-GCM under a key read from
483
+ * `AGENTPROTO_STORE_KEY` (hex or base64, 32 raw bytes). There is no plaintext
484
+ * fallback: a missing or malformed key throws rather than writing credentials
485
+ * to disk in the clear.
486
+ */
487
+
488
+ declare class FileStore implements CredentialStore {
489
+ private readonly filePath;
490
+ constructor(filePath: string);
491
+ read(ref: StoreRef): Promise<StoredCredential | undefined>;
492
+ write(ref: StoreRef, cred: StoredCredential): Promise<void>;
493
+ delete(ref: StoreRef): Promise<void>;
494
+ private readFile;
495
+ private writeFile;
496
+ }
497
+
498
+ /**
499
+ * Map a provider's `TokenStoreSpec` to a backend-agnostic `StoreRef`.
500
+ *
501
+ * Shared by every flow engine so the `path`/`keychain` back-compat alias and
502
+ * the `{server}` account template are resolved identically everywhere.
503
+ */
504
+
505
+ /** When `audience` is declared, it's folded into the physical store key —
506
+ * `${audience}:${path}` — so credentials scoped to different audiences
507
+ * (e.g. "tunnel" vs "api") never collide under the same slot. Absent
508
+ * `audience` = today's unprefixed path, unchanged. */
509
+ declare function resolveStoreRef(spec: TokenStoreSpec, server: string, audience?: string): StoreRef;
510
+
511
+ /**
512
+ * CredentialBroker — resolve ready-to-use auth headers for a `<providerId>`
513
+ * or `<providerId>/<account>` path, auto-refreshing via the flow engine when
514
+ * the stored credential is missing, expired, or not directly usable.
515
+ *
516
+ * Agent.pw-parity API: callers ask for headers by path and never touch
517
+ * tokens, flows, or the underlying store directly.
518
+ */
519
+
520
+ interface CredentialBrokerOptions {
521
+ /** Backend to read cached credentials from and persist refreshed ones to. */
522
+ store: CredentialStore;
523
+ /** Provider lookup — pass `getAuthProvider` from the registry, or a custom
524
+ * resolver (e.g. scoped to a single host's providers). */
525
+ getProvider: (id: string) => AuthProviderHandle | undefined;
526
+ }
527
+ declare class CredentialBroker {
528
+ private readonly store;
529
+ private readonly getProvider;
530
+ constructor(opts: CredentialBrokerOptions);
531
+ resolveHeaders(o: {
532
+ path: string;
533
+ server?: string;
534
+ signal?: AbortSignal;
535
+ /** Audience the caller expects this credential to be scoped to. Checked
536
+ * only when the resolved provider also declares one — see the mismatch
537
+ * error below. Does not itself change which store key is read; the
538
+ * provider's own `audience` (not the caller's) determines that. */
539
+ audience?: string;
540
+ }): Promise<Record<string, string>>;
541
+ }
542
+
288
543
  /** Guilde AI company platform.
289
544
  *
290
545
  * Authenticates via the AIP-50 service-auth claim ceremony: browser opens,
@@ -306,12 +561,18 @@ declare const BUILTIN_AUTH_PROVIDERS: readonly AuthProviderHandle[];
306
561
  * discoverEndpoints(apiBase) — two-hop PRM → AS metadata discovery
307
562
  * runAuthFlow(provider, opts) — resolve + discover + dispatch to engine
308
563
  * FLOW_ENGINES — registered flow engines (pat, …)
564
+ * CeremonyRequiredError — thrown by device-code's refreshOnly
565
+ * mode when only an interactive
566
+ * ceremony would satisfy the request
309
567
  * writeKeychainToken(svc, acct, t) — persist a credential to Keychain
310
568
  * readKeychainToken(svc, acct) — read a credential from Keychain
311
569
  * resolveAccount(acct, server) — expand {server} template
570
+ * KeychainStore / MemoryStore / FileStore — built-in CredentialStore backends
571
+ * resolveStoreRef(spec, server) — map a TokenStoreSpec to a StoreRef
572
+ * CredentialBroker — path → ready-to-use auth headers
312
573
  */
313
574
 
314
575
  declare const SPEC_NAME = "agentauth";
315
576
  declare const SPEC_VERSION = "v1";
316
577
 
317
- export { type AuthConfig, type AuthProviderDefinition, type AuthProviderFrontmatter, type AuthProviderHandle, type AuthProviderManifest, BUILTIN_AUTH_PROVIDERS, type DiscoveredEndpoints, DiscoveryError, FLOW_ENGINES, type FlowEngine, type FlowId, type FlowResult, type FlowRunOptions, type InstallConfig, type PATAuthConfig, type RunFlowOptions, SPEC_NAME, SPEC_VERSION, type ServiceAuthConfig, type TokenStoreSpec, authConfigSchema, authProviderFrontmatterSchema, defineAuthProvider, discoverEndpoints, getAuthProvider, guildeAuthProvider, installConfigSchema, listAuthProviderIds, listAuthProviders, parseAuthProviderManifest, parseAuthProviderManifestRaw, readKeychainToken, registerAuthProvider, resolveAccount, runAuthFlow, tokenStoreSpecSchema, writeKeychainToken };
578
+ export { type AuthConfig, type AuthProviderDefinition, type AuthProviderFrontmatter, type AuthProviderHandle, type AuthProviderManifest, BUILTIN_AUTH_PROVIDERS, CeremonyRequiredError, CredentialBroker, type CredentialBrokerOptions, type CredentialStore, type DeviceCodeAuthConfig, type DiscoveredEndpoints, DiscoveryError, FLOW_ENGINES, FileStore, type FlowEngine, type FlowId, type FlowResult, type FlowRunOptions, type InstallConfig, KeychainStore, MemoryStore, type PATAuthConfig, type RunFlowOptions, SPEC_NAME, SPEC_VERSION, type ServiceAuthConfig, type StoreRef, type StoredCredential, type TokenStoreSpec, authConfigSchema, authProviderFrontmatterSchema, defineAuthProvider, discoverEndpoints, getAuthProvider, guildeAuthProvider, installConfigSchema, listAuthProviderIds, listAuthProviders, parseAuthProviderManifest, parseAuthProviderManifestRaw, readKeychainToken, registerAuthProvider, resolveAccount, resolveStoreRef, runAuthFlow, tokenStoreSpecSchema, writeKeychainToken };