@possibl/rcrt-sdk 0.1.2 → 0.5.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/CHANGELOG.md +99 -62
- package/LICENSE +21 -0
- package/README.md +35 -21
- package/dist/auth.d.ts +45 -0
- package/dist/auth.d.ts.map +1 -0
- package/{src/auth.ts → dist/auth.js} +9 -24
- package/dist/auth.js.map +1 -0
- package/dist/authn.d.ts +114 -0
- package/dist/authn.d.ts.map +1 -0
- package/dist/authn.js +107 -0
- package/dist/authn.js.map +1 -0
- package/dist/breadcrumbs.d.ts +43 -0
- package/dist/breadcrumbs.d.ts.map +1 -0
- package/dist/breadcrumbs.js +122 -0
- package/dist/breadcrumbs.js.map +1 -0
- package/dist/cards.d.ts +28 -0
- package/dist/cards.d.ts.map +1 -0
- package/dist/cards.js +105 -0
- package/dist/cards.js.map +1 -0
- package/dist/chat.d.ts +103 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +105 -0
- package/dist/chat.js.map +1 -0
- package/dist/client.d.ts +85 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +133 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +76 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +41 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +64 -0
- package/dist/files.js.map +1 -0
- package/dist/generated/conformance.d.ts +48 -0
- package/dist/generated/conformance.d.ts.map +1 -0
- package/dist/generated/conformance.js +24 -0
- package/dist/generated/conformance.js.map +1 -0
- package/dist/generated/index.d.ts +34 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +34 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/generated/openapi.d.ts +3900 -0
- package/dist/generated/openapi.d.ts.map +1 -0
- package/dist/generated/openapi.js +6 -0
- package/dist/generated/openapi.js.map +1 -0
- package/dist/grants.d.ts +41 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +50 -0
- package/dist/grants.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/fetch.d.ts +47 -0
- package/dist/internal/fetch.d.ts.map +1 -0
- package/dist/internal/fetch.js +108 -0
- package/dist/internal/fetch.js.map +1 -0
- package/dist/internal/sse.d.ts +82 -0
- package/dist/internal/sse.d.ts.map +1 -0
- package/dist/internal/sse.js +161 -0
- package/dist/internal/sse.js.map +1 -0
- package/dist/marketplace.d.ts +98 -0
- package/dist/marketplace.d.ts.map +1 -0
- package/dist/marketplace.js +74 -0
- package/dist/marketplace.js.map +1 -0
- package/dist/members.d.ts +60 -0
- package/dist/members.d.ts.map +1 -0
- package/dist/members.js +74 -0
- package/dist/members.js.map +1 -0
- package/dist/org.d.ts +85 -0
- package/dist/org.d.ts.map +1 -0
- package/dist/org.js +70 -0
- package/dist/org.js.map +1 -0
- package/dist/types/breadcrumb.d.ts +70 -0
- package/dist/types/breadcrumb.d.ts.map +1 -0
- package/dist/types/breadcrumb.js +8 -0
- package/dist/types/breadcrumb.js.map +1 -0
- package/dist/types/card.d.ts +251 -0
- package/dist/types/card.d.ts.map +1 -0
- package/dist/types/card.js +10 -0
- package/dist/types/card.js.map +1 -0
- package/dist/types/engine.d.ts +69 -0
- package/dist/types/engine.d.ts.map +1 -0
- package/dist/types/engine.js +53 -0
- package/dist/types/engine.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +35 -6
- package/src/authn.ts +0 -159
- package/src/breadcrumbs.ts +0 -111
- package/src/capabilities.ts +0 -93
- package/src/cards.ts +0 -109
- package/src/chat.ts +0 -83
- package/src/client.ts +0 -97
- package/src/errors.ts +0 -101
- package/src/files.ts +0 -135
- package/src/grants.ts +0 -99
- package/src/index.ts +0 -103
- package/src/internal/fetch.ts +0 -133
- package/src/internal/sse.ts +0 -236
- package/src/sessions.ts +0 -110
- package/src/types/breadcrumb.ts +0 -77
- package/src/types/card.ts +0 -298
- package/src/types/index.ts +0 -2
package/src/client.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RcrtClient — the single entry-point to the SDK.
|
|
3
|
-
*
|
|
4
|
-
* import { RcrtClient, staticTokenProvider } from '@possibl/rcrt-sdk';
|
|
5
|
-
*
|
|
6
|
-
* const rcrt = new RcrtClient({
|
|
7
|
-
* apiUrl: 'https://rcrt-api-gateway-<hash>.run.app',
|
|
8
|
-
* tokenProvider: firebaseTokenProvider(auth),
|
|
9
|
-
* });
|
|
10
|
-
*
|
|
11
|
-
* rcrt.setTenantId(workspaceId);
|
|
12
|
-
* const me = await rcrt.auth.me();
|
|
13
|
-
* const stream = rcrt.chat.sessionStream(sessionId, { ...handlers });
|
|
14
|
-
*
|
|
15
|
-
* Every module on the client shares the same fetch context — one
|
|
16
|
-
* bearer refresh path, one tenant header, one error envelope.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import type { TokenProvider } from './auth.js';
|
|
20
|
-
import type { FetchContext } from './internal/fetch.js';
|
|
21
|
-
import { BreadcrumbsModule } from './breadcrumbs.js';
|
|
22
|
-
import { ChatModule } from './chat.js';
|
|
23
|
-
import { CardsModule } from './cards.js';
|
|
24
|
-
import { GrantsModule } from './grants.js';
|
|
25
|
-
import { IdentityModule } from './authn.js';
|
|
26
|
-
import { FilesModule } from './files.js';
|
|
27
|
-
import { SessionsModule } from './sessions.js';
|
|
28
|
-
import { CapabilitiesModule } from './capabilities.js';
|
|
29
|
-
import { SdkError } from './errors.js';
|
|
30
|
-
|
|
31
|
-
export interface RcrtClientConfig {
|
|
32
|
-
apiUrl: string;
|
|
33
|
-
tokenProvider: TokenProvider;
|
|
34
|
-
/** Optional workspace UUID. Can be set later via `setTenantId()`. */
|
|
35
|
-
tenantId?: string;
|
|
36
|
-
/** Optional fetch override (SSR, custom retry, mock). */
|
|
37
|
-
fetchImpl?: typeof fetch;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class RcrtClient {
|
|
41
|
-
private readonly ctx: FetchContext;
|
|
42
|
-
|
|
43
|
-
public readonly auth: IdentityModule;
|
|
44
|
-
public readonly breadcrumbs: BreadcrumbsModule;
|
|
45
|
-
public readonly chat: ChatModule;
|
|
46
|
-
public readonly cards: CardsModule;
|
|
47
|
-
public readonly grants: GrantsModule;
|
|
48
|
-
public readonly files: FilesModule;
|
|
49
|
-
public readonly sessions: SessionsModule;
|
|
50
|
-
public readonly capabilities: CapabilitiesModule;
|
|
51
|
-
|
|
52
|
-
constructor(config: RcrtClientConfig) {
|
|
53
|
-
if (!config.apiUrl) {
|
|
54
|
-
throw new SdkError('MISSING_API_URL', 'RcrtClient requires `apiUrl`');
|
|
55
|
-
}
|
|
56
|
-
if (!config.tokenProvider) {
|
|
57
|
-
throw new SdkError('MISSING_TOKEN_PROVIDER', 'RcrtClient requires a `tokenProvider`');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const ctxInternal = {
|
|
61
|
-
apiUrl: config.apiUrl,
|
|
62
|
-
tenantId: config.tenantId ?? null,
|
|
63
|
-
tokenProvider: config.tokenProvider,
|
|
64
|
-
fetchImpl: config.fetchImpl ?? undefined,
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Use a Proxy so module instances can read the current `tenantId`
|
|
68
|
-
// at request time without us having to rebuild them on every
|
|
69
|
-
// `setTenantId` call.
|
|
70
|
-
this.ctx = new Proxy({} as FetchContext, {
|
|
71
|
-
get: (_target, prop) => (ctxInternal as unknown as Record<string, unknown>)[prop as string],
|
|
72
|
-
set: (_target, prop, value) => {
|
|
73
|
-
(ctxInternal as unknown as Record<string, unknown>)[prop as string] = value;
|
|
74
|
-
return true;
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
this.auth = new IdentityModule(this.ctx);
|
|
79
|
-
this.breadcrumbs = new BreadcrumbsModule(this.ctx);
|
|
80
|
-
this.chat = new ChatModule(this.ctx);
|
|
81
|
-
this.cards = new CardsModule(this.ctx);
|
|
82
|
-
this.grants = new GrantsModule(this.ctx);
|
|
83
|
-
this.files = new FilesModule(this.ctx);
|
|
84
|
-
this.sessions = new SessionsModule(this.ctx);
|
|
85
|
-
this.capabilities = new CapabilitiesModule(this.ctx);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Switch workspaces. Subsequent requests carry the new `X-Tenant-ID`. */
|
|
89
|
-
setTenantId(tenantId: string | null): void {
|
|
90
|
-
(this.ctx as unknown as { tenantId: string | null }).tenantId = tenantId;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Read the workspace id this client is currently scoped to. */
|
|
94
|
-
getTenantId(): string | null {
|
|
95
|
-
return (this.ctx as unknown as { tenantId: string | null }).tenantId;
|
|
96
|
-
}
|
|
97
|
-
}
|
package/src/errors.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error taxonomy.
|
|
3
|
-
*
|
|
4
|
-
* The RCRT backend is mid-migration from a flat `{"error": "string"}`
|
|
5
|
-
* envelope to a typed `{"error": {"code", "message", "details"}}`
|
|
6
|
-
* shape. This module normalises both into one `ApiError` that app
|
|
7
|
-
* code can switch on.
|
|
8
|
-
*
|
|
9
|
-
* See `packages/docs/guides/07-error-handling.md`.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/** Stable machine-readable error codes recognised by the SDK. */
|
|
13
|
-
export type KnownErrorCode =
|
|
14
|
-
| 'UNAUTHORIZED'
|
|
15
|
-
| 'FORBIDDEN'
|
|
16
|
-
| 'NOT_FOUND'
|
|
17
|
-
| 'CONFLICT'
|
|
18
|
-
| 'INVALID_REQUEST'
|
|
19
|
-
| 'TOO_MANY_REQUESTS'
|
|
20
|
-
| 'INTERNAL'
|
|
21
|
-
| 'SERVICE_UNAVAILABLE'
|
|
22
|
-
| 'UNKNOWN';
|
|
23
|
-
|
|
24
|
-
export interface ApiErrorDetail {
|
|
25
|
-
/** Machine-readable code if the server sent one. Otherwise derived from status. */
|
|
26
|
-
code: KnownErrorCode | string;
|
|
27
|
-
message: string;
|
|
28
|
-
details?: Record<string, unknown>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class ApiError extends Error {
|
|
32
|
-
public readonly status: number;
|
|
33
|
-
public readonly detail: ApiErrorDetail;
|
|
34
|
-
public readonly rawBody: string | undefined;
|
|
35
|
-
|
|
36
|
-
constructor(status: number, detail: ApiErrorDetail, rawBody?: string) {
|
|
37
|
-
super(detail.message);
|
|
38
|
-
this.name = 'ApiError';
|
|
39
|
-
this.status = status;
|
|
40
|
-
this.detail = detail;
|
|
41
|
-
this.rawBody = rawBody;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static fromResponse(status: number, rawBody: string): ApiError {
|
|
45
|
-
return new ApiError(status, parseErrorBody(status, rawBody), rawBody);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Errors thrown by the SDK before we even reach the server. */
|
|
50
|
-
export class SdkError extends Error {
|
|
51
|
-
public readonly code: string;
|
|
52
|
-
|
|
53
|
-
constructor(code: string, message: string) {
|
|
54
|
-
super(message);
|
|
55
|
-
this.name = 'SdkError';
|
|
56
|
-
this.code = code;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ── Internal helpers ─────────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
const STATUS_TO_CODE: Record<number, KnownErrorCode> = {
|
|
63
|
-
400: 'INVALID_REQUEST',
|
|
64
|
-
401: 'UNAUTHORIZED',
|
|
65
|
-
403: 'FORBIDDEN',
|
|
66
|
-
404: 'NOT_FOUND',
|
|
67
|
-
409: 'CONFLICT',
|
|
68
|
-
429: 'TOO_MANY_REQUESTS',
|
|
69
|
-
500: 'INTERNAL',
|
|
70
|
-
503: 'SERVICE_UNAVAILABLE',
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export function parseErrorBody(status: number, rawBody: string): ApiErrorDetail {
|
|
74
|
-
const fallbackCode = STATUS_TO_CODE[status] ?? 'UNKNOWN';
|
|
75
|
-
if (!rawBody.trim()) {
|
|
76
|
-
return { code: fallbackCode, message: `HTTP ${status}` };
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
const parsed = JSON.parse(rawBody) as unknown;
|
|
80
|
-
if (parsed && typeof parsed === 'object' && 'error' in parsed) {
|
|
81
|
-
const err = (parsed as { error: unknown }).error;
|
|
82
|
-
if (typeof err === 'string') {
|
|
83
|
-
return { code: fallbackCode, message: err };
|
|
84
|
-
}
|
|
85
|
-
if (err && typeof err === 'object') {
|
|
86
|
-
const e = err as Record<string, unknown>;
|
|
87
|
-
const detail: ApiErrorDetail = {
|
|
88
|
-
code: typeof e.code === 'string' ? e.code : fallbackCode,
|
|
89
|
-
message: typeof e.message === 'string' ? e.message : `HTTP ${status}`,
|
|
90
|
-
};
|
|
91
|
-
if (typeof e.details === 'object' && e.details !== null) {
|
|
92
|
-
detail.details = e.details as Record<string, unknown>;
|
|
93
|
-
}
|
|
94
|
-
return detail;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return { code: fallbackCode, message: rawBody.slice(0, 200) };
|
|
98
|
-
} catch {
|
|
99
|
-
return { code: fallbackCode, message: rawBody.slice(0, 200) };
|
|
100
|
-
}
|
|
101
|
-
}
|
package/src/files.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Files module — upload + download + read text + list + delete.
|
|
3
|
-
*
|
|
4
|
-
* Files are stored as breadcrumbs under `interpret:file` so they
|
|
5
|
-
* inherit the same tag / permissions / SSE lifecycle as everything
|
|
6
|
-
* else. The upload returns the file's breadcrumb so callers can
|
|
7
|
-
* round-trip into `breadcrumbs.update()` for renames or tagging.
|
|
8
|
-
*
|
|
9
|
-
* `getDownloadUrl` returns a short-lived signed URL suitable for
|
|
10
|
-
* `<a href>` or `fetch()`; the SDK does not cache it.
|
|
11
|
-
*
|
|
12
|
-
* See `packages/docs/guides/06-files.md` for the narrative.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { FetchContext } from './internal/fetch.js';
|
|
16
|
-
import { request } from './internal/fetch.js';
|
|
17
|
-
import { ApiError, SdkError } from './errors.js';
|
|
18
|
-
import type { Breadcrumb } from './types/breadcrumb.js';
|
|
19
|
-
|
|
20
|
-
export type FileScope = 'tenant' | 'org' | 'user';
|
|
21
|
-
|
|
22
|
-
export interface UploadFileOptions {
|
|
23
|
-
/** Default `'tenant'` — file is visible to all members of the active workspace. */
|
|
24
|
-
scope?: FileScope;
|
|
25
|
-
/** Override the filename written on the breadcrumb (defaults to `File.name`). */
|
|
26
|
-
filename?: string;
|
|
27
|
-
/** Extra tags to attach. `interpret:file` is always added by the server. */
|
|
28
|
-
tags?: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ListFilesOptions {
|
|
32
|
-
scope?: FileScope;
|
|
33
|
-
limit?: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class FilesModule {
|
|
37
|
-
constructor(private readonly ctx: FetchContext) {}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* `POST /v1/files` — multipart upload.
|
|
41
|
-
*
|
|
42
|
-
* Accepts a `Blob` (browser / RN) or `File` (browser only). Server
|
|
43
|
-
* returns the breadcrumb wrapping the stored file.
|
|
44
|
-
*/
|
|
45
|
-
async upload(file: Blob | File, options: UploadFileOptions = {}): Promise<Breadcrumb> {
|
|
46
|
-
const fetchImpl = this.ctx.fetchImpl ?? globalThis.fetch;
|
|
47
|
-
if (typeof fetchImpl !== 'function') {
|
|
48
|
-
throw new SdkError('NO_FETCH', 'fetch() is not available — pass `fetchImpl` in client config');
|
|
49
|
-
}
|
|
50
|
-
if (typeof FormData === 'undefined') {
|
|
51
|
-
throw new SdkError(
|
|
52
|
-
'NO_FORMDATA',
|
|
53
|
-
'FormData is not available in this environment — provide a polyfill (e.g. form-data on Node) before calling files.upload()',
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const formData = new FormData();
|
|
58
|
-
const filename =
|
|
59
|
-
options.filename ?? ((file as File).name ? (file as File).name : 'upload');
|
|
60
|
-
formData.append('file', file as Blob, filename);
|
|
61
|
-
formData.append('scope', options.scope ?? 'tenant');
|
|
62
|
-
if (options.tags?.length) {
|
|
63
|
-
formData.append('tags', options.tags.join(','));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const url = `${this.ctx.apiUrl.replace(/\/$/, '')}/v1/files`;
|
|
67
|
-
const idToken = await this.ctx.tokenProvider.getIdToken();
|
|
68
|
-
const headers: Record<string, string> = {
|
|
69
|
-
Authorization: `Bearer ${idToken}`,
|
|
70
|
-
};
|
|
71
|
-
if (this.ctx.tenantId) headers['X-Tenant-ID'] = this.ctx.tenantId;
|
|
72
|
-
|
|
73
|
-
const res = await fetchImpl(url, { method: 'POST', headers, body: formData });
|
|
74
|
-
if (!res.ok) {
|
|
75
|
-
const raw = await res.text().catch(() => '');
|
|
76
|
-
throw ApiError.fromResponse(res.status, raw || 'upload failed');
|
|
77
|
-
}
|
|
78
|
-
const json = (await res.json()) as { breadcrumb?: Breadcrumb } & Breadcrumb;
|
|
79
|
-
return json.breadcrumb ?? json;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** `GET /v1/files?scope=...&limit=...` */
|
|
83
|
-
async list(options: ListFilesOptions = {}): Promise<Breadcrumb[]> {
|
|
84
|
-
const res = await request<{ files: Breadcrumb[] } | Breadcrumb[]>(this.ctx, '/v1/files', {
|
|
85
|
-
query: {
|
|
86
|
-
scope: options.scope,
|
|
87
|
-
limit: options.limit,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
return Array.isArray(res) ? res : (res.files ?? []);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** `GET /v1/files/{id}` — wrapping breadcrumb for the file. */
|
|
94
|
-
async get(fileId: string): Promise<Breadcrumb> {
|
|
95
|
-
const res = await request<{ file: Breadcrumb } | Breadcrumb>(this.ctx, `/v1/files/${fileId}`);
|
|
96
|
-
return 'file' in (res as { file: Breadcrumb })
|
|
97
|
-
? (res as { file: Breadcrumb }).file
|
|
98
|
-
: (res as Breadcrumb);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* `GET /v1/files/{id}/content?expiry=...` — signed download URL.
|
|
103
|
-
*
|
|
104
|
-
* Default expiry `1h`. URL format goes through Cloud Storage's V4
|
|
105
|
-
* signed URL flow on the server side. Don't cache — re-fetch when
|
|
106
|
-
* you need a fresh one.
|
|
107
|
-
*/
|
|
108
|
-
async getDownloadUrl(fileId: string, expiry: string = '1h'): Promise<string> {
|
|
109
|
-
const res = await request<{ download_url: string }>(this.ctx, `/v1/files/${fileId}/content`, {
|
|
110
|
-
query: { expiry },
|
|
111
|
-
});
|
|
112
|
-
return res.download_url;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* `GET /v1/files/{id}/text` — server-side OCR / text extraction.
|
|
117
|
-
*
|
|
118
|
-
* Only works for content the document-parser knows about (PDF,
|
|
119
|
-
* common image formats, plain text). Returns `''` for unparseable.
|
|
120
|
-
*/
|
|
121
|
-
async getText(fileId: string): Promise<string> {
|
|
122
|
-
const res = await request<{ text: string }>(this.ctx, `/v1/files/${fileId}/text`);
|
|
123
|
-
return res.text;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** `DELETE /v1/files/{id}` — soft delete on the wrapping breadcrumb. */
|
|
127
|
-
async delete(fileId: string): Promise<void> {
|
|
128
|
-
try {
|
|
129
|
-
await request<void>(this.ctx, `/v1/files/${fileId}`, { method: 'DELETE' });
|
|
130
|
-
} catch (err) {
|
|
131
|
-
if (err instanceof ApiError && err.status === 404) return; // idempotent
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
package/src/grants.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Service grants module — OAuth connect flow + grant management.
|
|
3
|
-
*
|
|
4
|
-
* See `packages/docs/guides/05-connecting-services.md`.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FetchContext } from './internal/fetch.js';
|
|
8
|
-
import { request } from './internal/fetch.js';
|
|
9
|
-
|
|
10
|
-
export type GrantType = 'per_user' | 'service_account';
|
|
11
|
-
export type GrantStatus = 'connected' | 'revoked' | 'error';
|
|
12
|
-
|
|
13
|
-
export interface ServiceGrantSummary {
|
|
14
|
-
id: string;
|
|
15
|
-
service_id: string;
|
|
16
|
-
account_label?: string | null;
|
|
17
|
-
email?: string | null;
|
|
18
|
-
scopes: string[];
|
|
19
|
-
status: GrantStatus;
|
|
20
|
-
granted_at: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface InitiateAuthRequest {
|
|
24
|
-
grant_type?: GrantType;
|
|
25
|
-
scopes?: string[];
|
|
26
|
-
/** Where the browser lands after provider consent. */
|
|
27
|
-
redirect_uri?: string;
|
|
28
|
-
/** Label this grant as a specific account (e.g. `work`, `personal`). */
|
|
29
|
-
account_label?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface InitiateAuthResponse {
|
|
33
|
-
auth_url: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class GrantsModule {
|
|
37
|
-
constructor(private readonly ctx: FetchContext) {}
|
|
38
|
-
|
|
39
|
-
/** All grants the current workspace can see. */
|
|
40
|
-
async list(): Promise<ServiceGrantSummary[]> {
|
|
41
|
-
const res = await request<ServiceGrantSummary[] | { grants: ServiceGrantSummary[] }>(
|
|
42
|
-
this.ctx,
|
|
43
|
-
'/v1/service-grants',
|
|
44
|
-
);
|
|
45
|
-
return Array.isArray(res) ? res : (res.grants ?? []);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Grant for one specific service (optionally an account label). */
|
|
49
|
-
async get(serviceId: string, accountLabel?: string): Promise<ServiceGrantSummary | null> {
|
|
50
|
-
const query: Record<string, string | undefined> = {};
|
|
51
|
-
if (accountLabel) query.account = accountLabel;
|
|
52
|
-
const res = await request<ServiceGrantSummary | null>(this.ctx, `/v1/service-grants/${serviceId}`, {
|
|
53
|
-
query,
|
|
54
|
-
});
|
|
55
|
-
return res ?? null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Kick off an OAuth flow. Returns the provider URL to open in a popup / in-app browser. */
|
|
59
|
-
async initiateAuth(serviceId: string, req: InitiateAuthRequest = {}): Promise<InitiateAuthResponse> {
|
|
60
|
-
return request<InitiateAuthResponse>(this.ctx, `/v1/service-grants/${serviceId}/auth/init`, {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
body: {
|
|
63
|
-
grant_type: req.grant_type ?? 'per_user',
|
|
64
|
-
scopes: req.scopes,
|
|
65
|
-
redirect_uri: req.redirect_uri,
|
|
66
|
-
account_label: req.account_label,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Revoke a grant. Safe + idempotent — revoking a revoked grant is a no-op. */
|
|
72
|
-
async revoke(serviceId: string, accountLabel?: string): Promise<void> {
|
|
73
|
-
const query: Record<string, string | undefined> = {};
|
|
74
|
-
if (accountLabel) query.account = accountLabel;
|
|
75
|
-
await request<void>(this.ctx, `/v1/service-grants/${serviceId}`, {
|
|
76
|
-
method: 'DELETE',
|
|
77
|
-
query,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* `GET /v1/services/{name}/resolve` — server-side credential resolution.
|
|
83
|
-
*
|
|
84
|
-
* Used by tools that need to call a third-party API on behalf of
|
|
85
|
-
* the workspace. The server hands back the live credentials for the
|
|
86
|
-
* requested service (Gmail, Notion etc.) keyed off the active grant.
|
|
87
|
-
*
|
|
88
|
-
* Returns `{ service, credentials }`. Treat the credentials as
|
|
89
|
-
* sensitive — don't log them.
|
|
90
|
-
*/
|
|
91
|
-
async resolveService(
|
|
92
|
-
name: string,
|
|
93
|
-
): Promise<{ service: string; credentials: Record<string, unknown> }> {
|
|
94
|
-
return request<{ service: string; credentials: Record<string, unknown> }>(
|
|
95
|
-
this.ctx,
|
|
96
|
-
`/v1/services/${name}/resolve`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @possibl/rcrt-sdk — TypeScript SDK for RCRT.
|
|
3
|
-
*
|
|
4
|
-
* Public surface. Anything not re-exported here is internal.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Primary entry
|
|
8
|
-
export { RcrtClient } from './client.js';
|
|
9
|
-
export type { RcrtClientConfig } from './client.js';
|
|
10
|
-
|
|
11
|
-
// Auth
|
|
12
|
-
export { staticTokenProvider } from './auth.js';
|
|
13
|
-
export type { TokenProvider } from './auth.js';
|
|
14
|
-
|
|
15
|
-
// Errors
|
|
16
|
-
export { ApiError, SdkError } from './errors.js';
|
|
17
|
-
export type { ApiErrorDetail, KnownErrorCode } from './errors.js';
|
|
18
|
-
|
|
19
|
-
// Modules (handy if consumers want the types directly)
|
|
20
|
-
export { BreadcrumbsModule } from './breadcrumbs.js';
|
|
21
|
-
export { ChatModule } from './chat.js';
|
|
22
|
-
export { CardsModule } from './cards.js';
|
|
23
|
-
export { GrantsModule } from './grants.js';
|
|
24
|
-
export { IdentityModule } from './authn.js';
|
|
25
|
-
export { FilesModule } from './files.js';
|
|
26
|
-
export { SessionsModule } from './sessions.js';
|
|
27
|
-
export { CapabilitiesModule } from './capabilities.js';
|
|
28
|
-
export type {
|
|
29
|
-
MeResponse,
|
|
30
|
-
Tenant,
|
|
31
|
-
PendingInvitation,
|
|
32
|
-
UserProfileBreadcrumb,
|
|
33
|
-
} from './authn.js';
|
|
34
|
-
export type { SendChatRequest, SendChatResponse, SseStreamOptions } from './chat.js';
|
|
35
|
-
export type {
|
|
36
|
-
ServiceGrantSummary,
|
|
37
|
-
InitiateAuthRequest,
|
|
38
|
-
InitiateAuthResponse,
|
|
39
|
-
GrantType,
|
|
40
|
-
GrantStatus,
|
|
41
|
-
} from './grants.js';
|
|
42
|
-
export type { FileScope, UploadFileOptions, ListFilesOptions } from './files.js';
|
|
43
|
-
export type {
|
|
44
|
-
SessionParticipant,
|
|
45
|
-
ConstellationData,
|
|
46
|
-
ConstellationNode,
|
|
47
|
-
ConstellationEdge,
|
|
48
|
-
} from './sessions.js';
|
|
49
|
-
export type { CapabilityType, ChattableAgent } from './capabilities.js';
|
|
50
|
-
|
|
51
|
-
// Types — breadcrumbs
|
|
52
|
-
export type {
|
|
53
|
-
Actor,
|
|
54
|
-
ActorType,
|
|
55
|
-
Breadcrumb,
|
|
56
|
-
BreadcrumbResponse,
|
|
57
|
-
CreateBreadcrumbRequest,
|
|
58
|
-
UpdateBreadcrumbRequest,
|
|
59
|
-
QueryByTagsOptions,
|
|
60
|
-
SemanticSearchOptions,
|
|
61
|
-
} from './types/breadcrumb.js';
|
|
62
|
-
|
|
63
|
-
// Types — JIT UI cards
|
|
64
|
-
export type {
|
|
65
|
-
Card,
|
|
66
|
-
CardLayout,
|
|
67
|
-
CardHeader,
|
|
68
|
-
CardBody,
|
|
69
|
-
CardFooter,
|
|
70
|
-
CardAction,
|
|
71
|
-
ActionStyle,
|
|
72
|
-
Row,
|
|
73
|
-
TextRow,
|
|
74
|
-
FlexibleTextRow,
|
|
75
|
-
ClaimRow,
|
|
76
|
-
MetricRow,
|
|
77
|
-
EventRow,
|
|
78
|
-
PersonRow,
|
|
79
|
-
PlaceRow,
|
|
80
|
-
ToggleRow,
|
|
81
|
-
DraftPreviewRow,
|
|
82
|
-
ChartRow,
|
|
83
|
-
ChartSpec,
|
|
84
|
-
TrendDirection,
|
|
85
|
-
DraftPreview,
|
|
86
|
-
InputSpec,
|
|
87
|
-
RatingSpec,
|
|
88
|
-
ProgressState,
|
|
89
|
-
ConnectDetails,
|
|
90
|
-
CompareColumn,
|
|
91
|
-
ResolveRequest,
|
|
92
|
-
CanonicalStatus,
|
|
93
|
-
} from './types/card.js';
|
|
94
|
-
|
|
95
|
-
// SSE primitives (useful for custom consumers)
|
|
96
|
-
export type {
|
|
97
|
-
SseConnection,
|
|
98
|
-
SseHandlers,
|
|
99
|
-
SseDelta,
|
|
100
|
-
SseThought,
|
|
101
|
-
SseAction,
|
|
102
|
-
SseError,
|
|
103
|
-
} from './internal/sse.js';
|
package/src/internal/fetch.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal HTTP helper. Handles:
|
|
3
|
-
* - auth + tenant headers
|
|
4
|
-
* - JSON encoding + parsing
|
|
5
|
-
* - error envelope normalisation
|
|
6
|
-
* - 409 retry for optimistic-locking PATCHes
|
|
7
|
-
* - 401 one-shot retry after TokenProvider.onUnauthorized
|
|
8
|
-
* - 429 honouring Retry-After
|
|
9
|
-
*
|
|
10
|
-
* Not exported from the public surface — use the RcrtClient methods.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { ApiError, parseErrorBody, SdkError } from '../errors.js';
|
|
14
|
-
import type { TokenProvider } from '../auth.js';
|
|
15
|
-
|
|
16
|
-
export interface FetchContext {
|
|
17
|
-
apiUrl: string;
|
|
18
|
-
tenantId: string | null;
|
|
19
|
-
tokenProvider: TokenProvider;
|
|
20
|
-
fetchImpl?: typeof fetch;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface RequestOptions {
|
|
24
|
-
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
25
|
-
/** JSON body; will be serialised. */
|
|
26
|
-
body?: unknown;
|
|
27
|
-
/** Extra query params. */
|
|
28
|
-
query?: Record<string, string | number | boolean | undefined | null>;
|
|
29
|
-
/** Extra headers. */
|
|
30
|
-
headers?: Record<string, string>;
|
|
31
|
-
/** Skip tenant header (identity routes). */
|
|
32
|
-
skipTenant?: boolean;
|
|
33
|
-
/** Abort signal. */
|
|
34
|
-
signal?: AbortSignal;
|
|
35
|
-
/**
|
|
36
|
-
* Retries for 409 Conflict. Each retry re-fetches first if the
|
|
37
|
-
* caller provides a `refetchBeforeRetry` hook.
|
|
38
|
-
*/
|
|
39
|
-
maxConflictRetries?: number;
|
|
40
|
-
refetchBeforeRetry?: () => Promise<Partial<{ body: unknown }>>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function request<T = unknown>(
|
|
44
|
-
ctx: FetchContext,
|
|
45
|
-
path: string,
|
|
46
|
-
options: RequestOptions = {},
|
|
47
|
-
): Promise<T> {
|
|
48
|
-
const method = options.method ?? 'GET';
|
|
49
|
-
const fetchImpl = ctx.fetchImpl ?? globalThis.fetch;
|
|
50
|
-
if (typeof fetchImpl !== 'function') {
|
|
51
|
-
throw new SdkError('NO_FETCH', 'fetch() is not available — pass `fetchImpl` in client config');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const url = buildUrl(ctx.apiUrl, path, options.query);
|
|
55
|
-
const maxRetries = options.maxConflictRetries ?? 2;
|
|
56
|
-
let attempt = 0;
|
|
57
|
-
let body = options.body;
|
|
58
|
-
let didRefreshAuth = false;
|
|
59
|
-
|
|
60
|
-
while (true) {
|
|
61
|
-
const idToken = await ctx.tokenProvider.getIdToken();
|
|
62
|
-
const headers: Record<string, string> = {
|
|
63
|
-
'Authorization': `Bearer ${idToken}`,
|
|
64
|
-
...(options.headers ?? {}),
|
|
65
|
-
};
|
|
66
|
-
if (!options.skipTenant && ctx.tenantId) {
|
|
67
|
-
headers['X-Tenant-ID'] = ctx.tenantId;
|
|
68
|
-
}
|
|
69
|
-
if (body !== undefined) {
|
|
70
|
-
headers['Content-Type'] = 'application/json';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const init: RequestInit = { method, headers };
|
|
74
|
-
if (body !== undefined) init.body = JSON.stringify(body);
|
|
75
|
-
if (options.signal) init.signal = options.signal;
|
|
76
|
-
const res = await fetchImpl(url, init);
|
|
77
|
-
|
|
78
|
-
if (res.status >= 200 && res.status < 300) {
|
|
79
|
-
if (res.status === 204) return undefined as T;
|
|
80
|
-
const text = await res.text();
|
|
81
|
-
if (!text) return undefined as T;
|
|
82
|
-
try {
|
|
83
|
-
return JSON.parse(text) as T;
|
|
84
|
-
} catch {
|
|
85
|
-
// Some endpoints return non-JSON (e.g. raw bytes); surface as a string.
|
|
86
|
-
return text as unknown as T;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Retry 401 once after the token provider has a chance to refresh.
|
|
91
|
-
if (res.status === 401 && !didRefreshAuth && ctx.tokenProvider.onUnauthorized) {
|
|
92
|
-
didRefreshAuth = true;
|
|
93
|
-
await ctx.tokenProvider.onUnauthorized();
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Retry 409 with a fresh body if the caller supports optimistic refetch.
|
|
98
|
-
if (res.status === 409 && attempt < maxRetries && options.refetchBeforeRetry) {
|
|
99
|
-
attempt += 1;
|
|
100
|
-
const fresh = await options.refetchBeforeRetry();
|
|
101
|
-
if (fresh.body !== undefined) body = fresh.body;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Retry 429 honouring Retry-After, one time.
|
|
106
|
-
if (res.status === 429 && attempt < 1) {
|
|
107
|
-
attempt += 1;
|
|
108
|
-
const ra = parseInt(res.headers.get('Retry-After') ?? '1', 10);
|
|
109
|
-
await sleep((isNaN(ra) ? 1 : ra) * 1000);
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const raw = await res.text().catch(() => '');
|
|
114
|
-
throw new ApiError(res.status, parseErrorBody(res.status, raw), raw);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function buildUrl(apiUrl: string, path: string, query?: RequestOptions['query']): string {
|
|
119
|
-
const base = apiUrl.replace(/\/$/, '');
|
|
120
|
-
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
121
|
-
const u = new URL(base + fullPath);
|
|
122
|
-
if (query) {
|
|
123
|
-
for (const [k, v] of Object.entries(query)) {
|
|
124
|
-
if (v === undefined || v === null) continue;
|
|
125
|
-
u.searchParams.set(k, String(v));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return u.toString();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function sleep(ms: number): Promise<void> {
|
|
132
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
133
|
-
}
|