Add per-model token provider to useReplicatedModels#2459
Add per-model token provider to useReplicatedModels#2459
Conversation
…meters parameter Co-authored-by: abstraktor <569215+abstraktor@users.noreply.github.com>
|
@copilot that won't work: the models must all share the same ClientJS instance. Also, the past and future models should share the same ClientJS instance. First, for being able to resolve cross-model references. Second, switching a branch must not result in refetching the entire version, but only in fetching the delta. The ClientJS instance is keeping the cache of versions to ensure we only transmit what is not known yet. I like the idea of changing the API to work without useModelClient though. |
…itching Co-authored-by: abstraktor <569215+abstraktor@users.noreply.github.com>
Redesigned in ee23b4b. All models now share a single
|
| return createClient( | ||
| clientOrUrl, | ||
| getToken !== undefined ? tokenProvider : undefined, | ||
| ); |
There was a problem hiding this comment.
the client only refetches the token upon 401. it has only means to store one token so far. Change the implementation of the client to be able to keep a token per "binding".
There was a problem hiding this comment.
Done in 98d2228. Added PerBindingAuthPlugin — a custom Ktor plugin installed in ClientJSInternalBuilder.configureHttpClient instead of the standard bearer-auth plugin. On every HTTP request the plugin parses the URL path for /repositories/{repoId}/branches/{branchId}, looks up the matching token from a mutable map (bindingTokenProviders) shared with ClientJSImpl, and sets the Authorization header. Added tokenProvider: (() -> Promise<String?>)? to ReplicatedModelParameters; startReplicatedModels registers each binding's provider in the map when called. Falls back to the global token (from connectClient) for non-binding requests.
| let currentModelsForToken: ReplicatedModelParameters[] = []; | ||
| const tokenProvider = (): Promise<string | null> => { | ||
| if (currentModelsForToken.length === 0) return Promise.resolve(null); | ||
| return getToken!(currentModelsForToken[0]); |
There was a problem hiding this comment.
Why [0]? this looks like an artifact from TDD with too low coverage. This doesnt make much sense
There was a problem hiding this comment.
Fixed in 98d2228. The [0] and the shared currentModelsForToken variable are gone. Each model in the modelsValue array now gets its own tokenProvider closure — () => getToken(p) — passed into a freshly constructed ReplicatedModelParameters. The per-binding token is then routed to the right HTTP requests by the URL-aware PerBindingAuthPlugin in the Kotlin client.
| // Re-runs when `client` changes. When the URL changes the old ClientJS is | ||
| // disposed and a new one is created (with the same token-provider closure). | ||
| // --------------------------------------------------------------------------- | ||
| useLastPromiseEffect<ClientJS>( |
There was a problem hiding this comment.
question: how is this any better than calling useModelClient(…) in here?
There was a problem hiding this comment.
Now using useModelClient internally (98d2228). When client is a URL string, useReplicatedModels delegates client lifecycle management to useModelClient with a getter that returns the URL (or null when client is a ClientJS). The two-effect architecture is collapsed into a single effect, and the createClient seam is forwarded directly to useModelClient's getClient parameter — consistent with the existing pattern.
…odels Co-authored-by: abstraktor <569215+abstraktor@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
When a Vue.js client switches branches, the existing
useReplicatedModelswould attempt to connect with a stale token, receive a 403, and provide no opportunity for the caller to supply a fresh one. The token provider had no access to model parameters, making repository/branch-scoped tokens impossible.Changes
useReplicatedModels— newgetTokenparameter and URL string supportgetToken?: (params: ReplicatedModelParameters) => Promise<string | null>clientnow also accepts a server URLstring(in addition toClientJS), eliminating the need to useuseModelClientseparatelyclientis a URL string, a singleClientJSis created and shared across all models and all branch switches viauseModelClientinternally. This preserves the version cache so that switching branches only fetches the delta, and cross-model references continue to resolve correctly.ClientJSis only disposed and recreated when the URL itself changes; model/branch changes reuse the existing client.createClientoptional parameter (defaults toconnectClientviauseModelClient) as a test seam.ClientJSusage is fully backwards compatible.ReplicatedModelParameters— newtokenProviderfield (Kotlin)tokenProvider: (() -> Promise<String?>)?toReplicatedModelParameters(changed fromdata classtoclass).getTokenis provided, each model inmodelsreceives its own independent() => getToken(p)closure astokenProvider— no shared state, no[0]index.Per-binding token routing in
ClientJS(Kotlin)PerBindingAuthPlugin— a custom Ktor client plugin that intercepts every individual HTTP request, parses the URL path for/repositories/{repoId}/branches/{branchId}, and looks up the matching token from abindingTokenProvidersmap shared withClientJSImpl.ClientJSInternalBuildersubclassesModelClientV2Builderto installPerBindingAuthPluginalongside the standard JSON/timeout/retry plugins, replacing the old single-token bearer-auth approach.connectClient'sbearerTokenProvider) for non-binding requests (e.g./v2/server-id).Usage example
Tests
getTokentests to reflect per-binding behaviour: verifytokenProvideris attached per model and delegates togetTokenwhen invoked, the sameClientJSis reused on branch switch, and a newClientJSis created on URL change.extractBindingKeyunit tests in Kotlin.💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.