@btst/stack 4.4.0 → 4.5.1

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 (103) hide show
  1. package/README.md +2 -2
  2. package/dist/packages/stack/src/plugins/ai-chat/api/compaction.cjs +166 -39
  3. package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +167 -40
  4. package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.cjs +54 -0
  5. package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.mjs +51 -0
  6. package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +2 -0
  7. package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +2 -0
  8. package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +275 -95
  9. package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +276 -96
  10. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +8 -5
  11. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +8 -5
  12. package/dist/packages/stack/src/plugins/ai-chat/api/serializers.cjs +1 -0
  13. package/dist/packages/stack/src/plugins/ai-chat/api/serializers.mjs +1 -0
  14. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +3 -2
  15. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +3 -2
  16. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +26 -17
  17. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +26 -17
  18. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +22 -2
  19. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +22 -2
  20. package/dist/packages/stack/src/plugins/ai-chat/db.cjs +8 -0
  21. package/dist/packages/stack/src/plugins/ai-chat/db.mjs +8 -0
  22. package/dist/packages/stack/src/plugins/ai-chat/history.cjs +47 -0
  23. package/dist/packages/stack/src/plugins/ai-chat/history.mjs +45 -0
  24. package/dist/plugins/ai-chat/api/index.d.cts +2 -2
  25. package/dist/plugins/ai-chat/api/index.d.mts +2 -2
  26. package/dist/plugins/ai-chat/api/index.d.ts +2 -2
  27. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  28. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  29. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  30. package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
  31. package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
  32. package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
  33. package/dist/plugins/ai-chat/client/index.d.cts +3 -3
  34. package/dist/plugins/ai-chat/client/index.d.mts +3 -3
  35. package/dist/plugins/ai-chat/client/index.d.ts +3 -3
  36. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  37. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  38. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  39. package/dist/plugins/blog/api/index.d.cts +1 -1
  40. package/dist/plugins/blog/api/index.d.mts +1 -1
  41. package/dist/plugins/blog/api/index.d.ts +1 -1
  42. package/dist/plugins/blog/query-keys.d.cts +1 -1
  43. package/dist/plugins/blog/query-keys.d.mts +1 -1
  44. package/dist/plugins/blog/query-keys.d.ts +1 -1
  45. package/dist/plugins/cms/api/index.d.cts +1 -1
  46. package/dist/plugins/cms/api/index.d.mts +1 -1
  47. package/dist/plugins/cms/api/index.d.ts +1 -1
  48. package/dist/plugins/cms/query-keys.d.cts +1 -1
  49. package/dist/plugins/cms/query-keys.d.mts +1 -1
  50. package/dist/plugins/cms/query-keys.d.ts +1 -1
  51. package/dist/plugins/form-builder/api/index.d.cts +1 -1
  52. package/dist/plugins/form-builder/api/index.d.mts +1 -1
  53. package/dist/plugins/form-builder/api/index.d.ts +1 -1
  54. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  55. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  56. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  57. package/dist/plugins/form-builder/query-keys.d.cts +1 -1
  58. package/dist/plugins/form-builder/query-keys.d.mts +1 -1
  59. package/dist/plugins/form-builder/query-keys.d.ts +1 -1
  60. package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
  61. package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
  62. package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
  63. package/dist/plugins/ui-builder/client/index.d.cts +1 -1
  64. package/dist/plugins/ui-builder/client/index.d.mts +1 -1
  65. package/dist/plugins/ui-builder/client/index.d.ts +1 -1
  66. package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
  67. package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
  68. package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
  69. package/dist/shared/{stack.DDQlE2Pm.d.cts → stack.Cei2T2gc.d.cts} +1 -0
  70. package/dist/shared/{stack.C4zspX0T.d.ts → stack.DDQTbADz.d.ts} +7 -2
  71. package/dist/shared/{stack.tAUeRAtd.d.mts → stack.DZRZAzcx.d.mts} +1 -0
  72. package/dist/shared/{stack.ymWsP4HZ.d.cts → stack.IY-ngxuw.d.cts} +7 -2
  73. package/dist/shared/{stack.DlehgNZZ.d.ts → stack.byjorQi5.d.ts} +1 -0
  74. package/dist/shared/{stack.Ps3MDhTC.d.mts → stack.mU9iMF2j.d.mts} +7 -2
  75. package/package.json +1 -1
  76. package/src/plugins/ai-chat/__tests__/authorization.test.ts +56 -30
  77. package/src/plugins/ai-chat/__tests__/chat-input-authorization.test.tsx +42 -0
  78. package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +25 -0
  79. package/src/plugins/ai-chat/__tests__/client-sweep.test.tsx +101 -0
  80. package/src/plugins/ai-chat/__tests__/compaction.test.ts +79 -19
  81. package/src/plugins/ai-chat/__tests__/persisted-compaction.test.ts +948 -0
  82. package/src/plugins/ai-chat/api/compaction.ts +243 -52
  83. package/src/plugins/ai-chat/api/context-checkpoint.ts +66 -0
  84. package/src/plugins/ai-chat/api/getters.ts +2 -0
  85. package/src/plugins/ai-chat/api/operations.ts +377 -101
  86. package/src/plugins/ai-chat/api/plugin.ts +14 -8
  87. package/src/plugins/ai-chat/api/serializers.ts +1 -0
  88. package/src/plugins/ai-chat/client/components/chat-input.tsx +3 -3
  89. package/src/plugins/ai-chat/client/components/chat-interface.tsx +43 -23
  90. package/src/plugins/ai-chat/client/components/chat-message.tsx +32 -2
  91. package/src/plugins/ai-chat/client/localization/index.ts +1 -0
  92. package/src/plugins/ai-chat/db.ts +8 -0
  93. package/src/plugins/ai-chat/history.ts +68 -0
  94. package/src/plugins/ai-chat/types.ts +2 -0
  95. package/dist/shared/{stack.B8PG21zX.d.mts → stack.BLYaUcQa.d.mts} +88 -88
  96. package/dist/shared/{stack.DNw6Hv5o.d.cts → stack.BOZRaVpu.d.cts} +2 -2
  97. package/dist/shared/{stack.D6Pg-ufX.d.cts → stack.Bf6BGZNk.d.cts} +88 -88
  98. package/dist/shared/{stack.CGmh-Lpe.d.ts → stack.BuxUQqL8.d.ts} +12 -12
  99. package/dist/shared/{stack.zch70Loj.d.cts → stack.CX83YNuS.d.cts} +12 -12
  100. package/dist/shared/{stack.CyalJ0j6.d.ts → stack.CyltNNZZ.d.ts} +88 -88
  101. package/dist/shared/{stack.Clil75lW.d.ts → stack.DlBoNqYu.d.ts} +2 -2
  102. package/dist/shared/{stack.DhZtP2JZ.d.mts → stack.Dvy7FmaA.d.mts} +2 -2
  103. package/dist/shared/{stack.Doy2_wgl.d.mts → stack.Egr3RWhA.d.mts} +12 -12
@@ -1,4 +1,4 @@
1
- export { C as CMSApiRouter, b as CMSRouteKey, d as CMS_QUERY_KEYS, c as cmsBackendPlugin } from '../../../shared/stack.D6Pg-ufX.cjs';
1
+ export { C as CMSApiRouter, b as CMSRouteKey, d as CMS_QUERY_KEYS, c as cmsBackendPlugin } from '../../../shared/stack.Bf6BGZNk.cjs';
2
2
  import { DBAdapter } from '@btst/db';
3
3
  import { S as SerializedContentType, a as SerializedContentItemWithType, b as ContentType, c as ContentItem, d as SerializedContentItem, e as ContentItemWithType } from '../../../shared/stack.B3_aDcYL.cjs';
4
4
  export { C as CMSBackendConfig, f as CMSBackendHooks, g as CMSCreateOperationContext, h as CMSCreateResultContext, i as CMSDeleteOperationContext, j as CMSDeleteResultContext, k as CMSOperationErrorContext, l as CMSOperationLifecycleContext, m as CMSUpdateOperationContext, n as CMSUpdateResultContext, o as ContentTypeConfig } from '../../../shared/stack.B3_aDcYL.cjs';
@@ -1,4 +1,4 @@
1
- export { C as CMSApiRouter, b as CMSRouteKey, d as CMS_QUERY_KEYS, c as cmsBackendPlugin } from '../../../shared/stack.B8PG21zX.mjs';
1
+ export { C as CMSApiRouter, b as CMSRouteKey, d as CMS_QUERY_KEYS, c as cmsBackendPlugin } from '../../../shared/stack.BLYaUcQa.mjs';
2
2
  import { DBAdapter } from '@btst/db';
3
3
  import { S as SerializedContentType, a as SerializedContentItemWithType, b as ContentType, c as ContentItem, d as SerializedContentItem, e as ContentItemWithType } from '../../../shared/stack.B3_aDcYL.mjs';
4
4
  export { C as CMSBackendConfig, f as CMSBackendHooks, g as CMSCreateOperationContext, h as CMSCreateResultContext, i as CMSDeleteOperationContext, j as CMSDeleteResultContext, k as CMSOperationErrorContext, l as CMSOperationLifecycleContext, m as CMSUpdateOperationContext, n as CMSUpdateResultContext, o as ContentTypeConfig } from '../../../shared/stack.B3_aDcYL.mjs';
@@ -1,4 +1,4 @@
1
- export { C as CMSApiRouter, b as CMSRouteKey, d as CMS_QUERY_KEYS, c as cmsBackendPlugin } from '../../../shared/stack.CyalJ0j6.js';
1
+ export { C as CMSApiRouter, b as CMSRouteKey, d as CMS_QUERY_KEYS, c as cmsBackendPlugin } from '../../../shared/stack.CyltNNZZ.js';
2
2
  import { DBAdapter } from '@btst/db';
3
3
  import { S as SerializedContentType, a as SerializedContentItemWithType, b as ContentType, c as ContentItem, d as SerializedContentItem, e as ContentItemWithType } from '../../../shared/stack.B3_aDcYL.js';
4
4
  export { C as CMSBackendConfig, f as CMSBackendHooks, g as CMSCreateOperationContext, h as CMSCreateResultContext, i as CMSDeleteOperationContext, j as CMSDeleteResultContext, k as CMSOperationErrorContext, l as CMSOperationLifecycleContext, m as CMSUpdateOperationContext, n as CMSUpdateResultContext, o as ContentTypeConfig } from '../../../shared/stack.B3_aDcYL.js';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { C as CMSApiRouter, a as ContentListDiscriminator } from '../../shared/stack.D6Pg-ufX.cjs';
3
+ import { C as CMSApiRouter, a as ContentListDiscriminator } from '../../shared/stack.Bf6BGZNk.cjs';
4
4
  import { S as SerializedContentType, I as InverseRelation, a as SerializedContentItemWithType, P as PaginatedContentItems } from '../../shared/stack.B3_aDcYL.cjs';
5
5
  import '@btst/stack/plugins/api';
6
6
  import '../../authorization/index.cjs';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { C as CMSApiRouter, a as ContentListDiscriminator } from '../../shared/stack.B8PG21zX.mjs';
