The STATIC API-KEY provider shape, owned once.
A vendor that authenticates with a plain key per plan (Alibaba Model Studio,
Z.ai) needs the same things: a file under ~/.vis, a per-plan slice inside
it, one lookup order (TUI/config key, env var, that file), a token envelope
for the router, a status report that never prints the key, the interactive
vis-agent providers auth flow, a per-plan logout and the extension entry
map. Only the STRINGS and the plan table differ, so a provider extension
declares a BOOK and this namespace owns the behaviour:
{:vendor "Alibaba" ; how a message names it :file "alibaba-auth.json" ; lives under ~/.vis :file-shape :flat ; or :by-plan (the default) :key-hint "<your-alibaba-api-key>" ; the export line's value :error-type :vis/alibaba-not-authenticated ; ex-info :type when no key :auth-notes [" The key is plan-scoped …"] ; extra prompt lines, optional :plans {:coding {:provider-id :alibaba-coding-plan :label "Alibaba (Coding Plan)" :base-url "https://…" :default-models ["…"] :env-keys ["ALIBABA_CODING_PLAN_API_KEY"]}}}
The plan TAG (:coding) is local to the file and the :provider-id is the
catalog id; the two never merge and no lookup ever falls back to a sibling
plan, because a key issued for one plan is rejected by the other's endpoint.
:file-shape decides where a slice LIVES. :by-plan (the default) nests each
plan under its tag, because those keys are separate credentials. :flat hands
the whole file to a book with ONE credential: the key sits at the root
({"api_key" …}), the plan tag never reaches disk, and no message shows a
plan vocabulary the user has nothing to choose between. Declared, never
inferred - growing a second plan is a deliberate change of file shape, not a
silent one that orphans every key already stored.
What a provider still owns: its plan table, its :provider/limits-fn (a
quota endpoint is vendor-specific) and its own namespace docstring.
The STATIC API-KEY provider shape, owned once.
A vendor that authenticates with a plain key per plan (Alibaba Model Studio,
Z.ai) needs the same things: a file under `~/.vis`, a per-plan slice inside
it, one lookup order (TUI/config key, env var, that file), a token envelope
for the router, a status report that never prints the key, the interactive
`vis-agent providers auth` flow, a per-plan logout and the extension entry
map. Only the STRINGS and the plan table differ, so a provider extension
declares a BOOK and this namespace owns the behaviour:
{:vendor "Alibaba" ; how a message names it
:file "alibaba-auth.json" ; lives under ~/.vis
:file-shape :flat ; or :by-plan (the default)
:key-hint "<your-alibaba-api-key>" ; the export line's value
:error-type :vis/alibaba-not-authenticated ; ex-info :type when no key
:auth-notes [" The key is plan-scoped …"] ; extra prompt lines, optional
:plans {:coding {:provider-id :alibaba-coding-plan
:label "Alibaba (Coding Plan)"
:base-url "https://…"
:default-models ["…"]
:env-keys ["ALIBABA_CODING_PLAN_API_KEY"]}}}
The plan TAG (`:coding`) is local to the file and the `:provider-id` is the
catalog id; the two never merge and no lookup ever falls back to a sibling
plan, because a key issued for one plan is rejected by the other's endpoint.
`:file-shape` decides where a slice LIVES. `:by-plan` (the default) nests each
plan under its tag, because those keys are separate credentials. `:flat` hands
the whole file to a book with ONE credential: the key sits at the root
(`{"api_key" …}`), the plan tag never reaches disk, and no message shows a
plan vocabulary the user has nothing to choose between. Declared, never
inferred - growing a second plan is a deliberate change of file shape, not a
silent one that orphans every key already stored.
What a provider still owns: its plan table, its `:provider/limits-fn` (a
quota endpoint is vendor-specific) and its own namespace docstring.(auth! book plan-tag printer-fn)The interactive auth flow, invoked by the runtime with ONE printer-fn
argument (an (fn [line] …) writing one user-visible line). read-line is
unavailable because the CLI dispatcher captures stdout/stderr to a log file,
so the shared pattern is: print instructions, and accept the key from the
env var the user set in the shell that ran the command. A key already in the
config or on disk is a no-op; a key found only in the env is written through
so later runs no longer depend on the user's shell.
The interactive auth flow, invoked by the runtime with ONE `printer-fn` argument (an `(fn [line] …)` writing one user-visible line). `read-line` is unavailable because the CLI dispatcher captures stdout/stderr to a log file, so the shared pattern is: print instructions, and accept the key from the env var the user set in the shell that ran the command. A key already in the config or on disk is a no-op; a key found only in the env is written through so later runs no longer depend on the user's shell.
(auth-file book)Where the book persists its keys. A FUNCTION, never a top-level def:
native-image folds constants at build time, which would bake the builder's
home directory into the binary.
Where the book persists its keys. A FUNCTION, never a top-level `def`: native-image folds constants at build time, which would bake the builder's home directory into the binary.
(auth-instruction-lines book plan-tag)What vis-agent providers auth <plan> prints when no key exists anywhere:
the two ways to provide one, this plan's own env vars, and its endpoint.
What `vis-agent providers auth <plan>` prints when no key exists anywhere: the two ways to provide one, this plan's own env vars, and its endpoint.
(detect-key book plan-tag)Lookup priority for one plan:
:api-key for this plan.{:api-key str :source kw} or nil. Never throws.:source is :config, :env-var or :auth-file, so a status report can
tell the user WHERE the key came from.
Lookup priority for one plan:
1. TUI/config provider `:api-key` for this plan.
2. The plan's env-var chain.
3. The book's file slice for this plan.
Returns `{:api-key str :source kw}` or nil. Never throws.
`:source` is `:config`, `:env-var` or `:auth-file`, so a status report can
tell the user WHERE the key came from.(key-preview api-key)Short non-secret preview for status output: these are long opaque tokens, so show the first 8 characters and an ellipsis.
Short non-secret preview for status output: these are long opaque tokens, so show the first 8 characters and an ellipsis.
(load-auth book)The WHOLE persisted map (every plan) or nil, so one read serves a caller asking about any sibling plan. Never throws: an unreadable file reads as 'no key'.
The WHOLE persisted map (every plan) or nil, so one read serves a caller asking about any sibling plan. Never throws: an unreadable file reads as 'no key'.
(logout-plan! book plan-tag)Clear ONE plan's persisted key; the sibling plan stays intact.
Clear ONE plan's persisted key; the sibling plan stays intact.
(provider-entries book limits-fn)One :ext/providers entry per plan in the book, in plan order.
limits-fn is (fn [plan-tag] (fn [] report)) because a quota endpoint -
or the absence of one - is the vendor's own business.
One `:ext/providers` entry per plan in the book, in plan order. `limits-fn` is `(fn [plan-tag] (fn [] report))` because a quota endpoint - or the absence of one - is the vendor's own business.
(status-report book plan-tag)One plan's authentication state, with the key PREVIEWED, never printed.
One plan's authentication state, with the key PREVIEWED, never printed.
(token-envelope book plan-tag)The uniform runtime credential the central router adapter consumes:
{:token … :api-url …}. Throws when no source has a key, so the runtime
fails fast pointing at vis-agent providers auth <plan> instead of a
confusing upstream 401.
The uniform runtime credential the central router adapter consumes:
`{:token … :api-url …}`. Throws when no source has a key, so the runtime
fails fast pointing at `vis-agent providers auth <plan>` instead of a
confusing upstream 401.(update-plan! book plan-tag slice)Merge slice into the file under plan-tag - or make it the WHOLE file when
the book is flat. A nil slice REMOVES that plan. An emptied file is deleted
so its mere existence keeps reading as 'authenticated' for detect-fn
semantics. Returns the new map.
Merge `slice` into the file under `plan-tag` - or make it the WHOLE file when the book is flat. A nil `slice` REMOVES that plan. An emptied file is deleted so its mere existence keeps reading as 'authenticated' for `detect-fn` semantics. Returns the new map.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |