@loomcycle/client 1.125.0 → 1.126.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 +158 -0
- package/dist/cjs/client-tools.js +28 -5
- package/dist/cjs/client.js +239 -9
- package/dist/cjs/device-login.js +146 -0
- package/dist/cjs/errors.js +40 -1
- package/dist/cjs/fetch-helpers.js +31 -0
- package/dist/cjs/index.js +35 -1
- package/dist/cjs/oauth.js +400 -0
- package/dist/client-tools.d.ts +6 -2
- package/dist/client-tools.js +28 -5
- package/dist/client.d.ts +196 -3
- package/dist/client.js +240 -10
- package/dist/device-login.d.ts +12 -0
- package/dist/device-login.js +143 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +37 -0
- package/dist/fetch-helpers.d.ts +8 -0
- package/dist/fetch-helpers.js +30 -0
- package/dist/index.d.ts +31 -2
- package/dist/index.js +29 -1
- package/dist/oauth.d.ts +131 -0
- package/dist/oauth.js +393 -0
- package/dist/types.d.ts +512 -11
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -24,11 +24,18 @@
|
|
|
24
24
|
* full mapping table.
|
|
25
25
|
*/
|
|
26
26
|
import { InteractiveSession } from "./interactive.js";
|
|
27
|
+
import { LoomcycleOAuth } from "./oauth.js";
|
|
27
28
|
import { ClientToolHost, type ConnectClientToolsOptions } from "./client-tools.js";
|
|
28
|
-
import type { ChangeFeedFrame, StreamChangesOptions, CredentialListResponse, CredentialMeta, CryptoKeyEncoding, CryptoKeyExport, CryptoKeyExternal, CryptoKeyType, CryptoRequest, CryptoResultByOp, DecideRequest, DecideResponse, DecisionModelsResponse, CredentialScope, DirectoryInspection, DirectoryTenant, DirectoryUser, ErasureReport, ErasureResult, Agent, AgentEvent, AgentList, CancelAgentResult, ClientOptions, ContinueOptions, RunOverrideOptions, EffectiveConfigResponse, RunFileInput, RunInboxDownload, AgentOverridableResponse, RetuneRunResponse, RunConfigResponse, RunPromptResponse, CreateSnapshotOptions, EnsureCodeAgentOptions, EnsureCodeAgentResult, EnsureHookDefOptions, EnsureHookDefResult, HookDefInput, HookDefOverlay, HookDefRowResponse, CreateDefOptions, ForkDefOptions, HookDefForkOverlay, ScheduleDefInput, ScheduleDefOverlay, ScheduleDefRowResponse, WebhookDefInput, WebhookDefOverlay, WebhookDefRowResponse, EnsureMcpServerOptions, EnsureMcpServerResult, HealthResponse, InterruptListResponse, InterruptStatus, ListUserAgentsOptions, WalkRunsPage, AckChannelOptions, AwaitChannelsOptions, BroadcastChannelsOptions, ChannelAckResult, ChannelAwaitResult, ChannelBroadcastResult, ChannelDescriptor, ChannelPeekResult, ListTeamChannelsOptions, PeekTeamChannelOptions, ReleaseTeamChannelOptions, TeamChannelPeekResult, TeamChannelReleaseResult, TeamChannelsResult, ChannelPublishResult, ChannelPurgeResult, ChannelReleaseResult, ChannelSubscribeResult, CreateChannelOptions, ListChannelsResponse, PeekChannelOptions, PublishChannelOptions, ReleaseChannelOptions, SetMemoryEntryOptions, SetMemoryEntryResponse, SubscribeChannelOptions, UpdateChannelOptions, LibraryAgentDefinition, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, ListUsersResponse, UserRecord, CreateUserBody, UpdateUserBody, MintedUserToken, ListUserTokensResponse, RunnableAgentsResponse, LLMChatOptions, LLMChatResponse, LLMChatStreamItem, LLMEmbeddingsOptions, LLMEmbeddingsResponse, MCPServerDefVerifyResult, MemoryEmbedStatsResponse, MemoryEntriesResponse, MemoryEntryResponse, MemoryBackfillResponse, MemoryPurgeResponse, MemoryReembedResponse, MemoryScopeIDsResponse, MemoryScopesResponse, MemorySearchInput, MemorySearchResponse, PauseResult, RetentionOverride, PersistentVolumesResponse, EphemeralVolumesResponse, ResolveInterruptOptions, ResumeResult, ResolverMatrix, CompactRunResult, RunTreeDeletion, ReplaySessionResult, CancelTurnResult, ReviewRunResult, RunBatchOptions, RunBatchResult, RunOptions, ConfiguredRun, ConfiguredRunPatch, RunStateStreamItem, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, StreamUserRunStatesOptions, SubstrateToolInput, SubstrateToolResponse, CreatedTeam, CreateTeamOptions, DefNamesResponse, ForkTeamOptions, ModelAliasesResponse, OperatorTokenDefNamesResponse, ProviderModelsResponse, RoutingResponse, ScheduleListResponse, SchedulePauseResult, ScheduleRunNowResult, ScheduleStateView, ListTeamsResponse, PromotedTeam, RetiredTeam, TeamBreakpoints, TeamDefDetail, TeamRunDetached, TeamRunTarget, TeamVerification, TeamDraft, TeamDraftVerification, TeamVersionList, TeamDiagram, TeamRunResult, PathToolInput, PathToolResponse, DocumentToolInput, DocumentToolResponse, HistoryToolInput, HistoryToolResponse, EvaluationSubmitInput, EvaluationGetInput, EvaluationListForRunInput, EvaluationListForDefInput, EvaluationAggregateInput, EvaluationInput, EvaluationSubmitResult, EvaluationRecord, EvaluationListForRunResult, EvaluationListForDefResult, EvaluationAggregate, TranscriptResponse, WhoamiResponse, UsageDimension, UsageReportResponse, TokenLimit, TokenLimitsResponse, ConfigResponse, SetTokenLimitRequest, AclBinding, AclBindingListResponse, AclCatalog, AclDefInput, AclDefListResponse, AclDefNamesResponse, AclDefRow, AclRule, AclValidateResult, AuthzDecision, AuthzEvaluationRequest, AuthzEvaluationsRequest, AuthzEvaluationsResponse, AuthzExplainResponse, AuthzSimulateRequest, AuthzSimulateResponse, CreateAclBindingBody, CreateGroupBody, GroupDetail, GroupEffective, GroupListResponse, GroupMemberChange, GroupMemberKind, GroupRow } from "./types.js";
|
|
29
|
+
import type { ChangeFeedFrame, StreamChangesOptions, CredentialListResponse, CredentialMeta, CryptoKeyEncoding, CryptoKeyExport, CryptoKeyExternal, CryptoKeyType, CryptoRequest, CryptoResultByOp, DecideRequest, DecideResponse, DecisionModelsResponse, CredentialScope, DirectoryInspection, DirectoryTenant, DirectoryUser, ErasureReport, ErasureResult, Agent, AgentEvent, AgentList, CancelAgentResult, ClientOptions, ContinueOptions, RunOverrideOptions, EffectiveConfigResponse, RunFileInput, RunInboxDownload, AgentOverridableResponse, RetuneRunResponse, RunConfigResponse, RunPromptResponse, CreateSnapshotOptions, EnsureCodeAgentOptions, EnsureCodeAgentResult, EnsureHookDefOptions, EnsureHookDefResult, HookDefInput, HookDefOverlay, HookDefRowResponse, CreateDefOptions, ForkDefOptions, HookDefForkOverlay, ScheduleDefInput, ScheduleDefOverlay, ScheduleDefRowResponse, WebhookDefInput, WebhookDefOverlay, WebhookDefRowResponse, EnsureMcpServerOptions, EnsureMcpServerResult, HealthResponse, InterruptListResponse, InterruptStatus, ListUserAgentsOptions, WalkRunsPage, AckChannelOptions, AwaitChannelsOptions, BroadcastChannelsOptions, ChannelAckResult, ChannelAwaitResult, ChannelBroadcastResult, ChannelDescriptor, ChannelPeekResult, ListTeamChannelsOptions, PeekTeamChannelOptions, ReleaseTeamChannelOptions, TeamChannelPeekResult, TeamChannelReleaseResult, TeamChannelsResult, ChannelPublishResult, ChannelPurgeResult, ChannelReleaseResult, ChannelSubscribeResult, CreateChannelOptions, ListChannelsResponse, PeekChannelOptions, PublishChannelOptions, ReleaseChannelOptions, SetMemoryEntryOptions, SetMemoryEntryResponse, SubscribeChannelOptions, UpdateChannelOptions, LibraryAgentDefinition, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, ListUsersResponse, UserRecord, CreateUserBody, UpdateUserBody, MintedUserToken, ListUserTokensResponse, UserIdentitiesResponse, AuthGateListResponse, CreateInviteBody, CreatedInvite, AuthInviteListResponse, CreatedPasswordReset, GateIdentitiesResponse, GateTenantStatus, RunnableAgentsResponse, LLMChatOptions, LLMChatResponse, LLMChatStreamItem, LLMEmbeddingsOptions, LLMEmbeddingsResponse, MCPServerDefVerifyResult, MemoryEmbedStatsResponse, MemoryEntriesResponse, MemoryEntryResponse, MemoryBackfillResponse, MemoryPurgeResponse, MemoryReembedResponse, MemoryScopeIDsResponse, MemoryScopesResponse, MemorySearchInput, MemorySearchResponse, PauseResult, RetentionOverride, PersistentVolumesResponse, EphemeralVolumesResponse, ResolveInterruptOptions, ResumeResult, ResolverMatrix, CompactRunResult, RunTreeDeletion, ReplaySessionResult, CancelTurnResult, ReviewRunResult, RunBatchOptions, RunBatchResult, RunOptions, ConfiguredRun, ConfiguredRunPatch, RunStateStreamItem, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, StreamUserRunStatesOptions, SubstrateToolInput, SubstrateToolResponse, CreatedTeam, CreateTeamOptions, DefNamesResponse, ForkTeamOptions, ModelAliasesResponse, OperatorTokenDefNamesResponse, ProviderModelsResponse, RoutingResponse, ScheduleListResponse, SchedulePauseResult, ScheduleRunNowResult, ScheduleStateView, ListTeamsResponse, PromotedTeam, RetiredTeam, TeamBreakpoints, TeamDefDetail, TeamRunDetached, TeamRunTarget, TeamVerification, TeamDraft, TeamDraftVerification, TeamVersionList, TeamDiagram, TeamRunResult, PathToolInput, PathToolResponse, DocumentToolInput, DocumentToolResponse, HistoryToolInput, HistoryToolResponse, EvaluationSubmitInput, EvaluationGetInput, EvaluationListForRunInput, EvaluationListForDefInput, EvaluationAggregateInput, EvaluationInput, EvaluationSubmitResult, EvaluationRecord, EvaluationListForRunResult, EvaluationListForDefResult, EvaluationAggregate, TranscriptResponse, WhoamiResponse, UsageDimension, UsageBucket, UsageReportResponse, TokenLimit, TokenLimitsResponse, ConfigResponse, SetTokenLimitRequest, AclBinding, AclBindingListResponse, AclCatalog, AclDefInput, AclDefListResponse, AclDefNamesResponse, AclDefRow, AclRule, AclValidateResult, AuthzDecision, AuthzEvaluationRequest, AuthzEvaluationsRequest, AuthzEvaluationsResponse, AuthzExplainResponse, AuthzSimulateRequest, AuthzSimulateResponse, CreateAclBindingBody, CreateGroupBody, GroupDetail, GroupEffective, GroupListResponse, GroupMemberChange, GroupMemberKind, GroupRow, AppClientConfig, AppClientEntry, ClientDefInput, ClientDefListResponse, ClientDefNamesResponse, ClientDefRecord, ClientDefRetireResult, ClientDefVerifyResult, DeviceLoginOptions, DeviceLoginResult } from "./types.js";
|
|
29
30
|
export declare class LoomcycleClient {
|
|
30
31
|
private ctx;
|
|
32
|
+
private readonly tokenProvider?;
|
|
33
|
+
private oauthHelper?;
|
|
34
|
+
private readonly rawFetch?;
|
|
31
35
|
constructor(opts?: ClientOptions);
|
|
36
|
+
/** The app sign-in helper (RFC FL) for this server: same base URL and
|
|
37
|
+
* fetch, never this client's bearer. See {@link LoomcycleOAuth}. */
|
|
38
|
+
get oauth(): LoomcycleOAuth;
|
|
32
39
|
/**
|
|
33
40
|
* Run an agent and stream events. Returns AsyncIterable<AgentEvent>;
|
|
34
41
|
* the iterator completes when the server closes the SSE stream.
|
|
@@ -501,7 +508,8 @@ export declare class LoomcycleClient {
|
|
|
501
508
|
signal?: AbortSignal;
|
|
502
509
|
}): Promise<WalkRunsPage>;
|
|
503
510
|
/** Aggregated token-usage + cost report (RFC AV). Group by any of
|
|
504
|
-
* tenant/user/provider/model/source/purpose over
|
|
511
|
+
* tenant/user/provider/model/source/purpose/tier/user_tier/agent/class over
|
|
512
|
+
* an optional time window, split into periods with `bucket`;
|
|
505
513
|
* group by `source` for the operator-vs-tenant split, by `purpose` to see
|
|
506
514
|
* what OCR and speech-to-text cost (a row's `audio_seconds` is the audio its
|
|
507
515
|
* speech-to-text calls transcribed). Tenant-scoped server-side: a
|
|
@@ -515,6 +523,16 @@ export declare class LoomcycleClient {
|
|
|
515
523
|
to?: string;
|
|
516
524
|
/** Super-admin tenant focus (?tenant=); ignored for a tenant principal. */
|
|
517
525
|
tenant?: string;
|
|
526
|
+
/** Split the window into UTC periods; each row then carries `period`.
|
|
527
|
+
* `hour` reaches only `detail_since` onward. */
|
|
528
|
+
bucket?: UsageBucket;
|
|
529
|
+
/** Keep the `top` largest values of the first `groupBy` dimension (by
|
|
530
|
+
* `topBy`, default cost); the rest fold into rows marked `other`. */
|
|
531
|
+
top?: number;
|
|
532
|
+
topBy?: "cost" | "tokens" | "calls";
|
|
533
|
+
/** Keep rows whose dimension is one of the values. `tenant` is not a
|
|
534
|
+
* filter: it is the `tenant` focus above. */
|
|
535
|
+
filters?: Partial<Record<Exclude<UsageDimension, "tenant">, string[]>>;
|
|
518
536
|
signal?: AbortSignal;
|
|
519
537
|
}): Promise<UsageReportResponse>;
|
|
520
538
|
/** Instance configuration: build identity, the feature matrix, and the live
|
|
@@ -635,12 +653,16 @@ export declare class LoomcycleClient {
|
|
|
635
653
|
* "isolated" confines the member to its own user scope. 409 on a duplicate
|
|
636
654
|
* (tenant, subject). */
|
|
637
655
|
createUser(body: CreateUserBody, opts?: {
|
|
656
|
+
tenant?: string;
|
|
638
657
|
signal?: AbortSignal;
|
|
639
658
|
}): Promise<UserRecord>;
|
|
640
659
|
/** Patch mutable fields on a user in the caller's own tenant. An omitted key
|
|
641
660
|
* leaves the column unchanged. Disabling a user (status:"disabled") also
|
|
642
|
-
* revokes its delegated tokens server-side.
|
|
661
|
+
* revokes its delegated tokens server-side. A pending user is activated by
|
|
662
|
+
* {@link approveUser}, not here (409 `use_approve`). Cross-tenant target →
|
|
663
|
+
* 404. */
|
|
643
664
|
updateUser(subject: string, body: UpdateUserBody, opts?: {
|
|
665
|
+
tenant?: string;
|
|
644
666
|
signal?: AbortSignal;
|
|
645
667
|
}): Promise<UserRecord>;
|
|
646
668
|
/** Delete the user identity row in the caller's own tenant (204). Its
|
|
@@ -648,8 +670,126 @@ export declare class LoomcycleClient {
|
|
|
648
670
|
* Owned data (runs / sessions / memory) is left intact — this is not
|
|
649
671
|
* erasure ({@link erasureExecute}). Cross-tenant target → 404. */
|
|
650
672
|
deleteUser(subject: string, opts?: {
|
|
673
|
+
tenant?: string;
|
|
674
|
+
signal?: AbortSignal;
|
|
675
|
+
}): Promise<void>;
|
|
676
|
+
/** Approve a user awaiting approval: `pending` → `active`
|
|
677
|
+
* (`POST /v1/_users/{subject}/approve`). A sign-in gate configured with
|
|
678
|
+
* `new_user.status: pending` provisions people who can sign in but are
|
|
679
|
+
* refused everything until this call. Returns the updated record. 409
|
|
680
|
+
* `not_pending` for a user that is not pending; 404 for an unknown or
|
|
681
|
+
* cross-tenant subject. An `auth:manage` credential may approve only a user
|
|
682
|
+
* at its floor (403 `target_beyond_floor`). */
|
|
683
|
+
approveUser(subject: string, opts?: {
|
|
684
|
+
tenant?: string;
|
|
685
|
+
signal?: AbortSignal;
|
|
686
|
+
}): Promise<UserRecord>;
|
|
687
|
+
/** The identity-provider accounts a user signs in with
|
|
688
|
+
* (`GET /v1/_users/{subject}/identities`): one row per linked gate, never a
|
|
689
|
+
* secret. A user reads its own; a tenant operator or an admin reads any in
|
|
690
|
+
* the tenant. Password-gate logins, with whether a password and an
|
|
691
|
+
* authenticator are set, are {@link listGateIdentities}. */
|
|
692
|
+
listUserIdentities(subject: string, opts?: {
|
|
693
|
+
tenant?: string;
|
|
694
|
+
signal?: AbortSignal;
|
|
695
|
+
}): Promise<UserIdentitiesResponse>;
|
|
696
|
+
/** Unlink one identity-provider account from a user
|
|
697
|
+
* (`DELETE /v1/_users/{subject}/identities/{gate}`, 204). Sessions made
|
|
698
|
+
* through it end on their next request. For a tenant operator or an admin;
|
|
699
|
+
* a user unlinking its own account must do it from a recently
|
|
700
|
+
* signed-in Web UI session, so a bearer is refused (403
|
|
701
|
+
* `link_needs_own_session`). A password gate's login is removed with
|
|
702
|
+
* {@link removeGateCredentials} instead (409 `password_gate`). 404
|
|
703
|
+
* `identity_not_found` when no account of that gate is linked. */
|
|
704
|
+
unlinkUserIdentity(subject: string, gate: string, opts?: {
|
|
705
|
+
tenant?: string;
|
|
706
|
+
signal?: AbortSignal;
|
|
707
|
+
}): Promise<void>;
|
|
708
|
+
/** Sign in through the server's device gate (RFC 8628): shows the person a
|
|
709
|
+
* code through `onPrompt`, polls until they approve it in a signed-in Web
|
|
710
|
+
* UI session (`/ui/device`), and returns the token issued to them. The
|
|
711
|
+
* token is the approver's own member token, at most the device gate's
|
|
712
|
+
* `token_ttl` and never past the approving session or the credential it
|
|
713
|
+
* was made from. Sends no bearer: a client constructed without one can
|
|
714
|
+
* call it. Throws {@link DeviceLoginError} when the login is denied,
|
|
715
|
+
* expires, or the server refuses it; 404 `device_login_disabled` when no
|
|
716
|
+
* device gate is configured. */
|
|
717
|
+
deviceLogin(opts: DeviceLoginOptions): Promise<DeviceLoginResult>;
|
|
718
|
+
/** Every configured sign-in gate (`GET /v1/_authgate`): kind, label,
|
|
719
|
+
* whether a browser can sign in through it, its identity map and an
|
|
720
|
+
* external gate's key fingerprint and removal timing — never key
|
|
721
|
+
* material. **Admin token only.** `probe: true` also fetches each
|
|
722
|
+
* external gate's discovery document (bounded) and compares it with the
|
|
723
|
+
* registration. */
|
|
724
|
+
listAuthGates(opts?: {
|
|
725
|
+
probe?: boolean;
|
|
726
|
+
signal?: AbortSignal;
|
|
727
|
+
}): Promise<AuthGateListResponse>;
|
|
728
|
+
/** Invite a person to sign in through a password gate
|
|
729
|
+
* (`POST /v1/_authgate/{gate}/invites`). Without `subject` the invite
|
|
730
|
+
* makes a new user, at the gate's new-user floor unless the gate is
|
|
731
|
+
* trusted; with one it gives that existing user a login (409
|
|
732
|
+
* `credential_exists` if it has one — issue a reset instead). Returns the
|
|
733
|
+
* one-time link, shown once. */
|
|
734
|
+
createInvite(gate: string, body: CreateInviteBody, opts?: {
|
|
735
|
+
tenant?: string;
|
|
736
|
+
signal?: AbortSignal;
|
|
737
|
+
}): Promise<CreatedInvite>;
|
|
738
|
+
/** A password gate's invites in the tenant, live and past
|
|
739
|
+
* (`GET /v1/_authgate/{gate}/invites`), never their codes. An admin naming
|
|
740
|
+
* no tenant lists every tenant's. */
|
|
741
|
+
listInvites(gate: string, opts?: {
|
|
742
|
+
tenant?: string;
|
|
743
|
+
signal?: AbortSignal;
|
|
744
|
+
}): Promise<AuthInviteListResponse>;
|
|
745
|
+
/** Revoke a live invite (`DELETE /v1/_authgate/{gate}/invites/{id}`, 204).
|
|
746
|
+
* 409 `invite_not_live` when it was already redeemed, revoked or expired;
|
|
747
|
+
* another tenant's invite is a 404. */
|
|
748
|
+
revokeInvite(gate: string, inviteId: string, opts?: {
|
|
749
|
+
tenant?: string;
|
|
750
|
+
signal?: AbortSignal;
|
|
751
|
+
}): Promise<void>;
|
|
752
|
+
/** Issue a one-time link that sets a new password for a user with a login
|
|
753
|
+
* at the gate (`POST /v1/_authgate/{gate}/resets`). A user with an
|
|
754
|
+
* authenticator still needs a code from it. Completing the reset ends the
|
|
755
|
+
* user's sessions from the old password and retires its delegated tokens.
|
|
756
|
+
* 404 `login_not_found` when the user has no login at the gate. */
|
|
757
|
+
createPasswordReset(gate: string, subject: string, opts?: {
|
|
758
|
+
tenant?: string;
|
|
759
|
+
signal?: AbortSignal;
|
|
760
|
+
}): Promise<CreatedPasswordReset>;
|
|
761
|
+
/** Remove a user's password, authenticator and recovery codes at a gate
|
|
762
|
+
* (`DELETE /v1/_authgate/{gate}/credentials/{subject}`, 204). The login
|
|
763
|
+
* stays reserved, so a later {@link createPasswordReset} restores access. */
|
|
764
|
+
removeGateCredentials(gate: string, subject: string, opts?: {
|
|
765
|
+
tenant?: string;
|
|
651
766
|
signal?: AbortSignal;
|
|
652
767
|
}): Promise<void>;
|
|
768
|
+
/** Change the login a user signs in with at a gate
|
|
769
|
+
* (`PATCH /v1/_authgate/{gate}/logins/{subject}`). The subject does not
|
|
770
|
+
* change, so nothing the user owns moves. Returns the normalised login;
|
|
771
|
+
* 409 `login_taken` when another user has it. */
|
|
772
|
+
renameGateLogin(gate: string, subject: string, login: string, opts?: {
|
|
773
|
+
tenant?: string;
|
|
774
|
+
signal?: AbortSignal;
|
|
775
|
+
}): Promise<{
|
|
776
|
+
subject: string;
|
|
777
|
+
login: string;
|
|
778
|
+
}>;
|
|
779
|
+
/** A user's logins at every gate, and whether it has a password and a
|
|
780
|
+
* confirmed authenticator (`GET /v1/_authgate/{gate}/identities`) — never
|
|
781
|
+
* a secret. `gate` must name a password gate. */
|
|
782
|
+
listGateIdentities(gate: string, subject: string, opts?: {
|
|
783
|
+
tenant?: string;
|
|
784
|
+
signal?: AbortSignal;
|
|
785
|
+
}): Promise<GateIdentitiesResponse>;
|
|
786
|
+
/** Whether the tenant has, or ever had, a user and whether an owner invite
|
|
787
|
+
* is live (`GET /v1/_authgate/{gate}/tenant-status`) — what deciding on an
|
|
788
|
+
* owner invite needs, and nothing about who the users are. */
|
|
789
|
+
getGateTenantStatus(gate: string, opts?: {
|
|
790
|
+
tenant?: string;
|
|
791
|
+
signal?: AbortSignal;
|
|
792
|
+
}): Promise<GateTenantStatus>;
|
|
653
793
|
/** Mint a bearer token for one of the caller's own ACTIVE users. The granted
|
|
654
794
|
* scopes are DERIVED server-side from the member's access_mode (never sent),
|
|
655
795
|
* and the plaintext `token` is returned exactly ONCE — store it now, it is
|
|
@@ -1265,6 +1405,59 @@ export declare class LoomcycleClient {
|
|
|
1265
1405
|
volumeDef(input: SubstrateToolInput, opts?: {
|
|
1266
1406
|
signal?: AbortSignal;
|
|
1267
1407
|
}): Promise<SubstrateToolResponse>;
|
|
1408
|
+
/** The ClientDef op dispatch, as sent: `{op, name?, def_id?, client?,
|
|
1409
|
+
* description?, tenant?, content_sha256?}`. The typed methods below are
|
|
1410
|
+
* the usual way in. */
|
|
1411
|
+
clientDef(input: ClientDefInput, opts?: {
|
|
1412
|
+
signal?: AbortSignal;
|
|
1413
|
+
}): Promise<unknown>;
|
|
1414
|
+
/** Register an app (`op: "create"`). Its redirect URIs and origins are
|
|
1415
|
+
* checked as strictly as a config client's. `tenant` is for an admin: the
|
|
1416
|
+
* tenant whose people it signs in, "" (the default) for every tenant. */
|
|
1417
|
+
createClientDef(name: string, client: AppClientConfig, opts?: {
|
|
1418
|
+
description?: string;
|
|
1419
|
+
tenant?: string;
|
|
1420
|
+
signal?: AbortSignal;
|
|
1421
|
+
}): Promise<ClientDefRecord>;
|
|
1422
|
+
/** Change an app (`op: "update"`): a new version, whole. Codes issued
|
|
1423
|
+
* under the old version that were not exchanged yet stop working. */
|
|
1424
|
+
updateClientDef(name: string, client: AppClientConfig, opts?: {
|
|
1425
|
+
description?: string;
|
|
1426
|
+
signal?: AbortSignal;
|
|
1427
|
+
}): Promise<ClientDefRecord>;
|
|
1428
|
+
/** One app by name (a config client too) or one version by `def_id`
|
|
1429
|
+
* (`op: "get"`). */
|
|
1430
|
+
getClientDef(ref: {
|
|
1431
|
+
name?: string;
|
|
1432
|
+
def_id?: string;
|
|
1433
|
+
}, opts?: {
|
|
1434
|
+
signal?: AbortSignal;
|
|
1435
|
+
}): Promise<AppClientEntry>;
|
|
1436
|
+
/** The apps the caller may see, config ones included (`op: "list"`). An
|
|
1437
|
+
* admin may focus one tenant with `tenant` ("" = the every-tenant ones). */
|
|
1438
|
+
listClientDefs(opts?: {
|
|
1439
|
+
tenant?: string;
|
|
1440
|
+
signal?: AbortSignal;
|
|
1441
|
+
}): Promise<ClientDefListResponse>;
|
|
1442
|
+
/** Take an app out of service (`op: "retire"`): its unused codes are
|
|
1443
|
+
* voided, its sign-ins revoked and their access tokens retired. The name
|
|
1444
|
+
* is not reusable. */
|
|
1445
|
+
retireClientDef(name: string, opts?: {
|
|
1446
|
+
signal?: AbortSignal;
|
|
1447
|
+
}): Promise<ClientDefRetireResult>;
|
|
1448
|
+
/** Whether an app is deployed and, with `contentSha256`, whether it matches
|
|
1449
|
+
* (`op: "verify"`). An absent app answers `deployed: false`. */
|
|
1450
|
+
verifyClientDef(name: string, opts?: {
|
|
1451
|
+
contentSha256?: string;
|
|
1452
|
+
signal?: AbortSignal;
|
|
1453
|
+
}): Promise<ClientDefVerifyResult>;
|
|
1454
|
+
/** The apps the caller may see, by name (`GET /v1/_clientdef/names`). An
|
|
1455
|
+
* admin may focus one tenant with `tenant`; unlike the other families'
|
|
1456
|
+
* name lists, `""` is a focus too (the every-tenant apps). */
|
|
1457
|
+
listClientDefNames(opts?: {
|
|
1458
|
+
tenant?: string;
|
|
1459
|
+
signal?: AbortSignal;
|
|
1460
|
+
}): Promise<ClientDefNamesResponse>;
|
|
1268
1461
|
private listDefNames;
|
|
1269
1462
|
/** Every AgentDef name (`GET /v1/_agentdef/names`). */
|
|
1270
1463
|
listAgentDefNames(opts?: {
|
package/dist/client.js
CHANGED
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
* via fetch-helpers.ts:raiseFromResponse — see README.md for the
|
|
24
24
|
* full mapping table.
|
|
25
25
|
*/
|
|
26
|
-
import { authHeaders, deleteRequest, deleteJSON, jsonFetch, patchJSON, postJSON, putJSON, raiseFromResponse, } from "./fetch-helpers.js";
|
|
26
|
+
import { authHeaders, authorizedFetch, deleteRequest, deleteJSON, jsonFetch, patchJSON, postJSON, putJSON, raiseFromResponse, } from "./fetch-helpers.js";
|
|
27
27
|
import { parseSSE } from "./stream.js";
|
|
28
28
|
import { InteractiveSession } from "./interactive.js";
|
|
29
|
+
import { LoomcycleOAuth } from "./oauth.js";
|
|
30
|
+
import { deviceLogin } from "./device-login.js";
|
|
29
31
|
import { ClientToolHost, clientToolsURL, } from "./client-tools.js";
|
|
30
32
|
/** samplingToWire maps the camelCase SamplingOptions to the snake_case wire
|
|
31
33
|
* object, omitting unset fields so they inherit the agent's value. */
|
|
@@ -217,13 +219,30 @@ function applyOverridesToWire(body, opts) {
|
|
|
217
219
|
}
|
|
218
220
|
export class LoomcycleClient {
|
|
219
221
|
ctx;
|
|
222
|
+
tokenProvider;
|
|
223
|
+
oauthHelper;
|
|
224
|
+
rawFetch;
|
|
220
225
|
constructor(opts = {}) {
|
|
226
|
+
if (opts.authToken && opts.tokenProvider) {
|
|
227
|
+
throw new TypeError("LoomcycleClient: pass authToken or tokenProvider, not both");
|
|
228
|
+
}
|
|
229
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
230
|
+
this.rawFetch = opts.fetch;
|
|
231
|
+
this.tokenProvider = opts.tokenProvider;
|
|
221
232
|
this.ctx = {
|
|
222
233
|
baseUrl: (opts.baseUrl ?? "http://127.0.0.1:8787").replace(/\/$/, ""),
|
|
223
234
|
authToken: opts.authToken,
|
|
224
|
-
|
|
235
|
+
// With a provider, ctx.authToken stays unset and the wrapper sets the
|
|
236
|
+
// header on every request, so no method needs to know.
|
|
237
|
+
fetchImpl: opts.tokenProvider ? authorizedFetch(fetchImpl, opts.tokenProvider) : fetchImpl,
|
|
225
238
|
};
|
|
226
239
|
}
|
|
240
|
+
/** The app sign-in helper (RFC FL) for this server: same base URL and
|
|
241
|
+
* fetch, never this client's bearer. See {@link LoomcycleOAuth}. */
|
|
242
|
+
get oauth() {
|
|
243
|
+
this.oauthHelper ??= new LoomcycleOAuth({ baseUrl: this.ctx.baseUrl, fetch: this.rawFetch });
|
|
244
|
+
return this.oauthHelper;
|
|
245
|
+
}
|
|
227
246
|
// ---- Run lifecycle ----
|
|
228
247
|
/**
|
|
229
248
|
* Run an agent and stream events. Returns AsyncIterable<AgentEvent>;
|
|
@@ -841,7 +860,8 @@ export class LoomcycleClient {
|
|
|
841
860
|
return { agents: resp.agents ?? [], next_cursor: resp.next_cursor ?? "" };
|
|
842
861
|
}
|
|
843
862
|
/** Aggregated token-usage + cost report (RFC AV). Group by any of
|
|
844
|
-
* tenant/user/provider/model/source/purpose over
|
|
863
|
+
* tenant/user/provider/model/source/purpose/tier/user_tier/agent/class over
|
|
864
|
+
* an optional time window, split into periods with `bucket`;
|
|
845
865
|
* group by `source` for the operator-vs-tenant split, by `purpose` to see
|
|
846
866
|
* what OCR and speech-to-text cost (a row's `audio_seconds` is the audio its
|
|
847
867
|
* speech-to-text calls transcribed). Tenant-scoped server-side: a
|
|
@@ -857,6 +877,16 @@ export class LoomcycleClient {
|
|
|
857
877
|
params.set("to", opts.to);
|
|
858
878
|
if (opts?.tenant)
|
|
859
879
|
params.set("tenant", opts.tenant);
|
|
880
|
+
if (opts?.bucket)
|
|
881
|
+
params.set("bucket", opts.bucket);
|
|
882
|
+
if (opts?.top)
|
|
883
|
+
params.set("top", String(opts.top));
|
|
884
|
+
if (opts?.topBy)
|
|
885
|
+
params.set("top_by", opts.topBy);
|
|
886
|
+
for (const [dim, values] of Object.entries(opts?.filters ?? {})) {
|
|
887
|
+
if (values && values.length > 0)
|
|
888
|
+
params.set(dim, values.join(","));
|
|
889
|
+
}
|
|
860
890
|
const q = params.toString() ? `?${params.toString()}` : "";
|
|
861
891
|
return jsonFetch(this.ctx, `/v1/_usage${q}`, opts);
|
|
862
892
|
}
|
|
@@ -981,28 +1011,151 @@ export class LoomcycleClient {
|
|
|
981
1011
|
//
|
|
982
1012
|
// A tenant operator (substrate:tenant) manages the first-class users in its
|
|
983
1013
|
// OWN tenant and mints/lists/revokes bearer tokens for them. The tenant is
|
|
984
|
-
//
|
|
985
|
-
//
|
|
1014
|
+
// server-derived from the authenticated principal. `opts.tenant` (sent as
|
|
1015
|
+
// ?tenant=) names another only for an admin token, and for an `auth:manage`
|
|
1016
|
+
// credential, which must name one (400 `tenant_required` otherwise); a
|
|
1017
|
+
// tenant operator always acts in its own tenant. Requires a persistent store
|
|
986
1018
|
// (503 otherwise).
|
|
987
1019
|
/** Register a first-class user in the caller's own tenant. `access_mode`
|
|
988
1020
|
* defaults to "tenant" (collaborates on tenant-shared primitives);
|
|
989
1021
|
* "isolated" confines the member to its own user scope. 409 on a duplicate
|
|
990
1022
|
* (tenant, subject). */
|
|
991
1023
|
async createUser(body, opts) {
|
|
992
|
-
return postJSON(this.ctx, "/v1/_users", body,
|
|
1024
|
+
return postJSON(this.ctx, this.aclPath("/v1/_users", opts?.tenant), body, {
|
|
1025
|
+
signal: opts?.signal,
|
|
1026
|
+
});
|
|
993
1027
|
}
|
|
994
1028
|
/** Patch mutable fields on a user in the caller's own tenant. An omitted key
|
|
995
1029
|
* leaves the column unchanged. Disabling a user (status:"disabled") also
|
|
996
|
-
* revokes its delegated tokens server-side.
|
|
1030
|
+
* revokes its delegated tokens server-side. A pending user is activated by
|
|
1031
|
+
* {@link approveUser}, not here (409 `use_approve`). Cross-tenant target →
|
|
1032
|
+
* 404. */
|
|
997
1033
|
async updateUser(subject, body, opts) {
|
|
998
|
-
return patchJSON(this.ctx, `/v1/_users/${encodeURIComponent(subject)}`, body, opts);
|
|
1034
|
+
return patchJSON(this.ctx, this.aclPath(`/v1/_users/${encodeURIComponent(subject)}`, opts?.tenant), body, { signal: opts?.signal });
|
|
999
1035
|
}
|
|
1000
1036
|
/** Delete the user identity row in the caller's own tenant (204). Its
|
|
1001
1037
|
* delegated tokens are retired first so the bearer cannot outlive the row.
|
|
1002
1038
|
* Owned data (runs / sessions / memory) is left intact — this is not
|
|
1003
1039
|
* erasure ({@link erasureExecute}). Cross-tenant target → 404. */
|
|
1004
1040
|
async deleteUser(subject, opts) {
|
|
1005
|
-
return deleteRequest(this.ctx, `/v1/_users/${encodeURIComponent(subject)}`, opts);
|
|
1041
|
+
return deleteRequest(this.ctx, this.aclPath(`/v1/_users/${encodeURIComponent(subject)}`, opts?.tenant), { signal: opts?.signal });
|
|
1042
|
+
}
|
|
1043
|
+
/** Approve a user awaiting approval: `pending` → `active`
|
|
1044
|
+
* (`POST /v1/_users/{subject}/approve`). A sign-in gate configured with
|
|
1045
|
+
* `new_user.status: pending` provisions people who can sign in but are
|
|
1046
|
+
* refused everything until this call. Returns the updated record. 409
|
|
1047
|
+
* `not_pending` for a user that is not pending; 404 for an unknown or
|
|
1048
|
+
* cross-tenant subject. An `auth:manage` credential may approve only a user
|
|
1049
|
+
* at its floor (403 `target_beyond_floor`). */
|
|
1050
|
+
async approveUser(subject, opts) {
|
|
1051
|
+
return postJSON(this.ctx, this.aclPath(`/v1/_users/${encodeURIComponent(subject)}/approve`, opts?.tenant), undefined, { signal: opts?.signal });
|
|
1052
|
+
}
|
|
1053
|
+
/** The identity-provider accounts a user signs in with
|
|
1054
|
+
* (`GET /v1/_users/{subject}/identities`): one row per linked gate, never a
|
|
1055
|
+
* secret. A user reads its own; a tenant operator or an admin reads any in
|
|
1056
|
+
* the tenant. Password-gate logins, with whether a password and an
|
|
1057
|
+
* authenticator are set, are {@link listGateIdentities}. */
|
|
1058
|
+
async listUserIdentities(subject, opts) {
|
|
1059
|
+
return jsonFetch(this.ctx, this.aclPath(`/v1/_users/${encodeURIComponent(subject)}/identities`, opts?.tenant), opts);
|
|
1060
|
+
}
|
|
1061
|
+
/** Unlink one identity-provider account from a user
|
|
1062
|
+
* (`DELETE /v1/_users/{subject}/identities/{gate}`, 204). Sessions made
|
|
1063
|
+
* through it end on their next request. For a tenant operator or an admin;
|
|
1064
|
+
* a user unlinking its own account must do it from a recently
|
|
1065
|
+
* signed-in Web UI session, so a bearer is refused (403
|
|
1066
|
+
* `link_needs_own_session`). A password gate's login is removed with
|
|
1067
|
+
* {@link removeGateCredentials} instead (409 `password_gate`). 404
|
|
1068
|
+
* `identity_not_found` when no account of that gate is linked. */
|
|
1069
|
+
async unlinkUserIdentity(subject, gate, opts) {
|
|
1070
|
+
return deleteRequest(this.ctx, this.aclPath(`/v1/_users/${encodeURIComponent(subject)}/identities/${encodeURIComponent(gate)}`, opts?.tenant), { signal: opts?.signal });
|
|
1071
|
+
}
|
|
1072
|
+
/** Sign in through the server's device gate (RFC 8628): shows the person a
|
|
1073
|
+
* code through `onPrompt`, polls until they approve it in a signed-in Web
|
|
1074
|
+
* UI session (`/ui/device`), and returns the token issued to them. The
|
|
1075
|
+
* token is the approver's own member token, at most the device gate's
|
|
1076
|
+
* `token_ttl` and never past the approving session or the credential it
|
|
1077
|
+
* was made from. Sends no bearer: a client constructed without one can
|
|
1078
|
+
* call it. Throws {@link DeviceLoginError} when the login is denied,
|
|
1079
|
+
* expires, or the server refuses it; 404 `device_login_disabled` when no
|
|
1080
|
+
* device gate is configured. */
|
|
1081
|
+
async deviceLogin(opts) {
|
|
1082
|
+
return deviceLogin(this.ctx, opts);
|
|
1083
|
+
}
|
|
1084
|
+
// ---- RFC FA: sign-in gates + the password gate's management API ----
|
|
1085
|
+
//
|
|
1086
|
+
// Invitations, reset links, credential removal, login renames and a user's
|
|
1087
|
+
// logins at a password gate, addressed by the gate's configured name. None
|
|
1088
|
+
// takes a password: the person sets their own through the link. A tenant
|
|
1089
|
+
// operator manages its own tenant, an admin any (`opts.tenant`, defaulting
|
|
1090
|
+
// to its own), and an `auth:manage` credential any tenant it names — it
|
|
1091
|
+
// must name one (400 `tenant_required`) and is held to the gate's new-user
|
|
1092
|
+
// floor. A gate that is not a password gate is a 404 `gate_not_found`.
|
|
1093
|
+
// Requires a persistent store (503 otherwise).
|
|
1094
|
+
/** Every configured sign-in gate (`GET /v1/_authgate`): kind, label,
|
|
1095
|
+
* whether a browser can sign in through it, its identity map and an
|
|
1096
|
+
* external gate's key fingerprint and removal timing — never key
|
|
1097
|
+
* material. **Admin token only.** `probe: true` also fetches each
|
|
1098
|
+
* external gate's discovery document (bounded) and compares it with the
|
|
1099
|
+
* registration. */
|
|
1100
|
+
async listAuthGates(opts) {
|
|
1101
|
+
const q = opts?.probe ? "?probe=1" : "";
|
|
1102
|
+
return jsonFetch(this.ctx, `/v1/_authgate${q}`, opts);
|
|
1103
|
+
}
|
|
1104
|
+
/** Invite a person to sign in through a password gate
|
|
1105
|
+
* (`POST /v1/_authgate/{gate}/invites`). Without `subject` the invite
|
|
1106
|
+
* makes a new user, at the gate's new-user floor unless the gate is
|
|
1107
|
+
* trusted; with one it gives that existing user a login (409
|
|
1108
|
+
* `credential_exists` if it has one — issue a reset instead). Returns the
|
|
1109
|
+
* one-time link, shown once. */
|
|
1110
|
+
async createInvite(gate, body, opts) {
|
|
1111
|
+
return postJSON(this.ctx, `/v1/_authgate/${encodeURIComponent(gate)}/invites`, withTenant(body, opts?.tenant), { signal: opts?.signal });
|
|
1112
|
+
}
|
|
1113
|
+
/** A password gate's invites in the tenant, live and past
|
|
1114
|
+
* (`GET /v1/_authgate/{gate}/invites`), never their codes. An admin naming
|
|
1115
|
+
* no tenant lists every tenant's. */
|
|
1116
|
+
async listInvites(gate, opts) {
|
|
1117
|
+
return jsonFetch(this.ctx, this.aclPath(`/v1/_authgate/${encodeURIComponent(gate)}/invites`, opts?.tenant), opts);
|
|
1118
|
+
}
|
|
1119
|
+
/** Revoke a live invite (`DELETE /v1/_authgate/{gate}/invites/{id}`, 204).
|
|
1120
|
+
* 409 `invite_not_live` when it was already redeemed, revoked or expired;
|
|
1121
|
+
* another tenant's invite is a 404. */
|
|
1122
|
+
async revokeInvite(gate, inviteId, opts) {
|
|
1123
|
+
return deleteRequest(this.ctx, this.aclPath(`/v1/_authgate/${encodeURIComponent(gate)}/invites/${encodeURIComponent(inviteId)}`, opts?.tenant), { signal: opts?.signal });
|
|
1124
|
+
}
|
|
1125
|
+
/** Issue a one-time link that sets a new password for a user with a login
|
|
1126
|
+
* at the gate (`POST /v1/_authgate/{gate}/resets`). A user with an
|
|
1127
|
+
* authenticator still needs a code from it. Completing the reset ends the
|
|
1128
|
+
* user's sessions from the old password and retires its delegated tokens.
|
|
1129
|
+
* 404 `login_not_found` when the user has no login at the gate. */
|
|
1130
|
+
async createPasswordReset(gate, subject, opts) {
|
|
1131
|
+
return postJSON(this.ctx, `/v1/_authgate/${encodeURIComponent(gate)}/resets`, withTenant({ subject }, opts?.tenant), { signal: opts?.signal });
|
|
1132
|
+
}
|
|
1133
|
+
/** Remove a user's password, authenticator and recovery codes at a gate
|
|
1134
|
+
* (`DELETE /v1/_authgate/{gate}/credentials/{subject}`, 204). The login
|
|
1135
|
+
* stays reserved, so a later {@link createPasswordReset} restores access. */
|
|
1136
|
+
async removeGateCredentials(gate, subject, opts) {
|
|
1137
|
+
return deleteRequest(this.ctx, this.aclPath(`/v1/_authgate/${encodeURIComponent(gate)}/credentials/${encodeURIComponent(subject)}`, opts?.tenant), { signal: opts?.signal });
|
|
1138
|
+
}
|
|
1139
|
+
/** Change the login a user signs in with at a gate
|
|
1140
|
+
* (`PATCH /v1/_authgate/{gate}/logins/{subject}`). The subject does not
|
|
1141
|
+
* change, so nothing the user owns moves. Returns the normalised login;
|
|
1142
|
+
* 409 `login_taken` when another user has it. */
|
|
1143
|
+
async renameGateLogin(gate, subject, login, opts) {
|
|
1144
|
+
return patchJSON(this.ctx, `/v1/_authgate/${encodeURIComponent(gate)}/logins/${encodeURIComponent(subject)}`, withTenant({ login }, opts?.tenant), { signal: opts?.signal });
|
|
1145
|
+
}
|
|
1146
|
+
/** A user's logins at every gate, and whether it has a password and a
|
|
1147
|
+
* confirmed authenticator (`GET /v1/_authgate/{gate}/identities`) — never
|
|
1148
|
+
* a secret. `gate` must name a password gate. */
|
|
1149
|
+
async listGateIdentities(gate, subject, opts) {
|
|
1150
|
+
return jsonFetch(this.ctx, this.aclPath(`/v1/_authgate/${encodeURIComponent(gate)}/identities`, opts?.tenant, {
|
|
1151
|
+
subject,
|
|
1152
|
+
}), opts);
|
|
1153
|
+
}
|
|
1154
|
+
/** Whether the tenant has, or ever had, a user and whether an owner invite
|
|
1155
|
+
* is live (`GET /v1/_authgate/{gate}/tenant-status`) — what deciding on an
|
|
1156
|
+
* owner invite needs, and nothing about who the users are. */
|
|
1157
|
+
async getGateTenantStatus(gate, opts) {
|
|
1158
|
+
return jsonFetch(this.ctx, this.aclPath(`/v1/_authgate/${encodeURIComponent(gate)}/tenant-status`, opts?.tenant), opts);
|
|
1006
1159
|
}
|
|
1007
1160
|
/** Mint a bearer token for one of the caller's own ACTIVE users. The granted
|
|
1008
1161
|
* scopes are DERIVED server-side from the member's access_mode (never sent),
|
|
@@ -1196,7 +1349,11 @@ export class LoomcycleClient {
|
|
|
1196
1349
|
* `.close()` to stop. Uses the global WebSocket (browsers, Node 22+); pass
|
|
1197
1350
|
* `WebSocketImpl` (e.g. the `ws` package) on older Node. */
|
|
1198
1351
|
connectClientTools(opts) {
|
|
1199
|
-
|
|
1352
|
+
// A provider is asked on every (re)connect, so a host outliving one
|
|
1353
|
+
// access token reconnects with the next.
|
|
1354
|
+
const provider = this.tokenProvider;
|
|
1355
|
+
const auth = provider ? () => provider.getToken() : this.ctx.authToken;
|
|
1356
|
+
const host = new ClientToolHost(clientToolsURL(this.ctx.baseUrl), auth, opts);
|
|
1200
1357
|
host.start();
|
|
1201
1358
|
return host;
|
|
1202
1359
|
}
|
|
@@ -1833,6 +1990,73 @@ export class LoomcycleClient {
|
|
|
1833
1990
|
async volumeDef(input, opts) {
|
|
1834
1991
|
return postJSON(this.ctx, "/v1/_volumedef", input, opts);
|
|
1835
1992
|
}
|
|
1993
|
+
// ---- App clients (RFC FL ClientDef) ----
|
|
1994
|
+
//
|
|
1995
|
+
// The apps that sign people in through this server's gates, registered at
|
|
1996
|
+
// runtime beside the config's `auth.clients`. A tenant operator
|
|
1997
|
+
// (substrate:tenant) registers its own tenant's; an admin any tenant's, or
|
|
1998
|
+
// one for every tenant. Config clients are read only here, and another
|
|
1999
|
+
// tenant's client answers as not found. A refusal raises
|
|
2000
|
+
// SubstrateToolRefusedError. Each method posts to `POST /v1/_clientdef`.
|
|
2001
|
+
/** The ClientDef op dispatch, as sent: `{op, name?, def_id?, client?,
|
|
2002
|
+
* description?, tenant?, content_sha256?}`. The typed methods below are
|
|
2003
|
+
* the usual way in. */
|
|
2004
|
+
async clientDef(input, opts) {
|
|
2005
|
+
return postJSON(this.ctx, "/v1/_clientdef", input, opts);
|
|
2006
|
+
}
|
|
2007
|
+
/** Register an app (`op: "create"`). Its redirect URIs and origins are
|
|
2008
|
+
* checked as strictly as a config client's. `tenant` is for an admin: the
|
|
2009
|
+
* tenant whose people it signs in, "" (the default) for every tenant. */
|
|
2010
|
+
async createClientDef(name, client, opts) {
|
|
2011
|
+
const input = { op: "create", name, client };
|
|
2012
|
+
if (opts?.description !== undefined)
|
|
2013
|
+
input.description = opts.description;
|
|
2014
|
+
if (opts?.tenant !== undefined)
|
|
2015
|
+
input.tenant = opts.tenant;
|
|
2016
|
+
return (await this.clientDef(input, signalOpts(opts)));
|
|
2017
|
+
}
|
|
2018
|
+
/** Change an app (`op: "update"`): a new version, whole. Codes issued
|
|
2019
|
+
* under the old version that were not exchanged yet stop working. */
|
|
2020
|
+
async updateClientDef(name, client, opts) {
|
|
2021
|
+
const input = { op: "update", name, client };
|
|
2022
|
+
if (opts?.description !== undefined)
|
|
2023
|
+
input.description = opts.description;
|
|
2024
|
+
return (await this.clientDef(input, signalOpts(opts)));
|
|
2025
|
+
}
|
|
2026
|
+
/** One app by name (a config client too) or one version by `def_id`
|
|
2027
|
+
* (`op: "get"`). */
|
|
2028
|
+
async getClientDef(ref, opts) {
|
|
2029
|
+
return (await this.clientDef({ op: "get", ...ref }, opts));
|
|
2030
|
+
}
|
|
2031
|
+
/** The apps the caller may see, config ones included (`op: "list"`). An
|
|
2032
|
+
* admin may focus one tenant with `tenant` ("" = the every-tenant ones). */
|
|
2033
|
+
async listClientDefs(opts) {
|
|
2034
|
+
const input = { op: "list" };
|
|
2035
|
+
if (opts?.tenant !== undefined)
|
|
2036
|
+
input.tenant = opts.tenant;
|
|
2037
|
+
return (await this.clientDef(input, signalOpts(opts)));
|
|
2038
|
+
}
|
|
2039
|
+
/** Take an app out of service (`op: "retire"`): its unused codes are
|
|
2040
|
+
* voided, its sign-ins revoked and their access tokens retired. The name
|
|
2041
|
+
* is not reusable. */
|
|
2042
|
+
async retireClientDef(name, opts) {
|
|
2043
|
+
return (await this.clientDef({ op: "retire", name }, opts));
|
|
2044
|
+
}
|
|
2045
|
+
/** Whether an app is deployed and, with `contentSha256`, whether it matches
|
|
2046
|
+
* (`op: "verify"`). An absent app answers `deployed: false`. */
|
|
2047
|
+
async verifyClientDef(name, opts) {
|
|
2048
|
+
const input = { op: "verify", name };
|
|
2049
|
+
if (opts?.contentSha256)
|
|
2050
|
+
input.content_sha256 = opts.contentSha256;
|
|
2051
|
+
return (await this.clientDef(input, signalOpts(opts)));
|
|
2052
|
+
}
|
|
2053
|
+
/** The apps the caller may see, by name (`GET /v1/_clientdef/names`). An
|
|
2054
|
+
* admin may focus one tenant with `tenant`; unlike the other families'
|
|
2055
|
+
* name lists, `""` is a focus too (the every-tenant apps). */
|
|
2056
|
+
async listClientDefNames(opts) {
|
|
2057
|
+
const q = opts?.tenant !== undefined ? `?tenant=${encodeURIComponent(opts.tenant)}` : "";
|
|
2058
|
+
return jsonFetch(this.ctx, `/v1/_clientdef/names${q}`, opts);
|
|
2059
|
+
}
|
|
1836
2060
|
// ---- Def name enumeration (GET /v1/_<family>def/names) ----
|
|
1837
2061
|
//
|
|
1838
2062
|
// Every name declared in one substrate family, each with its version
|
|
@@ -3347,6 +3571,12 @@ function defWriteInput(op, name, overlay, opts) {
|
|
|
3347
3571
|
input.promote = opts.promote;
|
|
3348
3572
|
return input;
|
|
3349
3573
|
}
|
|
3574
|
+
/** A password-gate management body with the tenant the caller named. The
|
|
3575
|
+
* POST/PATCH routes read the tenant from the body where the others read
|
|
3576
|
+
* ?tenant=; like aclPath, an empty tenant is left out. */
|
|
3577
|
+
function withTenant(body, tenant) {
|
|
3578
|
+
return tenant ? { ...body, tenant } : body;
|
|
3579
|
+
}
|
|
3350
3580
|
function signalOpts(opts) {
|
|
3351
3581
|
return opts?.signal ? { signal: opts.signal } : undefined;
|
|
3352
3582
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal — reached through LoomcycleClient.deviceLogin().
|
|
3
|
+
*
|
|
4
|
+
* The client side of loomcycle's device login (RFC 8628, a `kind: device`
|
|
5
|
+
* sign-in gate): ask the server for a code pair, show the person the short
|
|
6
|
+
* code, poll until they approve it in a signed-in Web UI session, and return
|
|
7
|
+
* the token issued to them. Both requests are unauthenticated — the device
|
|
8
|
+
* code is the credential — so no bearer the client holds is sent with them.
|
|
9
|
+
*/
|
|
10
|
+
import type { _FetchContext } from "./fetch-helpers.js";
|
|
11
|
+
import type { DeviceLoginOptions, DeviceLoginResult } from "./types.js";
|
|
12
|
+
export declare function deviceLogin(ctx: _FetchContext, opts: DeviceLoginOptions): Promise<DeviceLoginResult>;
|