3
+ import { C as CMSApiRouter, a as ContentListDiscriminator } from '../../shared/stack.BLYaUcQa.mjs';
4
4
  import { S as SerializedContentType, I as InverseRelation, a as SerializedContentItemWithType, P as PaginatedContentItems } from '../../shared/stack.B3_aDcYL.mjs';
5
5
  import '@btst/stack/plugins/api';
6
6
  import '../../authorization/index.mjs';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { C as CMSApiRouter, a as ContentListDiscriminator } from '../../shared/stack.CyalJ0j6.js';
3
+ import { C as CMSApiRouter, a as ContentListDiscriminator } from '../../shared/stack.CyltNNZZ.js';
4
4
  import { S as SerializedContentType, I as InverseRelation, a as SerializedContentItemWithType, P as PaginatedContentItems } from '../../shared/stack.B3_aDcYL.js';
5
5
  import '@btst/stack/plugins/api';
6
6
  import '../../authorization/index.js';
@@ -1,4 +1,4 @@
1
- export { k as FORM_QUERY_KEYS, a as FormBuilderApiRouter, c as FormBuilderOperationError, e as FormBuilderOperations, b as FormBuilderRouteKey, g as FormListOperationResult, h as SubmissionDetailOperationResult, i as SubmissionListOperationResult, j as SubmitOperationResult, d as createFormBuilderOperations, f as formBuilderBackendPlugin } from '../../../shared/stack.zch70Loj.cjs';
1
+ export { k as FORM_QUERY_KEYS, a as FormBuilderApiRouter, c as FormBuilderOperationError, e as FormBuilderOperations, b as FormBuilderRouteKey, g as FormListOperationResult, h as SubmissionDetailOperationResult, i as SubmissionListOperationResult, j as SubmitOperationResult, d as createFormBuilderOperations, f as formBuilderBackendPlugin } from '../../../shared/stack.CX83YNuS.cjs';
2
2
  export { formBuilderPermissions } from '../permissions.cjs';
3
3
  import { S as SerializedForm, d as SerializedFormSubmissionWithData, F as Form, f as FormSubmission, a as SerializedFormSubmission, c as SerializedFormSubmissionSummary, g as FormSubmissionWithForm } from '../../../shared/stack.v2O65JqF.cjs';
4
4
  export { h as FormBuilderBackendConfig, i as FormBuilderBackendHooks, j as FormBuilderHookContext, k as FormBuilderOperationHookContext, l as FormCreateOperationContext, m as FormDeleteOperationContext, o as FormGetForUpdateOperationContext, n as FormGetOperationContext, p as FormListOperationContext, q as FormUpdateOperationContext, r as SubmissionCreateOperationContext, s as SubmissionDeleteOperationContext, t as SubmissionGetOperationContext, v as SubmissionHookContext, b as SubmissionListFormContext, u as SubmissionListOperationContext } from '../../../shared/stack.v2O65JqF.cjs';
@@ -1,4 +1,4 @@
1
- export { k as FORM_QUERY_KEYS, a as FormBuilderApiRouter, c as FormBuilderOperationError, e as FormBuilderOperations, b as FormBuilderRouteKey, g as FormListOperationResult, h as SubmissionDetailOperationResult, i as SubmissionListOperationResult, j as SubmitOperationResult, d as createFormBuilderOperations, f as formBuilderBackendPlugin } from '../../../shared/stack.Doy2_wgl.mjs';
1
+ export { k as FORM_QUERY_KEYS, a as FormBuilderApiRouter, c as FormBuilderOperationError, e as FormBuilderOperations, b as FormBuilderRouteKey, g as FormListOperationResult, h as SubmissionDetailOperationResult, i as SubmissionListOperationResult, j as SubmitOperationResult, d as createFormBuilderOperations, f as formBuilderBackendPlugin } from '../../../shared/stack.Egr3RWhA.mjs';
2
2
  export { formBuilderPermissions } from '../permissions.mjs';
3
3
  import { S as SerializedForm, d as SerializedFormSubmissionWithData, F as Form, f as FormSubmission, a as SerializedFormSubmission, c as SerializedFormSubmissionSummary, g as FormSubmissionWithForm } from '../../../shared/stack.OYT8WU4n.mjs';
4
4
  export { h as FormBuilderBackendConfig, i as FormBuilderBackendHooks, j as FormBuilderHookContext, k as FormBuilderOperationHookContext, l as FormCreateOperationContext, m as FormDeleteOperationContext, o as FormGetForUpdateOperationContext, n as FormGetOperationContext, p as FormListOperationContext, q as FormUpdateOperationContext, r as SubmissionCreateOperationContext, s as SubmissionDeleteOperationContext, t as SubmissionGetOperationContext, v as SubmissionHookContext, b as SubmissionListFormContext, u as SubmissionListOperationContext } from '../../../shared/stack.OYT8WU4n.mjs';
@@ -1,4 +1,4 @@
1
- export { k as FORM_QUERY_KEYS, a as FormBuilderApiRouter, c as FormBuilderOperationError, e as FormBuilderOperations, b as FormBuilderRouteKey, g as FormListOperationResult, h as SubmissionDetailOperationResult, i as SubmissionListOperationResult, j as SubmitOperationResult, d as createFormBuilderOperations, f as formBuilderBackendPlugin } from '../../../shared/stack.CGmh-Lpe.js';
1
+ export { k as FORM_QUERY_KEYS, a as FormBuilderApiRouter, c as FormBuilderOperationError, e as FormBuilderOperations, b as FormBuilderRouteKey, g as FormListOperationResult, h as SubmissionDetailOperationResult, i as SubmissionListOperationResult, j as SubmitOperationResult, d as createFormBuilderOperations, f as formBuilderBackendPlugin } from '../../../shared/stack.BuxUQqL8.js';
2
2
  export { formBuilderPermissions } from '../permissions.js';
