@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.
Files changed (108) hide show
  1. package/CHANGELOG.md +99 -62
  2. package/LICENSE +21 -0
  3. package/README.md +35 -21
  4. package/dist/auth.d.ts +45 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/{src/auth.ts → dist/auth.js} +9 -24
  7. package/dist/auth.js.map +1 -0
  8. package/dist/authn.d.ts +114 -0
  9. package/dist/authn.d.ts.map +1 -0
  10. package/dist/authn.js +107 -0
  11. package/dist/authn.js.map +1 -0
  12. package/dist/breadcrumbs.d.ts +43 -0
  13. package/dist/breadcrumbs.d.ts.map +1 -0
  14. package/dist/breadcrumbs.js +122 -0
  15. package/dist/breadcrumbs.js.map +1 -0
  16. package/dist/cards.d.ts +28 -0
  17. package/dist/cards.d.ts.map +1 -0
  18. package/dist/cards.js +105 -0
  19. package/dist/cards.js.map +1 -0
  20. package/dist/chat.d.ts +103 -0
  21. package/dist/chat.d.ts.map +1 -0
  22. package/dist/chat.js +105 -0
  23. package/dist/chat.js.map +1 -0
  24. package/dist/client.d.ts +85 -0
  25. package/dist/client.d.ts.map +1 -0
  26. package/dist/client.js +133 -0
  27. package/dist/client.js.map +1 -0
  28. package/dist/errors.d.ts +32 -0
  29. package/dist/errors.d.ts.map +1 -0
  30. package/dist/errors.js +76 -0
  31. package/dist/errors.js.map +1 -0
  32. package/dist/files.d.ts +41 -0
  33. package/dist/files.d.ts.map +1 -0
  34. package/dist/files.js +64 -0
  35. package/dist/files.js.map +1 -0
  36. package/dist/generated/conformance.d.ts +48 -0
  37. package/dist/generated/conformance.d.ts.map +1 -0
  38. package/dist/generated/conformance.js +24 -0
  39. package/dist/generated/conformance.js.map +1 -0
  40. package/dist/generated/index.d.ts +34 -0
  41. package/dist/generated/index.d.ts.map +1 -0
  42. package/dist/generated/index.js +34 -0
  43. package/dist/generated/index.js.map +1 -0
  44. package/dist/generated/openapi.d.ts +3900 -0
  45. package/dist/generated/openapi.d.ts.map +1 -0
  46. package/dist/generated/openapi.js +6 -0
  47. package/dist/generated/openapi.js.map +1 -0
  48. package/dist/grants.d.ts +41 -0
  49. package/dist/grants.d.ts.map +1 -0
  50. package/dist/grants.js +50 -0
  51. package/dist/grants.js.map +1 -0
  52. package/dist/index.d.ts +34 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +24 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/internal/fetch.d.ts +47 -0
  57. package/dist/internal/fetch.d.ts.map +1 -0
  58. package/dist/internal/fetch.js +108 -0
  59. package/dist/internal/fetch.js.map +1 -0
  60. package/dist/internal/sse.d.ts +82 -0
  61. package/dist/internal/sse.d.ts.map +1 -0
  62. package/dist/internal/sse.js +161 -0
  63. package/dist/internal/sse.js.map +1 -0
  64. package/dist/marketplace.d.ts +98 -0
  65. package/dist/marketplace.d.ts.map +1 -0
  66. package/dist/marketplace.js +74 -0
  67. package/dist/marketplace.js.map +1 -0
  68. package/dist/members.d.ts +60 -0
  69. package/dist/members.d.ts.map +1 -0
  70. package/dist/members.js +74 -0
  71. package/dist/members.js.map +1 -0
  72. package/dist/org.d.ts +85 -0
  73. package/dist/org.d.ts.map +1 -0
  74. package/dist/org.js +70 -0
  75. package/dist/org.js.map +1 -0
  76. package/dist/types/breadcrumb.d.ts +70 -0
  77. package/dist/types/breadcrumb.d.ts.map +1 -0
  78. package/dist/types/breadcrumb.js +8 -0
  79. package/dist/types/breadcrumb.js.map +1 -0
  80. package/dist/types/card.d.ts +251 -0
  81. package/dist/types/card.d.ts.map +1 -0
  82. package/dist/types/card.js +10 -0
  83. package/dist/types/card.js.map +1 -0
  84. package/dist/types/engine.d.ts +69 -0
  85. package/dist/types/engine.d.ts.map +1 -0
  86. package/dist/types/engine.js +53 -0
  87. package/dist/types/engine.js.map +1 -0
  88. package/dist/types/index.d.ts +4 -0
  89. package/dist/types/index.d.ts.map +1 -0
  90. package/dist/types/index.js +4 -0
  91. package/dist/types/index.js.map +1 -0
  92. package/package.json +35 -6
  93. package/src/authn.ts +0 -159
  94. package/src/breadcrumbs.ts +0 -111
  95. package/src/capabilities.ts +0 -93
  96. package/src/cards.ts +0 -109
  97. package/src/chat.ts +0 -83
  98. package/src/client.ts +0 -97
  99. package/src/errors.ts +0 -101
  100. package/src/files.ts +0 -135
  101. package/src/grants.ts +0 -99
  102. package/src/index.ts +0 -103
  103. package/src/internal/fetch.ts +0 -133
  104. package/src/internal/sse.ts +0 -236
  105. package/src/sessions.ts +0 -110
  106. package/src/types/breadcrumb.ts +0 -77
  107. package/src/types/card.ts +0 -298
  108. package/src/types/index.ts +0 -2
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@possibl/rcrt-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript SDK for the RCRT multi-tenant AI BaaS — isomorphic (web + node + React Native)",
5
5
  "type": "module",
