@agentproto/auth 0.1.1 → 1.0.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
@@ -10,7 +10,7 @@ Aligned to the WorkOS [auth.md](https://github.com/workos/auth.md) open standard
10
10
  two-hop `.well-known` discovery, the `agent_auth` metadata block, and the
11
11
  service-auth claim ceremony (`urn:workos:agent-auth:grant-type:claim`).
12
12
 
13
- > **Status: 0.1.0-alpha.** Two flows specified in v1 (`pat`, `service-auth`);
13
+ > **Status: 0.2.0-alpha.** Two flows specified in v1 (`pat`, `service-auth`);
14
14
  > `id-jag` (agentproto-as-IdP) is reserved.
15
15
 
16
16
  Spec: <https://agentproto.sh/docs/aip-50> · Standard: <https://github.com/workos/auth.md>
@@ -240,6 +240,7 @@ through that structural seam. See `@agentproto/secrets`' README.
240
240
  | `runAuthFlow` | resolve → discover → dispatch |
241
241
  | `FLOW_ENGINES` | registered flow engines (`pat`, `service-auth`) |
242
242
  | `CredentialBroker` / `CredentialBrokerOptions` | path → fresh `resolveHeaders` |
243
+ | `eligibleProfiles` | `(adapter × route) → vendor + profile eligibility predicate` |
243
244
  | `CredentialStore` / `StoreRef` / `StoredCredential` | pluggable-store interface + shapes |
244
245
  | `KeychainStore` / `MemoryStore` / `FileStore` / `resolveStoreRef` | built-in backends + ref resolver |
245
246
  | `readKeychainToken` / `writeKeychainToken` / `resolveAccount` | Keychain helpers |
package/dist/index.d.ts CHANGED
@@ -443,6 +443,10 @@ declare function resolveAccount(account: string | undefined, server: string): st
443
443
  declare function readKeychainToken(service: string, account: string): Promise<string | undefined>;
444
444
  /** Write a token to the Keychain (-U updates in place). */
445
445
  declare function writeKeychainToken(service: string, account: string, token: string): Promise<void>;
446
+ /** Remove a token from the Keychain. Returns true if an entry was deleted,
447
+ * false when none existed (a delete of an absent entry is not an error —
448
+ * the desired end state, "no credential at this slot", already holds). */
449
+ declare function deleteKeychainToken(service: string, account: string): Promise<boolean>;
446
450
 
447
451
  /**
448
452
  * KeychainStore — `CredentialStore` backed by the platform Keychain.
@@ -460,6 +464,7 @@ declare function writeKeychainToken(service: string, account: string, token: str
460
464
  declare class KeychainStore implements CredentialStore {
461
465
  read(ref: StoreRef): Promise<StoredCredential | undefined>;
462
466
  write(ref: StoreRef, cred: StoredCredential): Promise<void>;
467
+ delete(ref: StoreRef): Promise<void>;
463
468
  }
464
469
 
465
470
  /**
@@ -540,6 +545,439 @@ declare class CredentialBroker {
540
545
  }): Promise<Record<string, string>>;
541
546
  }
542
547
 
548
+ /**
549
+ * Named auth-profile types (SPEC §1c/§3.1 — `agentproto-session-config-axes`).
550
+ *
551
+ * A profile is a named credential `{ endpoint, method, credentialRef }` that
552
+ * lives independently of any adapter, generalizing `providers-store`'s
553
+ * `ProviderEntry` (one api-key per provider, `packages/providers-store/src/
554
+ * index.ts:68-80`) to N named profiles per billing endpoint. `subscription|api-key` is
555
+ * not a session property — it demotes to the `method` facet of a profile.
556
+ *
557
+ * The secret itself is NEVER inlined here — `credentialRef` is an opaque
558
+ * handle into this package's own credential storage (`store/types.ts`'s
559
+ * `CredentialStore` / `token-store.ts`), matching how the rest of
560
+ * `@agentproto/auth` already fingerprints rather than echoes secrets
561
+ * (`broker.ts`, `KeychainStore`).
562
+ */
563
+ /** How a profile authenticates — the narrow eligibility gate (not adapter
564
+ * identity). Mirrors `@agentproto/auth`'s `FlowResult.tokenKind`
565
+ * (`types.ts:151`): `pat` → `api-key`, `oat` → `oauth-bearer`. Extensible —
566
+ * new methods are additive. */
567
+ type AuthMethod = "oauth-bearer" | "api-key";
568
+ /** Per-profile model curation (the allowlist). `mode: "all"` (or an ABSENT
569
+ * {@link AuthProfile.models}) services every model the profile is otherwise
570
+ * eligible for — today's behavior. `mode: "allow"` narrows the profile to
571
+ * exactly `ids`: it services a model only when the model's catalog identity
572
+ * (its `vendor/product` or its route-qualified `ref`) is in the list. The
573
+ * intersection is applied INSIDE the catalog eligibility join
574
+ * (`catalog-models.ts`), never at the endpoint/method predicate here, so a
575
+ * curated profile stays endpoint-eligible but only makes its chosen model
576
+ * refs runnable. */
577
+ interface ModelCuration {
578
+ mode: "all" | "allow";
579
+ /** Model identities allowed when `mode === "allow"` — each a catalog
580
+ * `vendor/product` or route-qualified `ref`. Empty ⇒ services nothing. */
581
+ ids: string[];
582
+ }
583
+ /** Which spend surface a {@link CostBudget} caps. `"session"` bounds the
584
+ * windowed spend of the ONE session the budget is evaluated against;
585
+ * `"profile"` bounds the aggregate windowed spend of every session that
586
+ * resolved to the same auth profile (the session's `accessProfile.profileRef`).
587
+ * Additive — new scopes are back-compat. */
588
+ type CostBudgetScope = "session" | "profile";
589
+ /** A windowed spend cap. DISTINCT from the scalar `maxCostUsd` session-kill
590
+ * (`sessions.ts` turn-end) — a `CostBudget` never kills a session; it trips a
591
+ * governance policy (`policy:failed` on the completion-policy bus) when the
592
+ * ROLLING windowed spend for its {@link scope} crosses `maxCostUsd`. `window`
593
+ * is a rolling spec (`parseWindow` shorthand `"5h"`/`"7d"` or ISO-8601 `"P7D"`);
594
+ * spend is the priced local-estimate rollup over durable usage snapshots. */
595
+ interface CostBudget {
596
+ /** Windowed spend ceiling in USD. The budget trips when the window's priced
597
+ * spend strictly exceeds this. */
598
+ maxCostUsd: number;
599
+ /** Rolling window spec — `parseWindow`-parseable (`"5h"`, `"7d"`, `"P7D"`). */
600
+ window: string;
601
+ /** Which spend surface the window is summed over. See {@link CostBudgetScope}. */
602
+ scope: CostBudgetScope;
603
+ }
604
+ /** A named, billing-endpoint-scoped credential reference. */
605
+ interface AuthProfile {
606
+ /** Stable id, unique across all profiles (the `profileRef` a session
607
+ * attaches). */
608
+ id: string;
609
+ /** Which billing endpoint this credential authenticates against
610
+ * (`anthropic`, `openrouter`, `moonshot`, …). This deliberately does NOT
611
+ * mean the model builder: OpenRouter can serve a z-ai model, for example.
612
+ * Kept as `string` here to stay decoupled from
613
+ * `@agentproto/model-catalog`, same rationale as the driver's
614
+ * `AgentCliDefinition.provider`. */
615
+ endpoint: string;
616
+ /** How this profile authenticates. */
617
+ method: AuthMethod;
618
+ /** Opaque handle into this package's credential storage — NEVER the secret
619
+ * itself. Resolved through `CredentialStore` / `token-store.ts` at use
620
+ * time, not stored inline. Set for a credential-backed profile; absent for
621
+ * a source-backed profile. Mutually exclusive with {@link source}. */
622
+ credentialRef?: string;
623
+ /** Self-refreshing credential source (`oauth-bearer` only) — same value
624
+ * spawn's subscription resolution accepts (today, only
625
+ * `"claude-code-oauth"`, `spawn-defaults.ts`'s `CLAUDE_CODE_OAUTH_SOURCE`).
626
+ * A source-backed profile has no stored secret: the credential is resolved
627
+ * fresh at spawn time instead. Mutually exclusive with
628
+ * {@link credentialRef}. */
629
+ source?: string;
630
+ /** Optional human-readable name ("Jeremy Max", "work OpenRouter"). */
631
+ label?: string;
632
+ /** Whole-profile enable/disable. ABSENT (or `false`) ⇒ enabled (today's
633
+ * behavior). `true` ⇒ the profile is skipped by {@link eligibleProfiles}
634
+ * entirely, so every model it would otherwise service drops to
635
+ * non-runnable. A REAL persisted state, distinct from the derived
636
+ * `runnable` flag the catalog computes. */
637
+ disabled?: boolean;
638
+ /** Optional per-model curation (the allowlist). ABSENT ⇒ `{ mode: "all" }`
639
+ * — services every eligible model, byte-identical to a profile that
640
+ * predates this field. See {@link ModelCuration}. */
641
+ models?: ModelCuration;
642
+ /** Optional windowed spend cap attached to this profile (SPEC §1c). ABSENT ⇒
643
+ * no profile-level budget — byte-identical to a profile that predates this
644
+ * field. When present with `scope: "profile"`, a governance policy evaluates
645
+ * the aggregate windowed spend of every session on this profile at each
646
+ * turn-end and trips `policy:failed` on overage. Distinct from the scalar
647
+ * session-kill `maxCostUsd` — see {@link CostBudget}. */
648
+ costBudget?: CostBudget;
649
+ /** Provenance: where this profile's credential was IMPORTED from, when it
650
+ * was materialized by the WS6 discovery→import flow
651
+ * (`auth_profile_import`) rather than created by hand. One of the discovery
652
+ * origins (`claude-code`, `hermes-config`, `env`, `codex`, `gemini`). Purely
653
+ * informational — it drives the panel's "imported from <origin>" badge and
654
+ * nothing in the spawn/eligibility path. ABSENT for a hand-created profile.
655
+ * Kept as `string` (not a union) to stay decoupled, same rationale as
656
+ * {@link endpoint}. */
657
+ origin?: string;
658
+ }
659
+
660
+ /**
661
+ * Named auth-profile store — CRUD over `~/.agentproto/auth-profiles.json`
662
+ * (mode 0600), generalizing `providers-store`'s single-key-per-provider file
663
+ * (`packages/providers-store/src/index.ts:68-90`, `ProviderEntry`/
664
+ * `ProvidersFile`) to N named `AuthProfile`s per billing endpoint, keyed by `id`.
665
+ *
666
+ * This is the profile-metadata store, not the credential store: entries hold
667
+ * `credentialRef` (a pointer), never a secret, so — unlike `FileStore`
668
+ * (`store/file-store.ts`) — this file needs no encryption. Reuses the exact
669
+ * persistence primitives `providers-store` already established (a versioned
670
+ * JSON file under `~/.agentproto/`, `node:fs/promises`, mode 0600) rather
671
+ * than inventing a new format, just relocated to `@agentproto/auth` — the
672
+ * correct home for named profiles per SPEC §1c (`providers.json` is the
673
+ * degenerate one-key-per-provider case this generalizes).
674
+ */
675
+
676
+ declare const authProfilesFileSchema: z.ZodObject<{
677
+ version: z.ZodLiteral<1>;
678
+ profiles: z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodObject<{
679
+ id: z.ZodString;
680
+ vendor: z.ZodString;
681
+ method: z.ZodEnum<{
682
+ "oauth-bearer": "oauth-bearer";
683
+ "api-key": "api-key";
684
+ }>;
685
+ credentialRef: z.ZodOptional<z.ZodString>;
686
+ source: z.ZodOptional<z.ZodString>;
687
+ label: z.ZodOptional<z.ZodString>;
688
+ disabled: z.ZodOptional<z.ZodBoolean>;
689
+ models: z.ZodOptional<z.ZodObject<{
690
+ mode: z.ZodEnum<{
691
+ allow: "allow";
692
+ all: "all";
693
+ }>;
694
+ ids: z.ZodArray<z.ZodString>;
695
+ }, z.core.$strip>>;
696
+ costBudget: z.ZodOptional<z.ZodObject<{
697
+ maxCostUsd: z.ZodNumber;
698
+ window: z.ZodString;
699
+ scope: z.ZodEnum<{
700
+ session: "session";
701
+ profile: "profile";
702
+ }>;
703
+ }, z.core.$strip>>;
704
+ origin: z.ZodOptional<z.ZodString>;
705
+ }, z.core.$strip>, z.ZodTransform<AuthProfile, {
706
+ id: string;
707
+ vendor: string;
708
+ method: "oauth-bearer" | "api-key";
709
+ credentialRef?: string | undefined;
710
+ source?: string | undefined;
711
+ label?: string | undefined;
712
+ disabled?: boolean | undefined;
713
+ models?: {
714
+ mode: "allow" | "all";
715
+ ids: string[];
716
+ } | undefined;
717
+ costBudget?: {
718
+ maxCostUsd: number;
719
+ window: string;
720
+ scope: "session" | "profile";
721
+ } | undefined;
722
+ origin?: string | undefined;
723
+ }>>>;
724
+ }, z.core.$strip>;
725
+ type AuthProfilesFile = z.infer<typeof authProfilesFileSchema>;
726
+ declare function authProfilesPath(): string;
727
+ declare function loadAuthProfiles(): Promise<AuthProfilesFile>;
728
+ /** List all profiles, optionally filtered to one billing endpoint. */
729
+ declare function listAuthProfiles(endpoint?: string): Promise<AuthProfile[]>;
730
+ /** Look up a single profile by id, or undefined if none exists. */
731
+ declare function getAuthProfile(id: string): Promise<AuthProfile | undefined>;
732
+ /** Add (or replace) a profile, keyed by its `id`. */
733
+ declare function addAuthProfile(profile: AuthProfile): Promise<void>;
734
+ /** Remove a profile by id. Returns true if it existed. */
735
+ declare function removeAuthProfile(id: string): Promise<boolean>;
736
+
737
+ /**
738
+ * Auth-profile provisioning — the create/delete flow the store CRUD
739
+ * (`profile-store.ts`) deliberately leaves out.
740
+ *
741
+ * `profile-store.ts` writes only profile *metadata* (`credentialRef`, a
742
+ * pointer). Provisioning is the two-sided operation nobody else owned: write
743
+ * the *secret* to the credential store at a derived slot, THEN record the
744
+ * metadata pointing at it — and, on delete, tear both down together. This is
745
+ * the single place that knows the `agentproto.auth.<vendor>[.<qualifier>]`
746
+ * keychain-slot convention (matching the entries seeded by hand in
747
+ * `~/.agentproto/auth-profiles.json`).
748
+ *
749
+ * The secret is INPUT-only. It is written to the `CredentialStore` and never
750
+ * echoed back: `createAuthProfile` returns non-secret metadata plus a
751
+ * one-way `fingerprint` (same "fingerprint, don't echo" discipline as
752
+ * `broker.ts` / `KeychainStore`), so a caller can confirm *which* credential
753
+ * landed without ever seeing it.
754
+ *
755
+ * Dependency-injected (`ProfileProvisionDeps`) so it's unit-testable against
756
+ * a `MemoryStore` + an in-memory profile map, with no keychain or filesystem.
757
+ */
758
+
759
+ /** Input to {@link createAuthProfile}. `credential` is the raw secret — it is
760
+ * written to the store and NEVER returned. Exactly one of `credential` /
761
+ * `source` must be given for an `oauth-bearer` profile; `api-key` always
762
+ * requires `credential` (a source-backed profile only makes sense for a
763
+ * self-refreshing subscription bearer). */
764
+ interface CreateAuthProfileInput {
765
+ /** Stable id, unique across all profiles. */
766
+ id: string;
767
+ /** Billing endpoint / vendor this credential authenticates against
768
+ * (`anthropic`, `openrouter`, `moonshot`, …). */
769
+ endpoint: string;
770
+ /** How this profile authenticates. */
771
+ method: AuthMethod;
772
+ /** The raw secret (a subscription OAuth bearer, an API key). INPUT-ONLY.
773
+ * Mutually exclusive with `source`. */
774
+ credential?: string;
775
+ /** Self-refreshing credential source (`oauth-bearer` only, e.g.
776
+ * `"claude-code-oauth"`) — the profile stores no secret; the credential is
777
+ * resolved fresh at spawn time instead. Mutually exclusive with
778
+ * `credential`. */
779
+ source?: string;
780
+ /** Optional human-readable name. */
781
+ label?: string;
782
+ /** Optional explicit credential-store slot. Omitted ⇒ derived from
783
+ * `endpoint` + `method` (see {@link deriveCredentialRef}). Ignored for a
784
+ * source-backed profile (nothing is written to the credential store). */
785
+ credentialRef?: string;
786
+ /** Optional provenance — where the credential was imported from (a WS6
787
+ * discovery origin). Stamped verbatim on the created profile; purely
788
+ * informational. */
789
+ origin?: string;
790
+ }
791
+ /** The non-secret result of a create — safe to log, return over the wire, or
792
+ * render in a UI. Carries a fingerprint, never the credential. */
793
+ interface CreatedAuthProfile {
794
+ id: string;
795
+ endpoint: string;
796
+ method: AuthMethod;
797
+ /** Set for a credential-backed profile; absent for a source-backed one. */
798
+ credentialRef?: string;
799
+ /** Set for a source-backed profile; absent for a credential-backed one. */
800
+ source?: string;
801
+ label?: string;
802
+ /** Provenance stamped at import time, when given. */
803
+ origin?: string;
804
+ /** One-way fingerprint of the stored credential (sha256, truncated) — lets
805
+ * a caller confirm *which* secret was stored without exposing it. Absent
806
+ * for a source-backed profile — there is no stored secret to fingerprint. */
807
+ fingerprint?: string;
808
+ }
809
+ /** The result of a delete. `deleted` is false when no profile had that id
810
+ * (idempotent — the desired end state already held). */
811
+ interface DeletedAuthProfile {
812
+ deleted: boolean;
813
+ id: string;
814
+ /** The slot the credential lived at, when a profile was actually removed. */
815
+ credentialRef?: string;
816
+ }
817
+ /** Injectable persistence surface — production wires these to
818
+ * `profile-store.ts` + a `KeychainStore`; tests pass in-memory doubles. */
819
+ interface ProfileProvisionDeps {
820
+ /** Where secrets live (a `KeychainStore` in production). */
821
+ store: CredentialStore;
822
+ getProfile: (id: string) => Promise<AuthProfile | undefined>;
823
+ listProfiles: () => Promise<AuthProfile[]>;
824
+ addProfile: (profile: AuthProfile) => Promise<void>;
825
+ removeProfile: (id: string) => Promise<boolean>;
826
+ }
827
+ /** Raised when create/delete input fails validation. Distinct type so a host
828
+ * (HTTP route, MCP tool) can map it to a 400 rather than a 500. */
829
+ declare class AuthProfileValidationError extends Error {
830
+ constructor(message: string);
831
+ }
832
+ /** Normalized, validated create input — every field trimmed and checked.
833
+ * Exactly one of `credential` / `source` is present. */
834
+ interface ValidatedCreateInput {
835
+ id: string;
836
+ endpoint: string;
837
+ method: AuthMethod;
838
+ credential?: string;
839
+ source?: string;
840
+ label?: string;
841
+ credentialRef?: string;
842
+ origin?: string;
843
+ }
844
+ /**
845
+ * Validate + normalize raw create input. Pure — no I/O. Throws
846
+ * {@link AuthProfileValidationError} with a specific message on the first
847
+ * problem it finds, so the surface layer can surface a clear 400.
848
+ */
849
+ declare function validateCreateInput(input: CreateAuthProfileInput): ValidatedCreateInput;
850
+ /**
851
+ * Derive the credential-store slot for a profile. Matches the convention of
852
+ * the hand-seeded entries: `agentproto.auth.<endpoint>` for an api-key,
853
+ * `agentproto.auth.<endpoint>.sub` for a subscription (oauth-bearer), plus an
854
+ * optional `<qualifier>` to disambiguate a second profile on the same
855
+ * endpoint+method. Pure.
856
+ */
857
+ declare function deriveCredentialRef(input: {
858
+ endpoint: string;
859
+ method: AuthMethod;
860
+ qualifier?: string;
861
+ }): string;
862
+ /** One-way fingerprint of a secret — sha256, first 12 hex chars. Enough to
863
+ * distinguish credentials by eye; not reversible. */
864
+ declare function fingerprintCredential(secret: string): string;
865
+ /** Non-secret identity of a STORED credential — a one-way {@link
866
+ * fingerprintCredential} plus the trailing 4 characters (the standard
867
+ * "which key is this" affordance, like a card's last four). Neither is
868
+ * reversible to the secret. `last4` is omitted for a secret too short to
869
+ * reveal a tail safely (see {@link MIN_LEN_FOR_LAST4}) — fail closed rather
870
+ * than expose. */
871
+ interface CredentialIdentity {
872
+ fingerprint: string;
873
+ last4?: string;
874
+ }
875
+ /** Compute the non-secret {@link CredentialIdentity} of a stored secret.
876
+ * Money-safety: returns ONLY the one-way fingerprint + a trailing tail —
877
+ * never the secret, and never the tail when the secret is too short to
878
+ * reveal one safely. */
879
+ declare function credentialIdentity(secret: string): CredentialIdentity;
880
+ /**
881
+ * Create a profile end-to-end: validate, derive the credential slot (avoiding
882
+ * collision with an existing profile's slot), write the secret to the store,
883
+ * then record the metadata. Rejects a duplicate id rather than silently
884
+ * overwriting. Returns non-secret metadata + a fingerprint; the credential is
885
+ * never echoed back.
886
+ */
887
+ declare function createAuthProfile(input: CreateAuthProfileInput, deps: ProfileProvisionDeps): Promise<CreatedAuthProfile>;
888
+ /**
889
+ * Delete a profile and its credential. Idempotent: a missing id returns
890
+ * `{ deleted: false }` rather than throwing. The keychain entry is removed
891
+ * only when no OTHER profile still references the same slot — two profiles
892
+ * can legitimately share a `credentialRef`, and dropping one must not strand
893
+ * the other.
894
+ */
895
+ declare function deleteAuthProfile(id: string, deps: ProfileProvisionDeps): Promise<DeletedAuthProfile>;
896
+ /**
897
+ * Enable or disable a whole profile (WS2). Metadata-only — never touches the
898
+ * credential store. Enabling CLEARS the `disabled` field entirely rather than
899
+ * writing `disabled: false`, so a re-enabled profile is byte-identical on
900
+ * disk to one that was never disabled (the ABSENT-means-enabled invariant).
901
+ * Throws {@link AuthProfileValidationError} for an unknown id so a host can map
902
+ * it to a 404/400. Returns the updated (non-secret) profile.
903
+ */
904
+ declare function setAuthProfileEnabled(id: string, enabled: boolean, deps: ProfileProvisionDeps): Promise<AuthProfile>;
905
+ /**
906
+ * Set a profile's per-model curation allowlist (WS3). Metadata-only. `mode:
907
+ * "all"` CLEARS the `models` field entirely (curation off ⇒ absent, the
908
+ * byte-identical "services everything" default), so only a genuine `mode:
909
+ * "allow"` persists a `models` object. `ids` are trimmed, de-duped, and
910
+ * blanks dropped. Throws {@link AuthProfileValidationError} for an unknown id.
911
+ * Returns the updated (non-secret) profile.
912
+ */
913
+ declare function setAuthProfileModels(id: string, curation: ModelCuration, deps: ProfileProvisionDeps): Promise<AuthProfile>;
914
+
915
+ /**
916
+ * (adapter × route) → billing-endpoint resolution + the profile eligibility predicate
917
+ * (SPEC §1c, `agentproto-session-config-axes/SPEC.md:102-125`).
918
+ *
919
+ * profile eligible for (adapter, route) iff
920
+ * profile.endpoint == resolveEndpoint(adapter, route).endpoint
921
+ * AND profile.method ∈ methodsPresentable(adapter, route)
922
+ *
923
+ * `route` is the endpoint selector (SPEC axis #4 — direct vs a gateway mode
924
+ * like `moonshot`/`openrouter`), so access is deliberately downstream of it:
925
+ * which endpoint (and thus which profiles) are eligible changes when route
926
+ * changes. `AdapterAuthManifest` is a narrow, package-local projection of
927
+ * exactly what this predicate needs from an adapter's real AIP-45 manifest
928
+ * (`packages/driver/agent-cli/src/types.ts`'s `AgentCliDefinition`) — this
929
+ * package intentionally does not depend on `@agentproto/driver-agent-cli`
930
+ * (wrong dependency direction; drivers consume auth, not vice versa), so the
931
+ * runtime is the one that will later project a real manifest into this
932
+ * shape (out of scope here — foundation only, no live spawn-path wiring).
933
+ *
934
+ * `methodsByRoute` is the static per-endpoint method table ADR'd in SPEC §3.4:
935
+ * "which auth methods this adapter can present for a resolved route." It is
936
+ * the derivable replacement for two hand-maintained artifacts SPEC §1c calls
937
+ * out for eventual removal — the per-adapter `authSubscription` boolean
938
+ * (`adapters/claude-code/src/index.ts:90-95`) and curatorial vendor deny
939
+ * lists (`adapters/hermes/HERMES.md:40-42`) — neither of which this PR
940
+ * touches; `authSubscription` stays exactly as-is (see the comment on it in
941
+ * `spawn-defaults.ts:345` — this module is additive, not a replacement).
942
+ *
943
+ * ACP `authenticate` is NOT a live discovery source for this table (the
944
+ * server handler is a stub, `packages/acp/src/server/index.ts:69`) — method
945
+ * discovery is this static table, not runtime negotiation (SPEC §3.7).
946
+ */
947
+
948
+ /** A narrow, package-local projection of an adapter's declared auth
949
+ * capability — enough to resolve (adapter, route) → endpoint and the auth
950
+ * methods presentable on that route. Not the real AIP-45 manifest type. */
951
+ interface AdapterAuthManifest {
952
+ /** Adapter id (e.g. "claude-code", "hermes"), for error messages only. */
953
+ id: string;
954
+ /** route id (e.g. "direct", or a gateway mode id like "moonshot") →
955
+ * the billing endpoint used when spawned on that route. */
956
+ endpointByRoute: Readonly<Record<string, string>>;
957
+ /** route id → the auth methods this adapter can present when spawned on
958
+ * that route — the method-side of the eligibility predicate. */
959
+ methodsByRoute: Readonly<Record<string, readonly AuthMethod[]>>;
960
+ }
961
+ /** Resolve which billing endpoint a given (adapter, route) pair uses. Throws
962
+ * when the manifest declares no endpoint for `route` — an unresolvable route
963
+ * is a caller bug (a bad/unknown route id), not a "no profiles eligible"
964
+ * case, so this fails loud rather than returning an empty endpoint. */
965
+ declare function resolveEndpoint(manifest: AdapterAuthManifest, route: string): {
966
+ endpoint: string;
967
+ };
968
+ /** Which auth methods `manifest`'s adapter can present on `route`. Empty
969
+ * (never throws) when the route is declared for vendor resolution but has
970
+ * no methods table entry — that's "no eligible profiles", a legitimate
971
+ * eligibility outcome, not an error. */
972
+ declare function methodsPresentable(manifest: AdapterAuthManifest, route: string): AuthMethod[];
973
+ /** The eligibility predicate: profiles whose endpoint matches the route's
974
+ * resolved endpoint AND whose method the adapter can present on that route.
975
+ * A whole-profile-disabled profile (`disabled: true`) is skipped outright —
976
+ * every model it would otherwise service drops to non-runnable (WS2). This is
977
+ * the profile-level gate; per-model curation (`models`) is intersected
978
+ * downstream in the catalog join, not here (SPEC §1c / catalog-models.ts). */
979
+ declare function eligibleProfiles(profiles: readonly AuthProfile[], manifest: AdapterAuthManifest, route: string): AuthProfile[];
980
+
543
981
  /** Guilde AI company platform.
544
982
  *
545
983
  * Authenticates via the AIP-50 service-auth claim ceremony: browser opens,
@@ -570,9 +1008,15 @@ declare const BUILTIN_AUTH_PROVIDERS: readonly AuthProviderHandle[];
570
1008
  * KeychainStore / MemoryStore / FileStore — built-in CredentialStore backends
571
1009
  * resolveStoreRef(spec, server) — map a TokenStoreSpec to a StoreRef
572
1010
  * CredentialBroker — path → ready-to-use auth headers
1011
+ * AuthProfile / AuthMethod — named, endpoint-scoped credential ref
1012
+ * listAuthProfiles/getAuthProfile/
1013
+ * addAuthProfile/removeAuthProfile — named-profile store CRUD
1014
+ * resolveEndpoint/methodsPresentable/
1015
+ * eligibleProfiles — (adapter × route) → endpoint + the
1016
+ * profile eligibility predicate
573
1017
  */
574
1018
 
575
1019
  declare const SPEC_NAME = "agentauth";
576
1020
  declare const SPEC_VERSION = "v1";
577
1021
 
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 };
1022
+ export { type AdapterAuthManifest, type AuthConfig, type AuthMethod, type AuthProfile, AuthProfileValidationError, type AuthProfilesFile, type AuthProviderDefinition, type AuthProviderFrontmatter, type AuthProviderHandle, type AuthProviderManifest, BUILTIN_AUTH_PROVIDERS, CeremonyRequiredError, type CostBudget, type CostBudgetScope, type CreateAuthProfileInput, type CreatedAuthProfile, CredentialBroker, type CredentialBrokerOptions, type CredentialIdentity, type CredentialStore, type DeletedAuthProfile, type DeviceCodeAuthConfig, type DiscoveredEndpoints, DiscoveryError, FLOW_ENGINES, FileStore, type FlowEngine, type FlowId, type FlowResult, type FlowRunOptions, type InstallConfig, KeychainStore, MemoryStore, type ModelCuration, type PATAuthConfig, type ProfileProvisionDeps, type RunFlowOptions, SPEC_NAME, SPEC_VERSION, type ServiceAuthConfig, type StoreRef, type StoredCredential, type TokenStoreSpec, type ValidatedCreateInput, addAuthProfile, authConfigSchema, authProfilesPath, authProviderFrontmatterSchema, createAuthProfile, credentialIdentity, defineAuthProvider, deleteAuthProfile, deleteKeychainToken, deriveCredentialRef, discoverEndpoints, eligibleProfiles, fingerprintCredential, getAuthProfile, getAuthProvider, guildeAuthProvider, installConfigSchema, listAuthProfiles, listAuthProviderIds, listAuthProviders, loadAuthProfiles, methodsPresentable, parseAuthProviderManifest, parseAuthProviderManifestRaw, readKeychainToken, registerAuthProvider, removeAuthProfile, resolveAccount, resolveEndpoint, resolveStoreRef, runAuthFlow, setAuthProfileEnabled, setAuthProfileModels, tokenStoreSpecSchema, validateCreateInput, writeKeychainToken };