3
3
  import { S as SerializedForm, d as SerializedFormSubmissionWithData, F as Form, f as FormSubmission, a as SerializedFormSubmission, c as SerializedFormSubmissionSummary, g as FormSubmissionWithForm } from '../../../shared/stack.C3bU9w6L.js';
4
4
  export { h as FormBuilderBackendConfig, i as FormBuilderBackendHooks, j as FormBuilderHookContext, k as FormBuilderOperationHookContext, l as FormCreateOperationContext, m as FormDeleteOperationContext, o as FormGetForUpdateOperationContext, n as FormGetOperationContext, p as FormListOperationContext, q as FormUpdateOperationContext, r as SubmissionCreateOperationContext, s as SubmissionDeleteOperationContext, t as SubmissionGetOperationContext, v as SubmissionHookContext, b as SubmissionListFormContext, u as SubmissionListOperationContext } from '../../../shared/stack.C3bU9w6L.js';
@@ -8,7 +8,7 @@ import '@btst/stack/context';
8
8
  import '../../permissions.cjs';
9
9
  import 'zod';
10
10
  import '@btst/stack/plugins/client';
11
- import '../../../../shared/stack.zch70Loj.cjs';
11
+ import '../../../../shared/stack.CX83YNuS.cjs';
12
12
  import '@btst/db';
13
13
  import 'zod/v4/core';
14
14
  import 'better-call';
@@ -8,7 +8,7 @@ import '@btst/stack/context';
8
8
  import '../../permissions.mjs';
9
9
  import 'zod';
10
10
  import '@btst/stack/plugins/client';
11
- import '../../../../shared/stack.Doy2_wgl.mjs';
11
+ import '../../../../shared/stack.Egr3RWhA.mjs';
12
12
  import '@btst/db';
13
13
  import 'zod/v4/core';
14
14
  import 'better-call';
@@ -8,7 +8,7 @@ import '@btst/stack/context';
8
8
  import '../../permissions.js';
9
9
  import 'zod';
10
10
  import '@btst/stack/plugins/client';
11
- import '../../../../shared/stack.CGmh-Lpe.js';
11
+ import '../../../../shared/stack.BuxUQqL8.js';
12
12
  import '@btst/db';
13
13
  import 'zod/v4/core';
14
14
  import 'better-call';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { F as FormsListDiscriminator, S as SubmissionsListDiscriminator, a as FormBuilderApiRouter } from '../../shared/stack.zch70Loj.cjs';
3
+ import { F as FormsListDiscriminator, S as SubmissionsListDiscriminator, a as FormBuilderApiRouter } from '../../shared/stack.CX83YNuS.cjs';
4
4
  import { P as PaginatedForms, S as SerializedForm, a as SerializedFormSubmission, e as PaginatedFormSubmissions, d as SerializedFormSubmissionWithData } from '../../shared/stack.v2O65JqF.cjs';
5
5
  import { StackIdentity } from '@btst/stack/context';
6
6
  import '@btst/stack/plugins/api';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { F as FormsListDiscriminator, S as SubmissionsListDiscriminator, a as FormBuilderApiRouter } from '../../shared/stack.Doy2_wgl.mjs';
3
+ import { F as FormsListDiscriminator, S as SubmissionsListDiscriminator, a as FormBuilderApiRouter } from '../../shared/stack.Egr3RWhA.mjs';
4
4
  import { P as PaginatedForms, S as SerializedForm, a as SerializedFormSubmission, e as PaginatedFormSubmissions, d as SerializedFormSubmissionWithData } from '../../shared/stack.OYT8WU4n.mjs';
5
5
  import { StackIdentity } from '@btst/stack/context';
6
6
  import '@btst/stack/plugins/api';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { F as FormsListDiscriminator, S as SubmissionsListDiscriminator, a as FormBuilderApiRouter } from '../../shared/stack.CGmh-Lpe.js';
3
+ import { F as FormsListDiscriminator, S as SubmissionsListDiscriminator, a as FormBuilderApiRouter } from '../../shared/stack.BuxUQqL8.js';
4
4
  import { P as PaginatedForms, S as SerializedForm, a as SerializedFormSubmission, e as PaginatedFormSubmissions, d as SerializedFormSubmissionWithData } from '../../shared/stack.C3bU9w6L.js';
5
5
  import { StackIdentity } from '@btst/stack/context';
6
6
  import '@btst/stack/plugins/api';
@@ -9,7 +9,7 @@ import '../../../../shared/stack.fdi94T4S.cjs';
9
9
  import 'react-hook-form';
10
10
  import 'react/jsx-runtime';
11
11
  import '@btst/stack/plugins/client';
12
- import '../../../../shared/stack.D6Pg-ufX.cjs';
12
+ import '../../../../shared/stack.Bf6BGZNk.cjs';
13
13
  import '@btst/stack/plugins/api';
14
14
  import '../../../../authorization/index.cjs';
15
15
  import 'better-call';
@@ -9,7 +9,7 @@ import '../../../../shared/stack.fdi94T4S.mjs';
9
9
  import 'react-hook-form';
10
10
  import 'react/jsx-runtime';
11
11
  import '@btst/stack/plugins/client';
12
- import '../../../../shared/stack.B8PG21zX.mjs';
12
+ import '../../../../shared/stack.BLYaUcQa.mjs';
13
13
  import '@btst/stack/plugins/api';