6
+ "//": "In-repo consumers (tests, dashboard) see raw src/*.ts via `main`/`exports`. publishConfig overrides these at publish time to point at compiled dist/ so npm installs are clean JS + .d.ts.",
6
7
  "main": "./src/index.ts",
7
8
  "types": "./src/index.ts",
8
9
  "exports": {
@@ -13,12 +14,36 @@
13
14
  "./types": {
14
15
  "types": "./src/types/index.ts",
15
16
  "default": "./src/types/index.ts"
17
+ },
18
+ "./generated": {
19
+ "types": "./src/generated/index.ts",
20
+ "default": "./src/generated/index.ts"
21
+ }
22
+ },
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./types": {
33
+ "types": "./dist/types/index.d.ts",
34
+ "default": "./dist/types/index.js"
35
+ },
36
+ "./generated": {
37
+ "types": "./dist/generated/index.d.ts",
38
+ "default": "./dist/generated/index.js"
39
+ }
16
40
  }
17
41
  },
18
42
  "files": [
19
- "src",
43
+ "dist",
20
44
  "README.md",
21
- "CHANGELOG.md"
45
+ "CHANGELOG.md",
46
+ "LICENSE"
22
47
  ],
23
48
  "keywords": [
24
49
  "rcrt",
@@ -33,7 +58,7 @@
33
58
  "license": "MIT",
34
59
  "repository": {
35
60
  "type": "git",
36
- "url": "git+https://github.com/possibl-ai/rcrt-v2.git",
61
+ "url": "https://github.com/possibl-ai/rcrt-v2",
37
62
  "directory": "packages/rcrt-sdk"
38
63
  },
39
64
  "homepage": "https://github.com/possibl-ai/rcrt-v2/tree/development/packages/docs",
@@ -43,11 +68,15 @@
43
68
  },
