@gravity-rail/sdk 0.46.0 → 0.49.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 +116 -8
- package/dist/agui.cjs +17 -2
- package/dist/agui.cjs.map +1 -1
- package/dist/agui.d.cts +9 -1
- package/dist/agui.d.ts +9 -1
- package/dist/agui.js +15 -1
- package/dist/agui.js.map +1 -1
- package/dist/{chat-interaction-0MSzmdxz.d.cts → chat-interaction-D4Edo92W.d.ts} +273 -48
- package/dist/{chat-interaction-CO9ecTSn.d.ts → chat-interaction-e5vHKt5j.d.cts} +273 -48
- package/dist/{chunk-72SWNLVD.js → chunk-MMDSZ25F.js} +1 -1
- package/dist/chunk-MMDSZ25F.js.map +1 -0
- package/dist/{chunk-67QXTQVL.js → chunk-TGL72KPS.js} +59 -118
- package/dist/chunk-TGL72KPS.js.map +1 -0
- package/dist/{chunk-I7BMDNPO.js → chunk-XQTPXOBU.js} +249 -50
- package/dist/chunk-XQTPXOBU.js.map +1 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +4 -4
- package/dist/helpers.d.ts +4 -4
- package/dist/helpers.js +1 -1
- package/dist/index.cjs +307 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +307 -164
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +148 -6
- package/dist/internal.d.ts +148 -6
- package/dist/internal.js +9 -3
- package/dist/internal.js.map +1 -1
- package/dist/{member-import-D7U4kDIy.d.cts → member-import-9zHkUjcn.d.cts} +1 -1
- package/dist/{member-import-C2Xt_o5d.d.ts → member-import-ocJ_K474.d.ts} +1 -1
- package/dist/{revision-state-Cc3-CIi5.d.ts → path-CQBykGoj.d.cts} +64 -3
- package/dist/{revision-state-m6Cjyr4M.d.cts → path-DVWLvQS-.d.ts} +64 -3
- package/dist/schemas.cjs +60 -15
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +34 -3
- package/dist/schemas.d.ts +34 -3
- package/dist/schemas.js +60 -16
- package/dist/schemas.js.map +1 -1
- package/dist/{tasks-Ca6yfsj2.d.cts → tasks-DsNsbco5.d.cts} +66 -14
- package/dist/{tasks-Ca6yfsj2.d.ts → tasks-DsNsbco5.d.ts} +66 -14
- package/package.json +2 -2
- package/dist/chunk-67QXTQVL.js.map +0 -1
- package/dist/chunk-72SWNLVD.js.map +0 -1
- package/dist/chunk-I7BMDNPO.js.map +0 -1
package/README.md
CHANGED
|
@@ -35,9 +35,10 @@ const client = new GravityRailClient(
|
|
|
35
35
|
'https://api.gravityrail.com'
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
-
const workspaces = await client.getWorkspaces();
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const workspaces = await client.getWorkspaces({ page: 1, pageSize: 20, search: 'clinic' });
|
|
39
|
+
// Read workspaces.items; request the next page explicitly when needed.
|
|
40
|
+
if (workspaces.items.length === 0) throw new Error('No workspace on this page; search or request another page');
|
|
41
|
+
const workspaceId = workspaces.items[0].uuid;
|
|
41
42
|
const workflows = await client.getWorkflows(workspaceId);
|
|
42
43
|
const chats = await client.getChats(workspaceId);
|
|
43
44
|
```
|
|
@@ -96,17 +97,29 @@ Select the required scopes and expiry using the [granular scope system](#scopes-
|
|
|
96
97
|
|
|
97
98
|
### OAuth2 with PKCE
|
|
98
99
|
|
|
99
|
-
For browser-based applications:
|
|
100
|
+
For browser-based applications on your own origin, complete the PKCE flow and pass the access token it returns as the client's credential — it is sent as `Authorization: Bearer`:
|
|
100
101
|
|
|
101
102
|
```typescript
|
|
102
|
-
const client = new GravityRailClient(
|
|
103
|
+
const client = new GravityRailClient(accessToken, 'https://api.gravityrail.com');
|
|
103
104
|
|
|
104
105
|
// Handle step-up authentication when the API requires it
|
|
105
106
|
client.setEnhancedAuthHandler(async (authInfo) => {
|
|
106
|
-
// Redirect to login or show auth modal
|
|
107
|
+
// Redirect to login or show auth modal; resolve with the new token
|
|
107
108
|
});
|
|
108
109
|
```
|
|
109
110
|
|
|
111
|
+
### Same-origin session
|
|
112
|
+
|
|
113
|
+
A client built **without** a credential is a same-origin session client: it sends no `Authorization` header and fetches with `credentials: 'same-origin'`, so the browser presents the httpOnly session cookie of the origin the page is on, and only to that origin. It never reads a cookie to build a credential. This is how Gravity Rail's own web app calls the API (it serves `/api/v2` on its own origin); point `baseUrl` at the page's origin:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const client = new GravityRailClient(undefined, window.location.origin);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A cross-origin `baseUrl` with no credential makes unauthenticated calls.
|
|
120
|
+
|
|
121
|
+
When an organization requires SSO, a 401 carrying `WWW-Authenticate: Bearer realm="org"` calls your `setOrgAuthHandler` handler once. A bearer client's handler must hand back the org session with `client.setOrgSession(session, orgUuid)` (sent as `X-Org-Session`); a same-origin session client is retried once after the handler resolves, carrying the httpOnly org-session cookie the sign-in left.
|
|
122
|
+
|
|
110
123
|
## Core Concepts
|
|
111
124
|
|
|
112
125
|
Every Gravity Rail workspace is a self-contained environment with isolated data, workflows, and configuration. The SDK organizes its 500+ methods by domain:
|
|
@@ -352,6 +365,8 @@ const operators = await client.getLiveOperators(workspaceId);
|
|
|
352
365
|
|
|
353
366
|
### Sites & Portals
|
|
354
367
|
|
|
368
|
+
Site record write methods may return a successful value-free receipt: `fieldValues` is empty and `memberId` / `memberName` / `externalId` are null unless Reading Records, level-2 sign-in verification, and the row read policy all allow values. This includes partial updates, resubmissions and upserts. The write still succeeds; use a permitted record read to load stored values.
|
|
369
|
+
|
|
355
370
|
```typescript
|
|
356
371
|
// Build customer-facing portals with AI chat built in
|
|
357
372
|
const site = await client.createSite(workspaceId, {
|
|
@@ -372,13 +387,42 @@ await client.crawlSite(workspaceId, site.id, { url: 'https://docs.example.com' }
|
|
|
372
387
|
|
|
373
388
|
### Workspace Management
|
|
374
389
|
|
|
390
|
+
Workspace and organization directories return `{ items, page, pageSize, total, totalPages }`. Read
|
|
391
|
+
`items` instead of treating the response as an array. Pass `page`, `pageSize` (at
|
|
392
|
+
most 100), and `search` to request a page. This applies to `getWorkspaces`,
|
|
393
|
+
`getDeviceAdmissibleWorkspaces`, `getPublicWorkspaces`, `getMyOrganizations`, and
|
|
394
|
+
`getOrganizationWorkspaceSummaries`.
|
|
395
|
+
Organization member details no longer embed workspace lists; request organization
|
|
396
|
+
workspace summary pages instead.
|
|
397
|
+
`getMyOrganizations` also accepts `organizationSlug` for exact host resolution
|
|
398
|
+
and `organizationUuids` (at most 100 UUIDs) for a bounded membership lookup. These
|
|
399
|
+
filters only narrow the current account's memberships.
|
|
400
|
+
Account and device workspace totals count eligible global membership candidates;
|
|
401
|
+
workspace admission can make a page sparse or empty. Use pagination metadata to
|
|
402
|
+
reach later candidates.
|
|
403
|
+
|
|
404
|
+
`getOrgWorkspaceUsage(orgUuid, { page, pageSize, search })` returns a bounded
|
|
405
|
+
`workspaces` list with `page`, `pageSize`, `total`, and `totalPages` metadata.
|
|
406
|
+
Organization usage totals remain global and can be null when measurement coverage
|
|
407
|
+
is incomplete. Do not sum the visible cards to calculate an organization total.
|
|
408
|
+
|
|
375
409
|
```typescript
|
|
376
410
|
// Workspaces this credential can reach
|
|
377
|
-
const workspaces = await client.getWorkspaces();
|
|
411
|
+
const workspaces = await client.getWorkspaces({ page: 1, pageSize: 20, search: 'clinic' });
|
|
412
|
+
// Read workspaces.items; request the next page explicitly when needed.
|
|
378
413
|
|
|
379
414
|
// Companion-device picker (Lantern): compact rows for only the
|
|
380
415
|
// workspaces a paired device could enter — no device credential needed
|
|
381
|
-
const deviceWorkspaces = await client.getDeviceAdmissibleWorkspaces(
|
|
416
|
+
const deviceWorkspaces = await client.getDeviceAdmissibleWorkspaces({
|
|
417
|
+
page: 1, pageSize: 20, search: 'clinic',
|
|
418
|
+
});
|
|
419
|
+
// Read deviceWorkspaces.items; a sparse page can still have later candidates.
|
|
420
|
+
|
|
421
|
+
// Public workspace directory
|
|
422
|
+
const publicWorkspaces = await client.getPublicWorkspaces({
|
|
423
|
+
page: 1, pageSize: 20, search: 'clinic',
|
|
424
|
+
});
|
|
425
|
+
// Read publicWorkspaces.items and use total/page/pageSize for pagination.
|
|
382
426
|
|
|
383
427
|
// Export/import full workspace configuration
|
|
384
428
|
const config = await client.exportWorkspace(workspaceId);
|
|
@@ -393,6 +437,70 @@ const clientWs = await client.createClientWorkspace(workspaceId, {
|
|
|
393
437
|
await client.provisionClientWorkspace(workspaceId, clientWs.id);
|
|
394
438
|
```
|
|
395
439
|
|
|
440
|
+
## Export Workflow revisions
|
|
441
|
+
|
|
442
|
+
Workspace JSON and ZIP exports default to active snapshots. Request
|
|
443
|
+
`revisionMode: 'all'` to include historical revisions and drafts. The version `2.0` archive carries isolated
|
|
444
|
+
`workflowRevisions` graphs and requires a revision-aware server for import. Import
|
|
445
|
+
it as new Workflows to preserve active and draft roles; an existing Workflow match
|
|
446
|
+
is refused before writes. Legacy version `1.0` imports keep their existing behavior.
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
const archive = await client.exportWorkspace(workspaceUuid, undefined, undefined, undefined, {
|
|
450
|
+
revisionMode: 'all',
|
|
451
|
+
});
|
|
452
|
+
const selected = await client.exportWorkspace(workspaceUuid, workflowId, undefined, undefined, {
|
|
453
|
+
revisionId: 87,
|
|
454
|
+
});
|
|
455
|
+
const active = await client.exportWorkspace(workspaceUuid, undefined, undefined, undefined, {
|
|
456
|
+
revisionMode: 'active',
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
The same options apply to `exportWorkspaceZip`. A selected revision requires a
|
|
461
|
+
Workflow ID and cannot be combined with active mode. Selected and active snapshots
|
|
462
|
+
import into an editable draft and do not change an existing active revision.
|
|
463
|
+
|
|
464
|
+
The public workspace export API supports only the `channels` exclusion in every
|
|
465
|
+
revision mode. All-revision archives preserve that exclusion. Other public
|
|
466
|
+
exclusion values are rejected.
|
|
467
|
+
|
|
468
|
+
The SDK throws when a legacy server returns an active-only snapshot for an
|
|
469
|
+
all-revision or selected export.
|
|
470
|
+
JSON validation checks selection metadata and internal Workflow/revision identities.
|
|
471
|
+
ZIP validation checks provenance headers generated from the actual exported data,
|
|
472
|
+
before reading the archive. It adds no archive size limit or permission requirement.
|
|
473
|
+
Deploy a server with ZIP provenance headers before releasing this client update;
|
|
474
|
+
servers with revision-aware JSON alone still fail the ZIP guard. Explicit
|
|
475
|
+
`revisionMode: 'active'` exports remain compatible with legacy servers.
|
|
476
|
+
|
|
477
|
+
ZIP provenance uses `X-Gravityrail-Export-Version` (`2.0` for all revisions,
|
|
478
|
+
`1.0` for active and selected snapshots) and `X-Gravityrail-Export-Selection`
|
|
479
|
+
(`active`, `all`, or `selected`). A selected ZIP also includes nonempty
|
|
480
|
+
`X-Gravityrail-Export-Revision-Uuid` and `X-Gravityrail-Export-Workflow-Uuid`.
|
|
481
|
+
These values come from the exported artifact. Explicit active exports do not
|
|
482
|
+
require these headers.
|
|
483
|
+
|
|
484
|
+
Shared dependency definitions are current exported Forms, files, and integrations,
|
|
485
|
+
not historical snapshots of those resources.
|
|
486
|
+
|
|
487
|
+
## Chat history (`@gravity-rail/sdk/agui`)
|
|
488
|
+
|
|
489
|
+
`mergeChatHistoryWindow(existing, fetched)` merges an authoritative chronological
|
|
490
|
+
history window into an existing transcript. If the first fetched ID appears in
|
|
491
|
+
the existing transcript, the helper retains only the earlier prefix and replaces
|
|
492
|
+
the covered tail. It removes prefix entries whose IDs also appear in the fetched
|
|
493
|
+
window; it does not remove repeated IDs within the retained prefix. If the first
|
|
494
|
+
ID is unknown, or the fetched window is empty, the result is the fetched window alone.
|
|
495
|
+
Message text does not identify a message. Fetch the needed history pages before
|
|
496
|
+
calling this helper; it does not fetch or filter messages.
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
import { mergeChatHistoryWindow } from '@gravity-rail/sdk/agui';
|
|
500
|
+
|
|
501
|
+
const messages = mergeChatHistoryWindow(existingMessages, fetchedMessages);
|
|
502
|
+
```
|
|
503
|
+
|
|
396
504
|
## Helpers (`@gravity-rail/sdk/helpers`)
|
|
397
505
|
|
|
398
506
|
Small compound operations built on the client methods, for the common
|
package/dist/agui.cjs
CHANGED
|
@@ -24,7 +24,8 @@ __export(agui_exports, {
|
|
|
24
24
|
getAguiTextMessageContent: () => getAguiTextMessageContent,
|
|
25
25
|
getAguiTextMessageId: () => getAguiTextMessageId,
|
|
26
26
|
getAguiTextMessageMemberId: () => getAguiTextMessageMemberId,
|
|
27
|
-
getAguiTextMessageRole: () => getAguiTextMessageRole
|
|
27
|
+
getAguiTextMessageRole: () => getAguiTextMessageRole,
|
|
28
|
+
mergeChatHistoryWindow: () => mergeChatHistoryWindow
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(agui_exports);
|
|
30
31
|
|
|
@@ -79,12 +80,26 @@ function applyAguiTextMessageEvent(messages, event) {
|
|
|
79
80
|
}
|
|
80
81
|
return false;
|
|
81
82
|
}
|
|
83
|
+
|
|
84
|
+
// src/agui/chat-history.ts
|
|
85
|
+
function mergeChatHistoryWindow(existingMessages, fetchedMessages) {
|
|
86
|
+
const joinsAt = fetchedMessages.length > 0 ? existingMessages.findIndex((msg) => msg.id === fetchedMessages[0].id) : -1;
|
|
87
|
+
if (joinsAt > 0) {
|
|
88
|
+
const inPage = new Set(fetchedMessages.map((msg) => msg.id));
|
|
89
|
+
return [
|
|
90
|
+
...existingMessages.slice(0, joinsAt).filter((msg) => !inPage.has(msg.id)),
|
|
91
|
+
...fetchedMessages
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
return fetchedMessages;
|
|
95
|
+
}
|
|
82
96
|
// Annotate the CommonJS export names for ESM import in node:
|
|
83
97
|
0 && (module.exports = {
|
|
84
98
|
applyAguiTextMessageEvent,
|
|
85
99
|
getAguiTextMessageContent,
|
|
86
100
|
getAguiTextMessageId,
|
|
87
101
|
getAguiTextMessageMemberId,
|
|
88
|
-
getAguiTextMessageRole
|
|
102
|
+
getAguiTextMessageRole,
|
|
103
|
+
mergeChatHistoryWindow
|
|
89
104
|
});
|
|
90
105
|
//# sourceMappingURL=agui.cjs.map
|
package/dist/agui.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agui/index.ts","../src/agui/text-messages.ts"],"sourcesContent":["export {\n applyAguiTextMessageEvent,\n getAguiTextMessageContent,\n getAguiTextMessageId,\n getAguiTextMessageMemberId,\n getAguiTextMessageRole,\n type TranscriptMessage,\n} from './text-messages';\n","import type { ChannelEvent } from '../realtime/types';\n\nexport interface TranscriptMessage {\n id: string;\n role: string;\n content: string;\n /** Authoring member id when TEXT_MESSAGE_START carried it (Gravity extension). */\n memberId?: number;\n}\n\nexport function getAguiTextMessageId(event: ChannelEvent): string | null {\n const raw = event.messageId ?? event.message_id;\n return typeof raw === 'string' && raw.length > 0 ? raw : null;\n}\n\nexport function getAguiTextMessageContent(event: ChannelEvent): string | null {\n const raw = event.content ?? event.text ?? event.delta;\n return typeof raw === 'string' ? raw : null;\n}\n\nexport function getAguiTextMessageRole(event: ChannelEvent, fallback = 'assistant'): string {\n const raw = event.role;\n return typeof raw === 'string' && raw.length > 0 ? raw : fallback;\n}\n\n/** Gravity extension: authoring member on TEXT_MESSAGE_START (camel or snake). */\nexport function getAguiTextMessageMemberId(event: ChannelEvent): number | undefined {\n const raw = event.memberId ?? event.member_id;\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;\n}\n\nexport function applyAguiTextMessageEvent(\n messages: Map<string, TranscriptMessage>,\n event: ChannelEvent\n): boolean {\n const type = event.type;\n if (typeof type !== 'string') return false;\n\n if (type === 'TEXT_MESSAGE_START') {\n const messageId = getAguiTextMessageId(event);\n if (!messageId) return false;\n const role = getAguiTextMessageRole(event);\n const memberId = getAguiTextMessageMemberId(event);\n messages.set(messageId, {\n id: messageId,\n role,\n content: '',\n ...(memberId !== undefined ? { memberId } : {}),\n });\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_CONTENT') {\n const messageId = getAguiTextMessageId(event);\n const content = getAguiTextMessageContent(event);\n if (!messageId || content === null) return false;\n\n // CONTENT does not carry memberId per protocol — only START does.\n // Synthesize a bare message if START was missed; leave memberId unset.\n const existing =\n messages.get(messageId) ??\n ({\n id: messageId,\n role: getAguiTextMessageRole(event),\n content: '',\n } satisfies TranscriptMessage);\n\n existing.content = content;\n messages.set(messageId, existing);\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_END') {\n return Boolean(getAguiTextMessageId(event));\n }\n\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACUO,SAAS,qBAAqB,OAAoC;AACtE,QAAM,MAAM,MAAM,aAAa,MAAM;AACrC,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BAA0B,OAAoC;AAC3E,QAAM,MAAM,MAAM,WAAW,MAAM,QAAQ,MAAM;AACjD,SAAO,OAAO,QAAQ,WAAW,MAAM;AAC1C;AAEO,SAAS,uBAAuB,OAAqB,WAAW,aAAqB;AACzF,QAAM,MAAM,MAAM;AAClB,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAGO,SAAS,2BAA2B,OAAyC;AACjF,QAAM,MAAM,MAAM,YAAY,MAAM;AACpC,SAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,IAAI,MAAM;AAClE;AAEO,SAAS,0BACb,UACA,OACQ;AACR,QAAM,OAAO,MAAM;AACnB,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,MAAI,SAAS,sBAAsB;AAChC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,OAAO,uBAAuB,KAAK;AACzC,UAAM,WAAW,2BAA2B,KAAK;AACjD,aAAS,IAAI,WAAW;AAAA,MACrB,IAAI;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,MACT,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,IAChD,CAAC;AACD,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,wBAAwB;AAClC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,UAAM,UAAU,0BAA0B,KAAK;AAC/C,QAAI,CAAC,aAAa,YAAY,KAAM,QAAO;AAI3C,UAAM,WACH,SAAS,IAAI,SAAS,KACrB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,uBAAuB,KAAK;AAAA,MAClC,SAAS;AAAA,IACZ;AAEH,aAAS,UAAU;AACnB,aAAS,IAAI,WAAW,QAAQ;AAChC,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,oBAAoB;AAC9B,WAAO,QAAQ,qBAAqB,KAAK,CAAC;AAAA,EAC7C;AAEA,SAAO;AACV;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/agui/index.ts","../src/agui/text-messages.ts","../src/agui/chat-history.ts"],"sourcesContent":["export {\n applyAguiTextMessageEvent,\n getAguiTextMessageContent,\n getAguiTextMessageId,\n getAguiTextMessageMemberId,\n getAguiTextMessageRole,\n type TranscriptMessage,\n} from './text-messages';\nexport { mergeChatHistoryWindow } from './chat-history';\n","import type { ChannelEvent } from '../realtime/types';\n\nexport interface TranscriptMessage {\n id: string;\n role: string;\n content: string;\n /** Authoring member id when TEXT_MESSAGE_START carried it (Gravity extension). */\n memberId?: number;\n}\n\nexport function getAguiTextMessageId(event: ChannelEvent): string | null {\n const raw = event.messageId ?? event.message_id;\n return typeof raw === 'string' && raw.length > 0 ? raw : null;\n}\n\nexport function getAguiTextMessageContent(event: ChannelEvent): string | null {\n const raw = event.content ?? event.text ?? event.delta;\n return typeof raw === 'string' ? raw : null;\n}\n\nexport function getAguiTextMessageRole(event: ChannelEvent, fallback = 'assistant'): string {\n const raw = event.role;\n return typeof raw === 'string' && raw.length > 0 ? raw : fallback;\n}\n\n/** Gravity extension: authoring member on TEXT_MESSAGE_START (camel or snake). */\nexport function getAguiTextMessageMemberId(event: ChannelEvent): number | undefined {\n const raw = event.memberId ?? event.member_id;\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;\n}\n\nexport function applyAguiTextMessageEvent(\n messages: Map<string, TranscriptMessage>,\n event: ChannelEvent\n): boolean {\n const type = event.type;\n if (typeof type !== 'string') return false;\n\n if (type === 'TEXT_MESSAGE_START') {\n const messageId = getAguiTextMessageId(event);\n if (!messageId) return false;\n const role = getAguiTextMessageRole(event);\n const memberId = getAguiTextMessageMemberId(event);\n messages.set(messageId, {\n id: messageId,\n role,\n content: '',\n ...(memberId !== undefined ? { memberId } : {}),\n });\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_CONTENT') {\n const messageId = getAguiTextMessageId(event);\n const content = getAguiTextMessageContent(event);\n if (!messageId || content === null) return false;\n\n // CONTENT does not carry memberId per protocol — only START does.\n // Synthesize a bare message if START was missed; leave memberId unset.\n const existing =\n messages.get(messageId) ??\n ({\n id: messageId,\n role: getAguiTextMessageRole(event),\n content: '',\n } satisfies TranscriptMessage);\n\n existing.content = content;\n messages.set(messageId, existing);\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_END') {\n return Boolean(getAguiTextMessageId(event));\n }\n\n return false;\n}\n","/** Keep loaded messages before a fetched window; the fetched tail is authoritative.\n * With no shared first message, the windows do not join and the fetched page stands alone.\n * Extracted from the main app's useChatMessages cache merge.\n */\nexport function mergeChatHistoryWindow<T extends { id: string }>(\n existingMessages: T[],\n fetchedMessages: T[]\n): T[] {\n const joinsAt =\n fetchedMessages.length > 0\n ? existingMessages.findIndex((msg) => msg.id === fetchedMessages[0].id)\n : -1;\n if (joinsAt > 0) {\n const inPage = new Set(fetchedMessages.map((msg) => msg.id));\n return [\n ...existingMessages.slice(0, joinsAt).filter((msg) => !inPage.has(msg.id)),\n ...fetchedMessages,\n ];\n }\n return fetchedMessages;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACUO,SAAS,qBAAqB,OAAoC;AACtE,QAAM,MAAM,MAAM,aAAa,MAAM;AACrC,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BAA0B,OAAoC;AAC3E,QAAM,MAAM,MAAM,WAAW,MAAM,QAAQ,MAAM;AACjD,SAAO,OAAO,QAAQ,WAAW,MAAM;AAC1C;AAEO,SAAS,uBAAuB,OAAqB,WAAW,aAAqB;AACzF,QAAM,MAAM,MAAM;AAClB,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAGO,SAAS,2BAA2B,OAAyC;AACjF,QAAM,MAAM,MAAM,YAAY,MAAM;AACpC,SAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,IAAI,MAAM;AAClE;AAEO,SAAS,0BACb,UACA,OACQ;AACR,QAAM,OAAO,MAAM;AACnB,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,MAAI,SAAS,sBAAsB;AAChC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,OAAO,uBAAuB,KAAK;AACzC,UAAM,WAAW,2BAA2B,KAAK;AACjD,aAAS,IAAI,WAAW;AAAA,MACrB,IAAI;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,MACT,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,IAChD,CAAC;AACD,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,wBAAwB;AAClC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,UAAM,UAAU,0BAA0B,KAAK;AAC/C,QAAI,CAAC,aAAa,YAAY,KAAM,QAAO;AAI3C,UAAM,WACH,SAAS,IAAI,SAAS,KACrB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,uBAAuB,KAAK;AAAA,MAClC,SAAS;AAAA,IACZ;AAEH,aAAS,UAAU;AACnB,aAAS,IAAI,WAAW,QAAQ;AAChC,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,oBAAoB;AAC9B,WAAO,QAAQ,qBAAqB,KAAK,CAAC;AAAA,EAC7C;AAEA,SAAO;AACV;;;ACzEO,SAAS,uBACb,kBACA,iBACI;AACJ,QAAM,UACH,gBAAgB,SAAS,IACpB,iBAAiB,UAAU,CAAC,QAAQ,IAAI,OAAO,gBAAgB,CAAC,EAAE,EAAE,IACpE;AACR,MAAI,UAAU,GAAG;AACd,UAAM,SAAS,IAAI,IAAI,gBAAgB,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC3D,WAAO;AAAA,MACJ,GAAG,iBAAiB,MAAM,GAAG,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;AAAA,MACzE,GAAG;AAAA,IACN;AAAA,EACH;AACA,SAAO;AACV;","names":[]}
|
package/dist/agui.d.cts
CHANGED
|
@@ -14,4 +14,12 @@ declare function getAguiTextMessageRole(event: ChannelEvent, fallback?: string):
|
|
|
14
14
|
declare function getAguiTextMessageMemberId(event: ChannelEvent): number | undefined;
|
|
15
15
|
declare function applyAguiTextMessageEvent(messages: Map<string, TranscriptMessage>, event: ChannelEvent): boolean;
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/** Keep loaded messages before a fetched window; the fetched tail is authoritative.
|
|
18
|
+
* With no shared first message, the windows do not join and the fetched page stands alone.
|
|
19
|
+
* Extracted from the main app's useChatMessages cache merge.
|
|
20
|
+
*/
|
|
21
|
+
declare function mergeChatHistoryWindow<T extends {
|
|
22
|
+
id: string;
|
|
23
|
+
}>(existingMessages: T[], fetchedMessages: T[]): T[];
|
|
24
|
+
|
|
25
|
+
export { type TranscriptMessage, applyAguiTextMessageEvent, getAguiTextMessageContent, getAguiTextMessageId, getAguiTextMessageMemberId, getAguiTextMessageRole, mergeChatHistoryWindow };
|
package/dist/agui.d.ts
CHANGED
|
@@ -14,4 +14,12 @@ declare function getAguiTextMessageRole(event: ChannelEvent, fallback?: string):
|
|
|
14
14
|
declare function getAguiTextMessageMemberId(event: ChannelEvent): number | undefined;
|
|
15
15
|
declare function applyAguiTextMessageEvent(messages: Map<string, TranscriptMessage>, event: ChannelEvent): boolean;
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/** Keep loaded messages before a fetched window; the fetched tail is authoritative.
|
|
18
|
+
* With no shared first message, the windows do not join and the fetched page stands alone.
|
|
19
|
+
* Extracted from the main app's useChatMessages cache merge.
|
|
20
|
+
*/
|
|
21
|
+
declare function mergeChatHistoryWindow<T extends {
|
|
22
|
+
id: string;
|
|
23
|
+
}>(existingMessages: T[], fetchedMessages: T[]): T[];
|
|
24
|
+
|
|
25
|
+
export { type TranscriptMessage, applyAguiTextMessageEvent, getAguiTextMessageContent, getAguiTextMessageId, getAguiTextMessageMemberId, getAguiTextMessageRole, mergeChatHistoryWindow };
|
package/dist/agui.js
CHANGED
|
@@ -49,11 +49,25 @@ function applyAguiTextMessageEvent(messages, event) {
|
|
|
49
49
|
}
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// src/agui/chat-history.ts
|
|
54
|
+
function mergeChatHistoryWindow(existingMessages, fetchedMessages) {
|
|
55
|
+
const joinsAt = fetchedMessages.length > 0 ? existingMessages.findIndex((msg) => msg.id === fetchedMessages[0].id) : -1;
|
|
56
|
+
if (joinsAt > 0) {
|
|
57
|
+
const inPage = new Set(fetchedMessages.map((msg) => msg.id));
|
|
58
|
+
return [
|
|
59
|
+
...existingMessages.slice(0, joinsAt).filter((msg) => !inPage.has(msg.id)),
|
|
60
|
+
...fetchedMessages
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
return fetchedMessages;
|
|
64
|
+
}
|
|
52
65
|
export {
|
|
53
66
|
applyAguiTextMessageEvent,
|
|
54
67
|
getAguiTextMessageContent,
|
|
55
68
|
getAguiTextMessageId,
|
|
56
69
|
getAguiTextMessageMemberId,
|
|
57
|
-
getAguiTextMessageRole
|
|
70
|
+
getAguiTextMessageRole,
|
|
71
|
+
mergeChatHistoryWindow
|
|
58
72
|
};
|
|
59
73
|
//# sourceMappingURL=agui.js.map
|
package/dist/agui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agui/text-messages.ts"],"sourcesContent":["import type { ChannelEvent } from '../realtime/types';\n\nexport interface TranscriptMessage {\n id: string;\n role: string;\n content: string;\n /** Authoring member id when TEXT_MESSAGE_START carried it (Gravity extension). */\n memberId?: number;\n}\n\nexport function getAguiTextMessageId(event: ChannelEvent): string | null {\n const raw = event.messageId ?? event.message_id;\n return typeof raw === 'string' && raw.length > 0 ? raw : null;\n}\n\nexport function getAguiTextMessageContent(event: ChannelEvent): string | null {\n const raw = event.content ?? event.text ?? event.delta;\n return typeof raw === 'string' ? raw : null;\n}\n\nexport function getAguiTextMessageRole(event: ChannelEvent, fallback = 'assistant'): string {\n const raw = event.role;\n return typeof raw === 'string' && raw.length > 0 ? raw : fallback;\n}\n\n/** Gravity extension: authoring member on TEXT_MESSAGE_START (camel or snake). */\nexport function getAguiTextMessageMemberId(event: ChannelEvent): number | undefined {\n const raw = event.memberId ?? event.member_id;\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;\n}\n\nexport function applyAguiTextMessageEvent(\n messages: Map<string, TranscriptMessage>,\n event: ChannelEvent\n): boolean {\n const type = event.type;\n if (typeof type !== 'string') return false;\n\n if (type === 'TEXT_MESSAGE_START') {\n const messageId = getAguiTextMessageId(event);\n if (!messageId) return false;\n const role = getAguiTextMessageRole(event);\n const memberId = getAguiTextMessageMemberId(event);\n messages.set(messageId, {\n id: messageId,\n role,\n content: '',\n ...(memberId !== undefined ? { memberId } : {}),\n });\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_CONTENT') {\n const messageId = getAguiTextMessageId(event);\n const content = getAguiTextMessageContent(event);\n if (!messageId || content === null) return false;\n\n // CONTENT does not carry memberId per protocol — only START does.\n // Synthesize a bare message if START was missed; leave memberId unset.\n const existing =\n messages.get(messageId) ??\n ({\n id: messageId,\n role: getAguiTextMessageRole(event),\n content: '',\n } satisfies TranscriptMessage);\n\n existing.content = content;\n messages.set(messageId, existing);\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_END') {\n return Boolean(getAguiTextMessageId(event));\n }\n\n return false;\n}\n"],"mappings":";AAUO,SAAS,qBAAqB,OAAoC;AACtE,QAAM,MAAM,MAAM,aAAa,MAAM;AACrC,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BAA0B,OAAoC;AAC3E,QAAM,MAAM,MAAM,WAAW,MAAM,QAAQ,MAAM;AACjD,SAAO,OAAO,QAAQ,WAAW,MAAM;AAC1C;AAEO,SAAS,uBAAuB,OAAqB,WAAW,aAAqB;AACzF,QAAM,MAAM,MAAM;AAClB,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAGO,SAAS,2BAA2B,OAAyC;AACjF,QAAM,MAAM,MAAM,YAAY,MAAM;AACpC,SAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,IAAI,MAAM;AAClE;AAEO,SAAS,0BACb,UACA,OACQ;AACR,QAAM,OAAO,MAAM;AACnB,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,MAAI,SAAS,sBAAsB;AAChC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,OAAO,uBAAuB,KAAK;AACzC,UAAM,WAAW,2BAA2B,KAAK;AACjD,aAAS,IAAI,WAAW;AAAA,MACrB,IAAI;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,MACT,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,IAChD,CAAC;AACD,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,wBAAwB;AAClC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,UAAM,UAAU,0BAA0B,KAAK;AAC/C,QAAI,CAAC,aAAa,YAAY,KAAM,QAAO;AAI3C,UAAM,WACH,SAAS,IAAI,SAAS,KACrB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,uBAAuB,KAAK;AAAA,MAClC,SAAS;AAAA,IACZ;AAEH,aAAS,UAAU;AACnB,aAAS,IAAI,WAAW,QAAQ;AAChC,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,oBAAoB;AAC9B,WAAO,QAAQ,qBAAqB,KAAK,CAAC;AAAA,EAC7C;AAEA,SAAO;AACV;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/agui/text-messages.ts","../src/agui/chat-history.ts"],"sourcesContent":["import type { ChannelEvent } from '../realtime/types';\n\nexport interface TranscriptMessage {\n id: string;\n role: string;\n content: string;\n /** Authoring member id when TEXT_MESSAGE_START carried it (Gravity extension). */\n memberId?: number;\n}\n\nexport function getAguiTextMessageId(event: ChannelEvent): string | null {\n const raw = event.messageId ?? event.message_id;\n return typeof raw === 'string' && raw.length > 0 ? raw : null;\n}\n\nexport function getAguiTextMessageContent(event: ChannelEvent): string | null {\n const raw = event.content ?? event.text ?? event.delta;\n return typeof raw === 'string' ? raw : null;\n}\n\nexport function getAguiTextMessageRole(event: ChannelEvent, fallback = 'assistant'): string {\n const raw = event.role;\n return typeof raw === 'string' && raw.length > 0 ? raw : fallback;\n}\n\n/** Gravity extension: authoring member on TEXT_MESSAGE_START (camel or snake). */\nexport function getAguiTextMessageMemberId(event: ChannelEvent): number | undefined {\n const raw = event.memberId ?? event.member_id;\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;\n}\n\nexport function applyAguiTextMessageEvent(\n messages: Map<string, TranscriptMessage>,\n event: ChannelEvent\n): boolean {\n const type = event.type;\n if (typeof type !== 'string') return false;\n\n if (type === 'TEXT_MESSAGE_START') {\n const messageId = getAguiTextMessageId(event);\n if (!messageId) return false;\n const role = getAguiTextMessageRole(event);\n const memberId = getAguiTextMessageMemberId(event);\n messages.set(messageId, {\n id: messageId,\n role,\n content: '',\n ...(memberId !== undefined ? { memberId } : {}),\n });\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_CONTENT') {\n const messageId = getAguiTextMessageId(event);\n const content = getAguiTextMessageContent(event);\n if (!messageId || content === null) return false;\n\n // CONTENT does not carry memberId per protocol — only START does.\n // Synthesize a bare message if START was missed; leave memberId unset.\n const existing =\n messages.get(messageId) ??\n ({\n id: messageId,\n role: getAguiTextMessageRole(event),\n content: '',\n } satisfies TranscriptMessage);\n\n existing.content = content;\n messages.set(messageId, existing);\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_END') {\n return Boolean(getAguiTextMessageId(event));\n }\n\n return false;\n}\n","/** Keep loaded messages before a fetched window; the fetched tail is authoritative.\n * With no shared first message, the windows do not join and the fetched page stands alone.\n * Extracted from the main app's useChatMessages cache merge.\n */\nexport function mergeChatHistoryWindow<T extends { id: string }>(\n existingMessages: T[],\n fetchedMessages: T[]\n): T[] {\n const joinsAt =\n fetchedMessages.length > 0\n ? existingMessages.findIndex((msg) => msg.id === fetchedMessages[0].id)\n : -1;\n if (joinsAt > 0) {\n const inPage = new Set(fetchedMessages.map((msg) => msg.id));\n return [\n ...existingMessages.slice(0, joinsAt).filter((msg) => !inPage.has(msg.id)),\n ...fetchedMessages,\n ];\n }\n return fetchedMessages;\n}\n"],"mappings":";AAUO,SAAS,qBAAqB,OAAoC;AACtE,QAAM,MAAM,MAAM,aAAa,MAAM;AACrC,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BAA0B,OAAoC;AAC3E,QAAM,MAAM,MAAM,WAAW,MAAM,QAAQ,MAAM;AACjD,SAAO,OAAO,QAAQ,WAAW,MAAM;AAC1C;AAEO,SAAS,uBAAuB,OAAqB,WAAW,aAAqB;AACzF,QAAM,MAAM,MAAM;AAClB,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAGO,SAAS,2BAA2B,OAAyC;AACjF,QAAM,MAAM,MAAM,YAAY,MAAM;AACpC,SAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,IAAI,MAAM;AAClE;AAEO,SAAS,0BACb,UACA,OACQ;AACR,QAAM,OAAO,MAAM;AACnB,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,MAAI,SAAS,sBAAsB;AAChC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,OAAO,uBAAuB,KAAK;AACzC,UAAM,WAAW,2BAA2B,KAAK;AACjD,aAAS,IAAI,WAAW;AAAA,MACrB,IAAI;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,MACT,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,IAChD,CAAC;AACD,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,wBAAwB;AAClC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,UAAM,UAAU,0BAA0B,KAAK;AAC/C,QAAI,CAAC,aAAa,YAAY,KAAM,QAAO;AAI3C,UAAM,WACH,SAAS,IAAI,SAAS,KACrB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,uBAAuB,KAAK;AAAA,MAClC,SAAS;AAAA,IACZ;AAEH,aAAS,UAAU;AACnB,aAAS,IAAI,WAAW,QAAQ;AAChC,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,oBAAoB;AAC9B,WAAO,QAAQ,qBAAqB,KAAK,CAAC;AAAA,EAC7C;AAEA,SAAO;AACV;;;ACzEO,SAAS,uBACb,kBACA,iBACI;AACJ,QAAM,UACH,gBAAgB,SAAS,IACpB,iBAAiB,UAAU,CAAC,QAAQ,IAAI,OAAO,gBAAgB,CAAC,EAAE,EAAE,IACpE;AACR,MAAI,UAAU,GAAG;AACd,UAAM,SAAS,IAAI,IAAI,gBAAgB,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC3D,WAAO;AAAA,MACJ,GAAG,iBAAiB,MAAM,GAAG,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;AAAA,MACzE,GAAG;AAAA,IACN;AAAA,EACH;AACA,SAAO;AACV;","names":[]}
|