14
14
  import '../../../../authorization/index.mjs';
15
15
  import 'better-call';
@@ -9,7 +9,7 @@ import '../../../../shared/stack.fdi94T4S.js';
9
9
  import 'react-hook-form';
10
10
  import 'react/jsx-runtime';
11
11
  import '@btst/stack/plugins/client';
12
- import '../../../../shared/stack.CyalJ0j6.js';
12
+ import '../../../../shared/stack.CyltNNZZ.js';
13
13
  import '@btst/stack/plugins/api';
14
14
  import '../../../../authorization/index.js';
15
15
  import 'better-call';
@@ -15,7 +15,7 @@ import '../../../shared/stack.B3_aDcYL.cjs';
15
15
  import 'react-hook-form';
16
16
  import '@tanstack/react-query';
17
17
  import '@btst/stack/plugins/client/hooks';
18
- import '../../../shared/stack.D6Pg-ufX.cjs';
18
+ import '../../../shared/stack.Bf6BGZNk.cjs';
19
19
  import '@btst/stack/plugins/api';
20
20
  import '../../../authorization/index.cjs';
21
21
  import 'better-call';
@@ -15,7 +15,7 @@ import '../../../shared/stack.B3_aDcYL.mjs';
15
15
  import 'react-hook-form';
16
16
  import '@tanstack/react-query';
17
17
  import '@btst/stack/plugins/client/hooks';
18
- import '../../../shared/stack.B8PG21zX.mjs';
18
+ import '../../../shared/stack.BLYaUcQa.mjs';
19
19
  import '@btst/stack/plugins/api';
20
20
  import '../../../authorization/index.mjs';
21
21
  import 'better-call';
@@ -15,7 +15,7 @@ import '../../../shared/stack.B3_aDcYL.js';
15
15
  import 'react-hook-form';
16
16
  import '@tanstack/react-query';
17
17
  import '@btst/stack/plugins/client/hooks';
18
- import '../../../shared/stack.CyalJ0j6.js';
18
+ import '../../../shared/stack.CyltNNZZ.js';
19
19
  import '@btst/stack/plugins/api';
20
20
  import '../../../authorization/index.js';
21
21
  import 'better-call';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { a as ContentListDiscriminator, C as CMSApiRouter } from '../../shared/stack.D6Pg-ufX.cjs';
3
+ import { a as ContentListDiscriminator, C as CMSApiRouter } from '../../shared/stack.Bf6BGZNk.cjs';
4
4
  import { C as ComponentLayer, V as Variable } from '../../shared/stack.DmpPDPxA.cjs';
5
5
  import { P as PaginatedContentItems, a as SerializedContentItemWithType } from '../../shared/stack.B3_aDcYL.cjs';
6
6
  import { UIBuilderPageSchemaType, PaginatedUIBuilderPages, SerializedUIBuilderPage } from './index.cjs';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { a as ContentListDiscriminator, C as CMSApiRouter } from '../../shared/stack.B8PG21zX.mjs';
3
+ import { a as ContentListDiscriminator, C as CMSApiRouter } from '../../shared/stack.BLYaUcQa.mjs';
4
4
  import { C as ComponentLayer, V as Variable } from '../../shared/stack.B1srlBud.mjs';
5
5
  import { P as PaginatedContentItems, a as SerializedContentItemWithType } from '../../shared/stack.B3_aDcYL.mjs';
6
6
  import { UIBuilderPageSchemaType, PaginatedUIBuilderPages, SerializedUIBuilderPage } from './index.mjs';
@@ -1,6 +1,6 @@
1
1
  import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
- import { a as ContentListDiscriminator, C as CMSApiRouter } from '../../shared/stack.CyalJ0j6.js';
3
+ import { a as ContentListDiscriminator, C as CMSApiRouter } from '../../shared/stack.CyltNNZZ.js';
4
4
  import { C as ComponentLayer, V as Variable } from '../../shared/stack.BK9Z2dcL.js';
5
5
  import { P as PaginatedContentItems, a as SerializedContentItemWithType } from '../../shared/stack.B3_aDcYL.js';
6
6
  import { UIBuilderPageSchemaType, PaginatedUIBuilderPages, SerializedUIBuilderPage } from './index.js';