44
69
  "scripts": {
45
70
  "type-check": "tsc --noEmit && tsc --noEmit -p tsconfig.tests.json",
46
- "build": "tsc",
47
- "test": "tsx --test tests/*.test.ts"
71
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
72
+ "test": "tsx --test tests/*.test.ts",
73
+ "codegen": "openapi-typescript ../docs/openapi/v1.yaml -o ./src/generated/openapi.ts",
74
+ "codegen:check": "openapi-typescript ../docs/openapi/v1.yaml -o /tmp/rcrt-openapi-check.ts && diff -q ./src/generated/openapi.ts /tmp/rcrt-openapi-check.ts",
75
+ "prepack": "pnpm run build"
48
76
  },
49
77
  "devDependencies": {
50
78
  "@types/node": "^20.0.0",
79
+ "openapi-typescript": "^7.13.0",
51
80
  "tsx": "^4.19.2",
52
81
  "typescript": "~5.6.0"
53
82
  }
package/src/authn.ts DELETED
@@ -1,159 +0,0 @@
1
- /**
2
- * Identity module — `/v1/auth/*` endpoints.
3
- *
4
- * Distinct from the TokenProvider abstraction: this is the server-side
5
- * identity surface that runs _after_ you've presented a bearer token.
6
- * See `packages/docs/guides/02-auth.md`.
7
- */
8
-
9
- import type { FetchContext } from './internal/fetch.js';
10
- import { request } from './internal/fetch.js';
11
-
12
- export interface MeResponse {
13
- user: {
14
- id: string;
15
- email: string;
16
- name?: string;
17
- picture?: string;
18
- };
19
- is_platform_admin: boolean;
20
- organizations: Array<{ id: string; name: string; role: string }>;
21
- tenants: Array<{ id: string; name: string; role: string }>;
22
- active_tenant?: { id: string; name: string; role: string };
23
- permissions?: string[];
24
- grants?: unknown[];
25
- }
26
-
27
- export interface Tenant {
28
- id: string;
29
- name: string;
30
- org_id?: string;
31
- role?: string;
32
- }
33
-
34
- export interface PendingInvitation {
35
- id: string;
36
- org_id?: string | null;
37
- tenant_id?: string | null;
38
- email: string;
39
- role: string;
40
- status: 'pending' | 'accepted' | 'declined' | 'expired' | 'cancelled';
41
- expires_at: string;
42
- invited_by?: { id: string; name?: string };
43
- }
44
-
45
- export class IdentityModule {
46
- constructor(private readonly ctx: FetchContext) {}
47
-
48
- /**
49
- * `GET /v1/auth/me` — returns identity + all accessible workspaces.
50
- *
51
- * Creates the user row on first sign-in. Call this before any other
52
- * RCRT endpoint for brand-new Firebase users.
53
- */
54
- async me(): Promise<MeResponse> {
55
- return request<MeResponse>(this.ctx, '/v1/auth/me', { skipTenant: true });
56
- }
57
-
58
- /** List workspaces the current user is a member of. */
59
- async listTenants(): Promise<Tenant[]> {
60
- const res = await request<Tenant[] | { tenants: Tenant[] }>(this.ctx, '/v1/auth/tenants', {
61
- skipTenant: true,
62
- });
63
- return Array.isArray(res) ? res : (res.tenants ?? []);
64
- }
65
-
66
- /** Confirm workspace membership. Client code is still responsible for setting X-Tenant-ID. */
67
- async selectTenant(tenantId: string): Promise<void> {
68
- await request<void>(this.ctx, `/v1/auth/tenants/${tenantId}/select`, {
69
- method: 'POST',
70
- skipTenant: true,
71
- });
72
- }
73
-
74
- /** `POST /v1/auth/logout` — server-side no-op; included for symmetry + audit. */
75
- async logout(): Promise<void> {
76
- await request<void>(this.ctx, '/v1/auth/logout', {
77
- method: 'POST',
78
- skipTenant: true,
79
- });
80
- }
81
-
82
- /**
83
- * `POST /v1/auth/delete-account` — cascading account deletion.
84
- *
85
- * **NOT YET ON `development`** at publish time — pending a follow-up
86
- * PR. The SDK method exists so consumer code can compile against the
87
- * intended shape; invoking it against a gateway that lacks the
88
- * handler returns 404.
89
- */
90
- async deleteAccount(): Promise<void> {
91
- await request<void>(this.ctx, '/v1/auth/delete-account', {
92
- method: 'POST',
93
- skipTenant: true,
94
- });
95
- }
96
-
97
- /** Pending invitations keyed by the caller's email. */
98
- async listPendingInvitations(): Promise<PendingInvitation[]> {
99
- const res = await request<
100
- PendingInvitation[] | { invitations: PendingInvitation[]; total?: number }
101
- >(this.ctx, '/v1/invitations/pending');
102
- return Array.isArray(res) ? res : (res.invitations ?? []);
103
- }
104
-
105
- async acceptInvitation(token: string): Promise<void> {
106
- await request<void>(this.ctx, '/v1/invitations/accept', {
107
- method: 'POST',
108
- body: { token },
109
- });
110
- }
111
-
112
- async declineInvitation(invitationId: string): Promise<void> {
113
- await request<void>(this.ctx, `/v1/invitations/${invitationId}/decline`, {
114
- method: 'POST',
115
- });
116
- }
117
-
118
- /**
119
- * `GET /v1/user/profile` — workspace-scoped user profile breadcrumb.
120
- *
121
- * Distinct from `me()` — this is the user's editable profile content
122
- * (name, timezone, preferences) rather than their identity envelope.
123
- * Returns `null` when no profile has been written yet (404 swallowed).
124
- */
125
- async getUserProfile(): Promise<UserProfileBreadcrumb | null> {
126
- try {
127
- return await request<UserProfileBreadcrumb>(this.ctx, '/v1/user/profile');
128
- } catch (err) {
129
- // 404 is the no-profile-yet case — caller renders the onboarding flow.
130
- if (err && typeof err === 'object' && 'status' in err && (err as { status: number }).status === 404) {
131
- return null;
132
- }
133
- throw err;
134
- }
135
- }
136
-
137
- /**
138
- * `PUT /v1/user/profile` — upsert. Body fields land on the
139
- * underlying breadcrumb's `content` payload.
140
- */
141
- async updateUserProfile(
142
- patch: Record<string, unknown>,
143
- ): Promise<UserProfileBreadcrumb> {
144
- return request<UserProfileBreadcrumb>(this.ctx, '/v1/user/profile', {
145
- method: 'PUT',
146
- body: patch,
147
- });
148
- }
149
- }
150
-
151
- export interface UserProfileBreadcrumb {
152
- id: string;
153
- title?: string;
154
- content: Record<string, unknown>;
155
- tags: string[];
156
- version: number;
157
- created_at?: string;
158
- updated_at?: string;
159
- }
@@ -1,111 +0,0 @@
1
- /**
2
- * Breadcrumbs module — CRUD + tag query + semantic search.
3
- *
4
- * See `packages/docs/guides/04-breadcrumbs.md` for the narrative
5
- * description of each operation.
6
- */
7
-
8
- import type { FetchContext } from './internal/fetch.js';
9
- import { request } from './internal/fetch.js';
10
- import type {
11
- Breadcrumb,
12
- BreadcrumbResponse,
13
- CreateBreadcrumbRequest,
14
- UpdateBreadcrumbRequest,
15
- QueryByTagsOptions,
16
- SemanticSearchOptions,
17
- } from './types/breadcrumb.js';
18
- import { ApiError, SdkError } from './errors.js';
19
-
20
- export class BreadcrumbsModule {
21
- constructor(private readonly ctx: FetchContext) {}
22
-
23
- /** `POST /v1/breadcrumbs` */
24
- async create(req: CreateBreadcrumbRequest): Promise<Breadcrumb> {
25
- const res = await request<BreadcrumbResponse | Breadcrumb>(this.ctx, '/v1/breadcrumbs', {
26
- method: 'POST',
27
- body: req,
28
- });
29
- return 'breadcrumb' in (res as BreadcrumbResponse)
30
- ? (res as BreadcrumbResponse).breadcrumb
31
- : (res as Breadcrumb);
32
- }
33
-
34
- /** `GET /v1/breadcrumbs?tags=...` — AND semantics. */
35
- async queryByTags(tags: string[], options: QueryByTagsOptions = {}): Promise<Breadcrumb[]> {
36
- if (tags.length === 0) {
37
- throw new SdkError('EMPTY_TAGS', 'queryByTags requires at least one tag');
38
- }
39
- const res = await request<Breadcrumb[] | { breadcrumbs: Breadcrumb[] }>(this.ctx, '/v1/breadcrumbs', {
40
- query: {
41
- tags: tags.join(','),
42
- limit: options.limit,
43
- offset: options.offset,
44
- name: options.name,
45
- order: options.order,
46
- },
47
- });
48
- return Array.isArray(res) ? res : (res.breadcrumbs ?? []);
49
- }
50
-
51
- /** `GET /v1/breadcrumbs/search?q=...` — cosine-similarity semantic search. */
52
- async search(q: string, options: SemanticSearchOptions = {}): Promise<Breadcrumb[]> {
53
- const query: Record<string, string | number | undefined> = {
54
- q,
55
- limit: options.limit,
56
- };
57
- if (options.tags?.length) query.tags = options.tags.join(',');
58
- const res = await request<Breadcrumb[] | { breadcrumbs: Breadcrumb[] }>(this.ctx, '/v1/breadcrumbs/search', { query });
59
- return Array.isArray(res) ? res : (res.breadcrumbs ?? []);
60
- }
61
-
62
- /** `GET /v1/breadcrumbs/{id}` */
63
- async get(id: string): Promise<Breadcrumb> {
64
- return request<Breadcrumb>(this.ctx, `/v1/breadcrumbs/${id}`);
65
- }
66
-
67
- /**
68
- * `PATCH /v1/breadcrumbs/{id}` — optimistic-locking update.
69
- *
70
- * If `req.version` is wrong, the server returns 409. Pass
71
- * `autoRetryConflict: true` to have the SDK refetch + retry once.
72
- */
73
- async update(
74
- id: string,
75
- req: UpdateBreadcrumbRequest,
76
- opts: { autoRetryConflict?: boolean } = {},
77
- ): Promise<Breadcrumb> {
78
- const refetchBeforeRetry = opts.autoRetryConflict
79
- ? async () => {
80
- const fresh = await this.get(id);
81
- return {
82
- body: {
83
- ...req,
84
- version: fresh.version,
85
- } satisfies UpdateBreadcrumbRequest,
86
- };
87
- }
88
- : undefined;
89
-
90
- const res = await request<BreadcrumbResponse | Breadcrumb>(this.ctx, `/v1/breadcrumbs/${id}`, {
91
- method: 'PATCH',
92
- body: req,
93
- ...(refetchBeforeRetry
94
- ? { maxConflictRetries: 2, refetchBeforeRetry }
95
- : {}),
96
- });
97
- return 'breadcrumb' in (res as BreadcrumbResponse)
98
- ? (res as BreadcrumbResponse).breadcrumb
99
- : (res as Breadcrumb);
100
- }
101
-
102
- /** `DELETE /v1/breadcrumbs/{id}` — soft delete. */
103
- async delete(id: string): Promise<void> {
104
- try {
105
- await request<void>(this.ctx, `/v1/breadcrumbs/${id}`, { method: 'DELETE' });
106
- } catch (err) {
107
- if (err instanceof ApiError && err.status === 404) return; // idempotent
108
- throw err;
109
- }
110
- }
111
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * Capabilities module — discover what the current workspace can do.
3
- *
4
- * In RCRT, capabilities are breadcrumbs tagged with `interpret:*`:
5
- * - `interpret:executable` — tools (functions an agent can call)
6
- * - `interpret:promptable` — agents (entities the user / other
7
- * agents can `chat.send()` to)
8
- * - `interpret:service-def` — connectable services (Gmail, Notion,
9
- * etc.) with their OAuth metadata
10
- * - `interpret:knowledge` — static knowledge bases attached to the
11
- * workspace
12
- *
13
- * This module is a thin convenience layer over `breadcrumbs.queryByTags`
14
- * that maps the `type` argument to the right tag.
15
- */
16
-
17
- import type { FetchContext } from './internal/fetch.js';
18
- import { request } from './internal/fetch.js';
19
- import type { Breadcrumb } from './types/breadcrumb.js';
20
-
21
- export type CapabilityType = 'tools' | 'agents' | 'services' | 'knowledge';
22
-
23
- const TAG_BY_TYPE: Record<CapabilityType, string> = {
24
- tools: 'interpret:executable',
25
- agents: 'interpret:promptable',
26
- services: 'interpret:service-def',
27
- knowledge: 'interpret:knowledge',
28
- };
29
-
30
- export interface ChattableAgent {
31
- /** The id you pass to `chat.send({ target_agent: ... })`. */
32
- id: string;
33
- name: string;
34
- description: string;
35
- /** True when this agent has the `interface:chat-default` tag. */
36
- isDefault: boolean;
37
- }
38
-
39
- export class CapabilitiesModule {
40
- constructor(private readonly ctx: FetchContext) {}
41
-
42
- /**
43
- * List every capability of every type. Useful when surfacing the
44
- * full toolbelt + agent roster to a debug / admin view.
45
- */
46
- async listAll(limit: number = 200): Promise<Breadcrumb[]> {
47
- const res = await request<Breadcrumb[] | { breadcrumbs: Breadcrumb[] }>(
48
- this.ctx,
49
- '/v1/breadcrumbs',
50
- { query: { tags: TAG_BY_TYPE.tools, limit } },
51
- );
52
- return Array.isArray(res) ? res : (res.breadcrumbs ?? []);
53
- }
54
-
55
- /** List capabilities of one specific type. */
56
- async list(type: CapabilityType, limit: number = 200): Promise<Breadcrumb[]> {
57
- const tag = TAG_BY_TYPE[type];
58
- const res = await request<Breadcrumb[] | { breadcrumbs: Breadcrumb[] }>(
59
- this.ctx,
60
- '/v1/breadcrumbs',
61
- { query: { tags: tag, limit } },
62
- );
63
- return Array.isArray(res) ? res : (res.breadcrumbs ?? []);
64
- }
65
-
66
- /**
67
- * Convenience: agents the user can chat with directly.
68
- *
69
- * Filters `interpret:promptable` to those tagged `interface:chat`
70
- * or `interface:chat-default`. Falls back to all promptables when
71
- * no agents have a chat interface tag (older deploys).
72
- */
73
- async getChattableAgents(limit: number = 50): Promise<ChattableAgent[]> {
74
- const promptables = await this.list('agents', limit);
75
- const chatAgents = promptables.filter((bc) =>
76
- bc.tags?.some(
77
- (t) => t === 'interface:chat' || t === 'interface:chat-default',
78
- ),
79
- );
80
- const source = chatAgents.length > 0 ? chatAgents : promptables;
81
- return source.map<ChattableAgent>((bc) => {
82
- const content = (bc.content ?? {}) as Record<string, unknown>;
83
- const id = (bc as { name?: string }).name ?? bc.id;
84
- const name =
85
- (content.name as string | undefined) ??
86
- (bc as { title?: string }).title ??
87
- id;
88
- const description = (content.description as string | undefined) ?? '';
89
- const isDefault = !!bc.tags?.includes('interface:chat-default');
90
- return { id, name, description, isDefault };
91
- });
92
- }
93
- }
package/src/cards.ts DELETED
@@ -1,109 +0,0 @@
1
- /**
2
- * Cards module — resolve an `interpret:pending-action` breadcrumb.
3
- *
4
- * The card's footer action ids correspond to values the SDK writes
5
- * to `content.status`. See
6
- * `packages/docs/guides/06-rendering-cards.md`.
7
- */
8
-
9
- import type { FetchContext } from './internal/fetch.js';
10
- import { request } from './internal/fetch.js';
11
- import type { Breadcrumb } from './types/breadcrumb.js';
12
- import type { Card, ResolveRequest } from './types/card.js';
13
-
14
- export class CardsModule {
15
- constructor(private readonly ctx: FetchContext) {}
16
-
17
- /**
18
- * PATCH the card breadcrumb with a resolution. Handles optimistic
19
- * locking transparently — refetches + retries on 409.
20
- */
21
- async resolve(breadcrumbId: string, resolution: ResolveRequest): Promise<Breadcrumb> {
22
- const current = await request<Breadcrumb>(this.ctx, `/v1/breadcrumbs/${breadcrumbId}`);
23
- const nextContent = {
24
- ...(current.content ?? {}),
25
- status: resolution.status,
26
- user_response: resolution.user_response,
27
- resolved_at: new Date().toISOString(),
28
- };
29
- const nextTags = ensureStatusTag(current.tags, resolution.status);
30
- return request<Breadcrumb>(this.ctx, `/v1/breadcrumbs/${breadcrumbId}`, {
31
- method: 'PATCH',
32
- body: {
33
- version: current.version,
34
- content: nextContent,
35
- tags: nextTags,
36
- },
37
- maxConflictRetries: 2,
38
- refetchBeforeRetry: async () => {
39
- const fresh = await request<Breadcrumb>(this.ctx, `/v1/breadcrumbs/${breadcrumbId}`);
40
- return {
41
- body: {
42
- version: fresh.version,
43
- content: {
44
- ...(fresh.content ?? {}),
45
- status: resolution.status,
46
- user_response: resolution.user_response,
47
- resolved_at: new Date().toISOString(),
48
- },
49
- tags: ensureStatusTag(fresh.tags, resolution.status),
50
- },
51
- };
52
- },
53
- });
54
- }
55
-
56
- /** Pending cards for the current user's current workspace. */
57
- async listPending(limit = 50): Promise<Breadcrumb[]> {
58
- const list = await request<Breadcrumb[] | { breadcrumbs: Breadcrumb[] }>(this.ctx, '/v1/breadcrumbs', {
59
- query: { tags: 'interpret:pending-action,status:pending', limit },
60
- });
61
- return Array.isArray(list) ? list : (list.breadcrumbs ?? []);
62
- }
63
-
64
- /**
65
- * Extract the Card object from a breadcrumb's content, normalising
66
- * over legacy shapes (old breadcrumbs stored `card.type` + flat
67
- * fields instead of `card.layout` + structured body).
68
- */
69
- static extractCard(bc: Breadcrumb): Card | undefined {
70
- const content = (bc.content ?? {}) as Record<string, unknown>;
71
- const raw = (content.card as Record<string, unknown> | undefined) ?? undefined;
72
- if (!raw) return undefined;
73
- if (typeof raw.layout === 'string') return raw as unknown as Card;
74
- // Legacy — fall back to a minimal info card.
75
- if (typeof raw.type === 'string') {
76
- const card: Card = {
77
- layout: legacyTypeToLayout(raw.type as string),
78
- header: { title: typeof content.title === 'string' ? content.title : bc.title },
79
- };
80
- if (typeof content.summary === 'string') {
81
- card.body = { text: content.summary };
82
- }
83
- return card;
84
- }
85
- return undefined;
86
- }
87
- }
88
-
89
- function ensureStatusTag(existing: string[], status: string): string[] {
90
- const withoutStatus = existing.filter((t) => !t.startsWith('status:'));
91
- withoutStatus.push(`status:${status}`);
92
- return withoutStatus;
93
- }
94
-
95
- function legacyTypeToLayout(type: string): Card['layout'] {
96
- switch (type) {
97
- case 'connect':
98
- return 'connect';
99
- case 'confirm':
100
- case 'choice':
101
- case 'multi-choice':
102
- case 'approval':
103
- return 'decision';
104
- case 'text-input':
105
- return 'input';
106
- default:
107
- return 'info';
108
- }
109
- }
package/src/chat.ts DELETED
@@ -1,83 +0,0 @@
1
- /**
2
- * Chat module — `POST /v1/chat` + per-session / global SSE streams.
3
- *
4
- * See `packages/docs/guides/03-chat-and-sse.md`.
5
- */
6
-
7
- import type { FetchContext } from './internal/fetch.js';
8
- import { request } from './internal/fetch.js';
9
- import type { SseConnection, SseConnectConfig, SseHandlers } from './internal/sse.js';
10
- import { connect as sseConnect } from './internal/sse.js';
11
-
12
- export interface SendChatRequest {
13
- message: string;
14
- /** The agent to route this turn to. `life-coordinator` by default in the Ritual bundle. */
15
- target_agent: string;
16
- /** Resume an existing session; omit to start a new one. */
17
- session_id?: string;
18
- /** Extra tags stamped on the user's message breadcrumb. */
19
- extra_tags?: string[];
20
- }
21
-
22
- export interface SendChatResponse {
23
- id: string;
24
- session_id: string;
25
- }
26
-
27
- export interface SseStreamOptions {
28
- /** Override the EventSource constructor (required in React Native). */
29
- eventSource?: SseConnectConfig['eventSource'];
30
- useHeaderAuth?: boolean;
31
- maxBackoffMs?: number;
32
- /** Supply a Last-Event-ID-style resume token if your server supports it. */
33
- last_event_id?: string;
34
- }
35
-
36
- export class ChatModule {
37
- constructor(private readonly ctx: FetchContext) {}
38
-
39
- /**
40
- * Send a user message to an agent. Returns immediately with the
41
- * breadcrumb id + session id. The agent reply arrives via the SSE
42
- * stream — call `stream(session_id)` before posting, not after.
43
- */
44
- async send(req: SendChatRequest): Promise<SendChatResponse> {
45
- return request<SendChatResponse>(this.ctx, '/v1/chat', {
46
- method: 'POST',
47
- body: req,
48
- });
49
- }
50
-
51
- /** Per-session SSE — just this session's events. Recommended for chat views. */
52
- sessionStream(sessionId: string, handlers: SseHandlers, options: SseStreamOptions = {}): SseConnection {
53
- return sseConnect(this.buildConfig(`/v1/sessions/${sessionId}/stream`, options), handlers);
54
- }
55
-
56
- /** Global SSE — everything the user can see. Useful for home feeds / awaiting-you. */
57
- globalStream(handlers: SseHandlers, options: SseStreamOptions = {}): SseConnection {
58
- return sseConnect(this.buildConfig('/v1/events', options), handlers);
59
- }
60
-
61
- private buildConfig(path: string, options: SseStreamOptions): SseConnectConfig {
62
- const config: SseConnectConfig = {
63
- apiUrl: this.ctx.apiUrl,
64
- path,
65
- tenantId: this.ctx.tenantId,
66
- getToken: () => this.ctx.tokenProvider.getIdToken(),
67
- };
68
- if (options.eventSource) config.eventSource = options.eventSource;
69
- if (options.useHeaderAuth !== undefined) config.useHeaderAuth = options.useHeaderAuth;
70
- if (options.maxBackoffMs !== undefined) config.maxBackoffMs = options.maxBackoffMs;
71
- if (options.last_event_id) config.query = { last_event_id: options.last_event_id };
72
- return config;
73
- }
74
-
75
- /**
76
- * Clear a session's suspend flag after the loop detector fired.
77
- *
78
- * @see `packages/docs/operations/loop-detector.md`
79
- */
80
- async resumeSession(sessionId: string): Promise<void> {
81
- await request<void>(this.ctx, `/v1/sessions/${sessionId}/resume`, { method: 'POST' });
82
- }
83
- }