@@ -44,6 +44,7 @@ interface AiChatLocalization {
44
44
  CHAT_EMPTY_STATE: string;
45
45
  CHAT_LOADING: string;
46
46
  CHAT_COMPACTING?: string;
47
+ CHAT_INTERRUPTED?: string;
47
48
  CHAT_ERROR: string;
48
49
  CHAT_RETRY: string;
49
50
  CHAT_GENERIC_ERROR_TITLE: string;
@@ -31,6 +31,8 @@ type Message = {
31
31
  conversationId: string;
32
32
  role: "system" | "user" | "assistant" | "data";
33
33
  content: string;
34
+ /** True when generation stopped before a successful completion. */
35
+ interrupted?: boolean | null;
34
36
  createdAt: Date;
35
37
  };
36
38
  type SerializedConversation = Omit<Conversation, "createdAt" | "updatedAt"> & {
@@ -55,10 +57,12 @@ interface AiChatPageContentConfig {
55
57
  description?: string;
56
58
  }
57
59
 
58
- /** Opt-in context compaction for public, text-only conversations. */
60
+ /** Opt-in context compaction for conversations containing text and images. */
59
61
  interface AiChatCompactionConfig {
60
62
  /** Actual context window. Configure the model's output limit at or below 20% of this. */
61
63
  contextWindowTokens: number;
64
+ /** Estimated tokens per image, excluding encoded bytes. Default: 4096. Increase for models with higher image token costs. */
65
+ imageTokenEstimate?: number;
62
66
  }
63
67
 
64
68
  type AiChatAccess = "authorized" | "public";
@@ -228,7 +232,7 @@ interface AiChatBackendConfig {
228
232
  */
229
233
  access?: AiChatAccess;
230
234
  systemPrompt?: string;
231
- /** Automatically summarize older context in public text chats; off by default. */
235
+ /** Automatically summarize older text and image context; off by default. */
232
236
  compaction?: AiChatCompactionConfig;
233
237
  tools?: Record<string, Tool>;
234
238
  /** Enable the standard readPage server tool for full page content. Reserves the readPage tool name. */
@@ -319,6 +323,7 @@ declare const aiChatBackendPlugin: <TTools extends Record<string, Tool> = Record
319
323
  readonly conversationId: string;
320
324
  readonly role: "system" | "user" | "assistant" | "data";
321
325
  readonly content: string;
326
+ readonly interrupted?: boolean | null | undefined;
322
327
  readonly createdAt: string;
323
328
  }[];
324
329
  }>;
@@ -44,6 +44,7 @@ interface AiChatLocalization {
44
44
  CHAT_EMPTY_STATE: string;
45
45
  CHAT_LOADING: string;
46
46
  CHAT_COMPACTING?: string;
47
+ CHAT_INTERRUPTED?: string;
47
48
  CHAT_ERROR: string;
48
49
  CHAT_RETRY: string;
49
50
  CHAT_GENERIC_ERROR_TITLE: string;
@@ -31,6 +31,8 @@ type Message = {
31
31
  conversationId: string;
32
32
  role: "system" | "user" | "assistant" | "data";
33
33
  content: string;
34
+ /** True when generation stopped before a successful completion. */
35
+ interrupted?: boolean | null;
34
36
  createdAt: Date;
35
37
  };
36
38
  type SerializedConversation = Omit<Conversation, "createdAt" | "updatedAt"> & {
@@ -55,10 +57,12 @@ interface AiChatPageContentConfig {
55
57
  description?: string;
56
58
  }
57
59
 
58
- /** Opt-in context compaction for public, text-only conversations. */
60
+ /** Opt-in context compaction for conversations containing text and images. */
59
61
  interface AiChatCompactionConfig {
60
62
  /** Actual context window. Configure the model's output limit at or below 20% of this. */
61
63
  contextWindowTokens: number;
64
+ /** Estimated tokens per image, excluding encoded bytes. Default: 4096. Increase for models with higher image token costs. */
65
+ imageTokenEstimate?: number;
62
66
  }
63
67
 
64
68
  type AiChatAccess = "authorized" | "public";
@@ -228,7 +232,7 @@ interface AiChatBackendConfig {
228
232
  */
229
233
  access?: AiChatAccess;
230
234
  systemPrompt?: string;
231
- /** Automatically summarize older context in public text chats; off by default. */
235
+ /** Automatically summarize older text and image context; off by default. */
232
236
  compaction?: AiChatCompactionConfig;
233
237
  tools?: Record<string, Tool>;
234
238
  /** Enable the standard readPage server tool for full page content. Reserves the readPage tool name. */
@@ -319,6 +323,7 @@ declare const aiChatBackendPlugin: <TTools extends Record<string, Tool> = Record
319
323
  readonly conversationId: string;
320
324
  readonly role: "system" | "user" | "assistant" | "data";
321
325
  readonly content: string;
326
+ readonly interrupted?: boolean | null | undefined;
322
327
  readonly createdAt: string;
323
328
  }[];
324
329
  }>;
@@ -44,6 +44,7 @@ interface AiChatLocalization {
44
44
  CHAT_EMPTY_STATE: string;
45
45
  CHAT_LOADING: string;
46
46
  CHAT_COMPACTING?: string;
47
+ CHAT_INTERRUPTED?: string;
47
48
  CHAT_ERROR: string;
48
49
  CHAT_RETRY: string;
49
50
  CHAT_GENERIC_ERROR_TITLE: string;
@@ -31,6 +31,8 @@ type Message = {
31
31
  conversationId: string;
32
32
  role: "system" | "user" | "assistant" | "data";
33
33
  content: string;
34
+ /** True when generation stopped before a successful completion. */
35
+ interrupted?: boolean | null;
34
36
  createdAt: Date;
35
37
  };
36
38
  type SerializedConversation = Omit<Conversation, "createdAt" | "updatedAt"> & {
@@ -55,10 +57,12 @@ interface AiChatPageContentConfig {
55
57
  description?: string;
56
58
  }
57
59
 
58
- /** Opt-in context compaction for public, text-only conversations. */
60
+ /** Opt-in context compaction for conversations containing text and images. */
59
61
  interface AiChatCompactionConfig {
60
62
  /** Actual context window. Configure the model's output limit at or below 20% of this. */
61
63
  contextWindowTokens: number;
64
+ /** Estimated tokens per image, excluding encoded bytes. Default: 4096. Increase for models with higher image token costs. */
65
+ imageTokenEstimate?: number;
62
66
  }
63
67
 
64
68
  type AiChatAccess = "authorized" | "public";
@@ -228,7 +232,7 @@ interface AiChatBackendConfig {
228
232
  */
229
233
  access?: AiChatAccess;
230
234
  systemPrompt?: string;
231
- /** Automatically summarize older context in public text chats; off by default. */
235
+ /** Automatically summarize older text and image context; off by default. */
232
236
  compaction?: AiChatCompactionConfig;
233
237
  tools?: Record<string, Tool>;
234
238
  /** Enable the standard readPage server tool for full page content. Reserves the readPage tool name. */
@@ -319,6 +323,7 @@ declare const aiChatBackendPlugin: <TTools extends Record<string, Tool> = Record
319
323
  readonly conversationId: string;
320
324
  readonly role: "system" | "user" | "assistant" | "data";
321
325
  readonly content: string;
326
+ readonly interrupted?: boolean | null | undefined;
322
327
  readonly createdAt: string;
323
328
  }[];
324
329
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btst/stack",
3
- "version": "4.4.0",
3
+ "version": "4.5.1",
4
4
  "description": "A composable, plugin-based library for building full-stack applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,18 +14,53 @@ import {
14
14
  import { aiChatPermissions } from "../permissions";
15
15
  import type { Conversation, Message } from "../types";
16
16
 
17
- const { streamText } = vi.hoisted(() => ({
17
+ const { streamText, completions } = vi.hoisted(() => ({
18
+ completions: [] as Array<
19
+ NonNullable<
20
+ Parameters<typeof import("ai").createUIMessageStream>[0]["onFinish"]
21
+ >
22
+ >,
18
23
  streamText: vi.fn(() => ({
24
+ toUIMessageStream: () =>
25
+ new ReadableStream({
26
+ start(controller) {
27
+ controller.close();
28
+ },
29
+ }),
19
30
  toUIMessageStreamResponse: () => new Response("stream", { status: 200 }),
20
31
  })),
21
32
  }));
22
33
 
23
- vi.mock("ai", async (importOriginal) => ({
24
- ...(await importOriginal<typeof import("ai")>()),
25
- convertToModelMessages: (messages: unknown) => messages,
26
- stepCountIs: () => () => false,
27
- streamText,
28
- }));
34
+ vi.mock("ai", async (importOriginal) => {
35
+ const original = await importOriginal<typeof import("ai")>();
36
+ return {
37
+ ...original,
38
+ createUIMessageStream: (
39
+ options: Parameters<typeof original.createUIMessageStream>[0],
40
+ ) => {
41
+ if (options.onFinish) completions.push(options.onFinish);
42
+ return original.createUIMessageStream(options);
43
+ },
44
+ convertToModelMessages: (messages: unknown) => messages,
45
+ stepCountIs: () => () => false,
46
+ streamText,
47
+ };
48
+ });
49
+
50
+ function completion(text: string) {
51
+ const responseMessage = {
52
+ id: "server-response",
53
+ role: "assistant" as const,
54
+ parts: [{ type: "text" as const, text }],
55
+ };
56
+ return {
57
+ responseMessage,
58
+ messages: [responseMessage],
59
+ isAborted: false,
60
+ isContinuation: false,
61
+ finishReason: "stop" as const,
62
+ };
63
+ }
29
64
 
30
65
  type Identity = { id: string; role: "user" | "admin" };
31
66
 
@@ -173,7 +208,10 @@ async function seedMessage(
173
208
  }
174
209
 
175
210
  describe("AI Chat operation authorization", () => {
176
- beforeEach(() => streamText.mockClear());
211
+ beforeEach(() => {
212
+ streamText.mockClear();
213
+ completions.length = 0;
214
+ });
177
215
 
178
216
  it("publishes one executable inventory for every maintained transport", () => {
179
217
  const plugin = aiChatBackendPlugin({ model });
@@ -815,15 +853,7 @@ describe("AI Chat operation authorization", () => {
815
853
  await app
816
854
  .forRequest(request("/chat", { identity: owner }))
817
855
  .operations.aiChat.startStream(messageBody);
818
- const finish = (
819
- streamText.mock.calls as unknown as Array<
820
- [
821
- {
822
- onFinish: (completion: { text: string }) => Promise<void>;
823
- },
824
- ]
825
- >
826
- )[0]?.[0].onFinish;
856
+ const finish = completions[0];
827
857
  expect(finish).toBeDefined();
828
858
  if (!finish) throw new Error("Missing stream completion callback");
829
859
  const adapterConfig = app.adapter.options?.adapterConfig;
@@ -831,7 +861,7 @@ describe("AI Chat operation authorization", () => {
831
861
  if (!adapterConfig) throw new Error("Missing adapter config");
832
862
  adapterConfig.transaction = false;
833
863
 
834
- await finish({ text: "unsafe answer" });
864
+ await finish(completion("unsafe answer"));
835
865
 
836
866
  expect(
837
867
  await app.adapter.count({
@@ -876,6 +906,7 @@ describe("AI Chat operation authorization", () => {
876
906
  identity: z.object({ id: z.string(), role: z.enum(["user", "admin"]) }),
877
907
  permissions: [aiChatPermissions] as const,
878
908
  rules: ({ aiChat }) => [
909
+ aiChat.conversation.read.allow(),
879
910
  aiChat.stream.start.allow(),
880
911
  aiChat.message.retry.when(({ facts }) => {
881
912
  observed.push({ intent: "retry", messageId: facts.messageId });
@@ -959,6 +990,7 @@ describe("AI Chat operation authorization", () => {
959
990
  identity: z.object({ id: z.string(), role: z.enum(["user", "admin"]) }),
960
991
  permissions: [aiChatPermissions] as const,
961
992
  rules: ({ aiChat }) => [
993
+ aiChat.conversation.read.allow(),
962
994
  aiChat.stream.start.allow(),
963
995
  aiChat.message.retry.allow(),
964
996
  aiChat.message.edit.when(() => false),
@@ -1030,6 +1062,7 @@ describe("AI Chat operation authorization", () => {
1030
1062
  identity: z.object({ id: z.string(), role: z.enum(["user", "admin"]) }),
1031
1063
  permissions: [aiChatPermissions] as const,
1032
1064
  rules: ({ aiChat }) => [
1065
+ aiChat.conversation.read.allow(),
1033
1066
  aiChat.stream.start.allow(),
1034
1067
  aiChat.message.send.allow(),
1035
1068
  aiChat.message.retry.when(({ facts }) => observedRetry(facts)),
@@ -1490,7 +1523,7 @@ describe("AI Chat operation authorization", () => {
1490
1523
  expect(before).not.toHaveBeenCalled();
1491
1524
  expect(streamText).not.toHaveBeenCalled();
1492
1525
  expect(await app.adapter.count({ model: "message" })).toBe(0);
1493
- expect(onError).toHaveBeenCalledOnce();
1526
+ expect(onError).not.toHaveBeenCalled();
1494
1527
  });
1495
1528
 
1496
1529
  it("rechecks a missing requested conversation before entering hooks", async () => {
@@ -1962,20 +1995,13 @@ describe("AI Chat operation authorization", () => {
1962
1995
  await app
1963
1996
  .forRequest(request("/first", { identity: owner }))
1964
1997
  .operations.aiChat.startStream(input);
1965
- const streamCalls = streamText.mock.calls as unknown as Array<
1966
- [
1967
- {
1968
- onFinish: (completion: { text: string }) => Promise<void>;
1969
- },
1970
- ]
1971
- >;
1972
- const firstFinish = streamCalls[0]?.[0].onFinish;
1998
+ const firstFinish = completions[0];
1973
1999
  await app
1974
2000
  .forRequest(request("/second", { identity: owner }))
1975
2001
  .operations.aiChat.startStream(input);
1976
- const secondFinish = streamCalls[1]?.[0].onFinish;
2002
+ const secondFinish = completions[1];
1977
2003
 
1978
- await firstFinish?.({ text: "stale answer" });
2004
+ await firstFinish?.(completion("stale answer"));
1979
2005
  expect(
1980
2006
  await app.adapter.count({
1981
2007
  model: "message",
@@ -1984,7 +2010,7 @@ describe("AI Chat operation authorization", () => {
1984
2010
  ).toBe(0);
1985
2011
  expect(onErrorChat).toHaveBeenCalledOnce();
1986
2012
 
1987
- await secondFinish?.({ text: "current answer" });
2013
+ await secondFinish?.(completion("current answer"));
1988
2014
  const assistants = await app.adapter.findMany<Message>({
1989
2015
  model: "message",
1990
2016
  where: [{ field: "role", value: "assistant", operator: "eq" }],
@@ -37,6 +37,48 @@ afterEach(async () => {
37
37
  });
38
38
 
39
39
  describe("AI Chat attachment authorization", () => {
40
+ it("uses the configured upload transport in public mode without requiring a saved-chat identity", async () => {
41
+ const uploadFile = vi.fn(async () => "https://files/public.png");
42
+ const onFilesAttached = vi.fn();
43
+ await act(async () => {
44
+ root.render(
45
+ <StackProvider
46
+ stack={createTestClientStack({
47
+ aiChat: aiChatClientPlugin({ mode: "public" }),
48
+ })}
49
+ overrides={{ aiChat: { uploadFile, allowedFileTypes: ["image"] } }}
50
+ >
51
+ <ChatInput
52
+ handleInputChange={() => {}}
53
+ handleSubmit={() => {}}
54
+ isLoading={false}
55
+ attachedFiles={[]}
56
+ onFilesAttached={onFilesAttached}
57
+ />
58
+ </StackProvider>,
59
+ );
60
+ });
61
+ const input =
62
+ container.querySelector<HTMLInputElement>('input[type="file"]');
63
+ expect(input).toBeTruthy();
64
+ Object.defineProperty(input!, "files", {
65
+ configurable: true,
66
+ value: [new File(["image"], "public.png", { type: "image/png" })],
67
+ });
68
+ await act(async () => {
69
+ input!.dispatchEvent(new Event("change", { bubbles: true }));
70
+ await Promise.resolve();
71
+ });
72
+ expect(uploadFile).toHaveBeenCalledOnce();
73
+ expect(onFilesAttached).toHaveBeenCalledWith([
74
+ {
75
+ url: "https://files/public.png",
76
+ mediaType: "image/png",
77
+ filename: "public.png",
78
+ },
79
+ ]);
80
+ });
81
+
40
82
  it("checks the selected MIME type before calling the upload transport", async () => {
41
83
  const observedMediaTypes: string[][] = [];
42
84
  const authorization = defineAuthorization({
@@ -161,6 +161,31 @@ function query(selector: string): HTMLElement {
161
161
  }
162
162
 
163
163
  describe("ai-chat classNames overrides (issue #36)", () => {
164
+ it("labels interrupted responses and stops unfinished tool indicators", async () => {
165
+ await render(
166
+ <ChatMessage
167
+ message={{
168
+ ...assistantMessage,
169
+ metadata: { interrupted: true },
170
+ parts: [
171
+ ...assistantMessage.parts,
172
+ {
173
+ type: "tool-inspect",
174
+ toolCallId: "pending",
175
+ state: "input-available",
176
+ input: {},
177
+ },
178
+ ],
179
+ }}
180
+ />,
181
+ );
182
+ expect(query('[data-testid="chat-interrupted"]').textContent).toBe(
183
+ "Response interrupted",
184
+ );
185
+ expect(container.textContent).not.toContain("Executing...");
186
+ expect(container.querySelector(".animate-spin")).toBeNull();
187
+ });
188
+
164
189
  it("applies message, userMessage and assistantMessage to a user message", async () => {
165
190
  await render(<ChatMessage message={userMessage} />);
166
191