@carlonicora/nextjs-jsonapi 1.113.0 → 1.115.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 (68) hide show
  1. package/dist/{AssistantInterface-B1c8FhGA.d.mts → AssistantInterface-B56qznuH.d.mts} +2 -0
  2. package/dist/{AssistantInterface-BBUHxOCd.d.ts → AssistantInterface-CHiJIYDY.d.ts} +2 -0
  3. package/dist/{AssistantMessageInterface-DrgHDjZ5.d.mts → AssistantMessageInterface-BuUZFkE5.d.mts} +16 -1
  4. package/dist/{AssistantMessageInterface-BNzf1etU.d.ts → AssistantMessageInterface-CQwh5tEb.d.ts} +16 -1
  5. package/dist/{BlockNoteEditor-DOAPXS7S.js → BlockNoteEditor-V74ICZY3.js} +19 -19
  6. package/dist/{BlockNoteEditor-DOAPXS7S.js.map → BlockNoteEditor-V74ICZY3.js.map} +1 -1
  7. package/dist/{BlockNoteEditor-F3HSL4LX.mjs → BlockNoteEditor-Z3EKI3C6.mjs} +4 -4
  8. package/dist/billing/index.js +346 -346
  9. package/dist/billing/index.mjs +3 -3
  10. package/dist/{chunk-6XWW67F2.js → chunk-3MUBMSDD.js} +7 -7
  11. package/dist/{chunk-6XWW67F2.js.map → chunk-3MUBMSDD.js.map} +1 -1
  12. package/dist/{chunk-UBFVV2DA.js → chunk-6LSGIJ5W.js} +881 -826
  13. package/dist/chunk-6LSGIJ5W.js.map +1 -0
  14. package/dist/{chunk-CI36BXIG.mjs → chunk-BKKVLD42.mjs} +75 -20
  15. package/dist/chunk-BKKVLD42.mjs.map +1 -0
  16. package/dist/{chunk-U2HR57MY.js → chunk-J3DFHESB.js} +79 -28
  17. package/dist/chunk-J3DFHESB.js.map +1 -0
  18. package/dist/{chunk-CWVYU4YZ.mjs → chunk-SJRSR3AF.mjs} +2 -2
  19. package/dist/{chunk-ZYPNVEHB.mjs → chunk-UBBTQUFB.mjs} +56 -5
  20. package/dist/chunk-UBBTQUFB.mjs.map +1 -0
  21. package/dist/client/index.js +4 -4
  22. package/dist/client/index.mjs +3 -3
  23. package/dist/components/index.d.mts +24 -7
  24. package/dist/components/index.d.ts +24 -7
  25. package/dist/components/index.js +4 -4
  26. package/dist/components/index.mjs +3 -3
  27. package/dist/contexts/index.d.mts +7 -2
  28. package/dist/contexts/index.d.ts +7 -2
  29. package/dist/contexts/index.js +4 -4
  30. package/dist/contexts/index.mjs +3 -3
  31. package/dist/core/index.d.mts +28 -6
  32. package/dist/core/index.d.ts +28 -6
  33. package/dist/core/index.js +2 -2
  34. package/dist/core/index.mjs +1 -1
  35. package/dist/features/help/index.js +36 -36
  36. package/dist/features/help/index.mjs +3 -3
  37. package/dist/index.d.mts +4 -4
  38. package/dist/index.d.ts +4 -4
  39. package/dist/index.js +3 -3
  40. package/dist/index.mjs +2 -2
  41. package/dist/{s3.service-0BTClOYO.d.mts → s3.service-CAL5dhGG.d.mts} +3 -0
  42. package/dist/{s3.service-CT27Fm1s.d.ts → s3.service-COLYvcW2.d.ts} +3 -0
  43. package/dist/server/index.d.mts +1 -1
  44. package/dist/server/index.d.ts +1 -1
  45. package/dist/server/index.js +3 -3
  46. package/dist/server/index.mjs +1 -1
  47. package/package.json +1 -1
  48. package/src/core/abstracts/AbstractService.ts +6 -3
  49. package/src/features/assistant/components/containers/AssistantContainer.tsx +18 -2
  50. package/src/features/assistant/components/parts/AssistantEmptyState.tsx +22 -1
  51. package/src/features/assistant/components/parts/AssistantThread.tsx +12 -1
  52. package/src/features/assistant/contexts/AssistantContext.tsx +42 -9
  53. package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +94 -3
  54. package/src/features/assistant/data/Assistant.ts +6 -0
  55. package/src/features/assistant/data/AssistantInterface.ts +2 -0
  56. package/src/features/assistant/data/AssistantService.ts +38 -0
  57. package/src/features/assistant/data/__tests__/Assistant.spec.ts +30 -0
  58. package/src/features/assistant-message/AssistantMessageModule.ts +2 -0
  59. package/src/features/assistant-message/components/MessageItem.tsx +26 -4
  60. package/src/features/assistant-message/components/MessageList.tsx +4 -2
  61. package/src/features/assistant-message/data/AssistantMessage.ts +21 -1
  62. package/src/features/assistant-message/data/AssistantMessageInterface.ts +16 -0
  63. package/dist/chunk-CI36BXIG.mjs.map +0 -1
  64. package/dist/chunk-U2HR57MY.js.map +0 -1
  65. package/dist/chunk-UBFVV2DA.js.map +0 -1
  66. package/dist/chunk-ZYPNVEHB.mjs.map +0 -1
  67. /package/dist/{BlockNoteEditor-F3HSL4LX.mjs.map → BlockNoteEditor-Z3EKI3C6.mjs.map} +0 -0
  68. /package/dist/{chunk-CWVYU4YZ.mjs.map → chunk-SJRSR3AF.mjs.map} +0 -0
@@ -4,6 +4,7 @@ import { AssistantInput, AssistantInterface } from "./AssistantInterface";
4
4
  export class Assistant extends AbstractApiData implements AssistantInterface {
5
5
  private _title?: string;
6
6
  private _messageCount?: number;
7
+ private _engine?: string;
7
8
 
8
9
  get title(): string {
9
10
  return this._title ?? "";
@@ -13,9 +14,14 @@ export class Assistant extends AbstractApiData implements AssistantInterface {
13
14
  return this._messageCount ?? 0;
14
15
  }
15
16
 
17
+ get engine(): string | undefined {
18
+ return this._engine;
19
+ }
20
+
16
21
  rehydrate(data: JsonApiHydratedDataInterface): this {
17
22
  super.rehydrate(data);
18
23
  this._title = data.jsonApi.attributes?.title;
24
+ this._engine = data.jsonApi.attributes?.engine;
19
25
  const fromMeta = data.jsonApi.meta?.messageCount;
20
26
  const fromAttrs = data.jsonApi.attributes?.messageCount;
21
27
  this._messageCount = typeof fromMeta === "number" ? fromMeta : typeof fromAttrs === "number" ? fromAttrs : 0;
@@ -10,4 +10,6 @@ export type AssistantInput = {
10
10
  export interface AssistantInterface extends ApiDataInterface {
11
11
  get title(): string;
12
12
  get messageCount(): number;
13
+ /** Agent engine the thread runs on — `"operator"` or undefined (responder). */
14
+ get engine(): string | undefined;
13
15
  }
@@ -31,6 +31,20 @@ export class AssistantService extends AbstractService {
31
31
  });
32
32
  }
33
33
 
34
+ /**
35
+ * Creates an assistant whose first turn runs on the operator engine
36
+ * (durable checkpointing + approval gates). Mirrors `create()` against the
37
+ * standalone operator module's create route (`POST operator`).
38
+ */
39
+ static async createOperator(params: AssistantInput): Promise<AssistantInterface> {
40
+ return this.callApi({
41
+ type: Modules.Assistant,
42
+ method: HttpMethod.POST,
43
+ endpoint: new EndpointCreator({ endpoint: "operator" }).generate(),
44
+ input: params,
45
+ });
46
+ }
47
+
34
48
  /**
35
49
  * Sends a new user message to an existing assistant thread. The agent turn
36
50
  * runs synchronously; the response is a two-element list: [user, assistant].
@@ -63,6 +77,30 @@ export class AssistantService extends AbstractService {
63
77
  });
64
78
  }
65
79
 
80
+ /**
81
+ * Operator-engine variant of `appendMessage()`. Targets
82
+ * `POST operator/:assistantId/assistant-messages`; the turn may freeze on a
83
+ * destructive tool call, in which case the returned list ends with an
84
+ * `approval-request` assistant message linked to a pending AssistantAction.
85
+ */
86
+ static async appendMessageOperator(params: {
87
+ assistantId: string;
88
+ content: string;
89
+ }): Promise<AssistantMessageInterface[]> {
90
+ const message = new AssistantMessage();
91
+ return this.callApi<AssistantMessageInterface[]>({
92
+ type: Modules.AssistantMessage,
93
+ method: HttpMethod.POST,
94
+ endpoint: new EndpointCreator({
95
+ endpoint: "operator",
96
+ id: params.assistantId,
97
+ childEndpoint: Modules.AssistantMessage,
98
+ }).generate(),
99
+ input: message.createAppendMessageJsonApi({ content: params.content }),
100
+ overridesJsonApiCreation: true,
101
+ });
102
+ }
103
+
66
104
  static async rename(params: { id: string; title: string }): Promise<void> {
67
105
  await this.callApi({
68
106
  type: Modules.Assistant,
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { Assistant } from "../Assistant";
3
+
4
+ describe("Assistant model", () => {
5
+ it("rehydrates engine from attributes (operator thread)", () => {
6
+ const assistant = new Assistant().rehydrate({
7
+ jsonApi: { type: "assistants", id: "a-1", attributes: { title: "T", engine: "operator" } },
8
+ included: [],
9
+ });
10
+ expect(assistant.engine).toBe("operator");
11
+ expect(assistant.title).toBe("T");
12
+ });
13
+
14
+ it("leaves engine undefined when the attribute is absent (responder thread)", () => {
15
+ const assistant = new Assistant().rehydrate({
16
+ jsonApi: { type: "assistants", id: "a-2", attributes: { title: "T" } },
17
+ included: [],
18
+ });
19
+ expect(assistant.engine).toBeUndefined();
20
+ });
21
+
22
+ it("preserves engine through dehydrate/rehydrate round-trips", () => {
23
+ const original = new Assistant().rehydrate({
24
+ jsonApi: { type: "assistants", id: "a-3", attributes: { title: "T", engine: "operator" } },
25
+ included: [],
26
+ });
27
+ const roundTripped = new Assistant().rehydrate(original.dehydrate());
28
+ expect(roundTripped.engine).toBe("operator");
29
+ });
30
+ });
@@ -21,8 +21,10 @@ export const AssistantMessageModule = (factory: ModuleFactory) =>
21
21
  `suggestedQuestions`,
22
22
  `inputTokens`,
23
23
  `outputTokens`,
24
+ `messageType`,
24
25
  `references`,
25
26
  `citations`,
27
+ `action`,
26
28
  ]),
27
29
  createJsonApiInclusion("chunks", [`content`, `nodeId`, `nodeType`, `imagePath`, `source`]),
28
30
  createJsonApiInclusion("users", [`name`, `avatar`]),
@@ -2,20 +2,36 @@
2
2
 
3
3
  import { useTranslations } from "next-intl";
4
4
  import { Sparkles, AlertCircle } from "lucide-react";
5
+ import type { ReactNode } from "react";
5
6
  import ReactMarkdown from "react-markdown";
6
7
  import remarkGfm from "remark-gfm";
7
8
  import type { AssistantMessageInterface } from "../data/AssistantMessageInterface";
8
9
  import { MessageSourcesContainer } from "./parts/MessageSourcesContainer";
9
10
 
11
+ /**
12
+ * Render slot for `approval-request` messages. The approval card lives in the
13
+ * consuming app (it depends on the app-registered AssistantAction module), so
14
+ * the chat renderer receives it as a function instead of importing it.
15
+ */
16
+ export type ApprovalActionRenderer = (message: AssistantMessageInterface) => ReactNode;
17
+
10
18
  interface Props {
11
19
  message: AssistantMessageInterface;
12
20
  isLatestAssistant: boolean;
13
21
  onSelectFollowUp: (q: string) => void;
14
22
  failedMessageIds?: Set<string>;
15
23
  onRetry?: (tempId: string) => void;
24
+ renderApprovalAction?: ApprovalActionRenderer;
16
25
  }
17
26
 
18
- export function MessageItem({ message, isLatestAssistant, onSelectFollowUp, failedMessageIds, onRetry }: Props) {
27
+ export function MessageItem({
28
+ message,
29
+ isLatestAssistant,
30
+ onSelectFollowUp,
31
+ failedMessageIds,
32
+ onRetry,
33
+ renderApprovalAction,
34
+ }: Props) {
19
35
  const t = useTranslations();
20
36
  const isUser = message.role === "user";
21
37
  const isFailed = isUser && !!failedMessageIds?.has(message.id);
@@ -39,6 +55,8 @@ export function MessageItem({ message, isLatestAssistant, onSelectFollowUp, fail
39
55
  );
40
56
  }
41
57
 
58
+ const isApprovalRequest = message.messageType === "approval-request" && !!renderApprovalAction;
59
+
42
60
  return (
43
61
  <div className="flex min-w-0 max-w-[78%] flex-col gap-1.5">
44
62
  <div className="text-muted-foreground flex items-center gap-2 pl-1 text-xs">
@@ -47,9 +65,13 @@ export function MessageItem({ message, isLatestAssistant, onSelectFollowUp, fail
47
65
  </span>
48
66
  <span>{t("features.assistant.agent_name")}</span>
49
67
  </div>
50
- <div className="bg-muted text-foreground rounded-2xl rounded-bl-sm px-3.5 py-2.5 text-sm leading-relaxed">
51
- <ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown>
52
- </div>
68
+ {isApprovalRequest ? (
69
+ renderApprovalAction(message)
70
+ ) : (
71
+ <div className="bg-muted text-foreground rounded-2xl rounded-bl-sm px-3.5 py-2.5 text-sm leading-relaxed">
72
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown>
73
+ </div>
74
+ )}
53
75
  <MessageSourcesContainer
54
76
  message={message}
55
77
  isLatestAssistant={isLatestAssistant}
@@ -1,16 +1,17 @@
1
1
  "use client";
2
2
 
3
3
  import { AssistantMessageInterface } from "../data/AssistantMessageInterface";
4
- import { MessageItem } from "./MessageItem";
4
+ import { MessageItem, type ApprovalActionRenderer } from "./MessageItem";
5
5
 
6
6
  interface Props {
7
7
  messages: AssistantMessageInterface[];
8
8
  onSelectFollowUp: (q: string) => void;
9
9
  failedMessageIds?: Set<string>;
10
10
  onRetry?: (tempId: string) => void;
11
+ renderApprovalAction?: ApprovalActionRenderer;
11
12
  }
12
13
 
13
- export function MessageList({ messages, onSelectFollowUp, failedMessageIds, onRetry }: Props) {
14
+ export function MessageList({ messages, onSelectFollowUp, failedMessageIds, onRetry, renderApprovalAction }: Props) {
14
15
  const ordered = [...messages].sort((a, b) => a.position - b.position);
15
16
 
16
17
  let lastAssistantIndex = -1;
@@ -31,6 +32,7 @@ export function MessageList({ messages, onSelectFollowUp, failedMessageIds, onRe
31
32
  onSelectFollowUp={onSelectFollowUp}
32
33
  failedMessageIds={failedMessageIds}
33
34
  onRetry={onRetry}
35
+ renderApprovalAction={renderApprovalAction}
34
36
  />
35
37
  ))}
36
38
  </div>
@@ -1,6 +1,11 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
2
  import { AbstractApiData, ApiDataInterface, JsonApiHydratedDataInterface, Modules } from "../../../core";
3
- import { AssistantMessageInput, AssistantMessageInterface, AssistantMessageRole } from "./AssistantMessageInterface";
3
+ import {
4
+ AssistantMessageInput,
5
+ AssistantMessageInterface,
6
+ AssistantMessageRole,
7
+ AssistantMessageType,
8
+ } from "./AssistantMessageInterface";
4
9
  import { resolveReferenceableModules } from "../../assistant/utils/resolveReferenceableModules";
5
10
  import { ChunkInterface, ChunkRelationshipMeta } from "../../chunk/data/ChunkInterface";
6
11
 
@@ -14,6 +19,8 @@ export class AssistantMessage extends AbstractApiData implements AssistantMessag
14
19
  private _references?: ApiDataInterface[];
15
20
  private _citations?: (ChunkInterface & ChunkRelationshipMeta)[];
16
21
  private _isOptimistic = false;
22
+ private _messageType?: AssistantMessageType;
23
+ private _actionId?: string;
17
24
 
18
25
  get role(): AssistantMessageRole {
19
26
  return this._role ?? "assistant";
@@ -51,6 +58,14 @@ export class AssistantMessage extends AbstractApiData implements AssistantMessag
51
58
  return this._isOptimistic;
52
59
  }
53
60
 
61
+ get messageType(): AssistantMessageType {
62
+ return this._messageType ?? "text";
63
+ }
64
+
65
+ get actionId(): string | undefined {
66
+ return this._actionId;
67
+ }
68
+
54
69
  rehydrate(data: JsonApiHydratedDataInterface): this {
55
70
  super.rehydrate(data);
56
71
  const attrs = data.jsonApi.attributes ?? {};
@@ -60,6 +75,11 @@ export class AssistantMessage extends AbstractApiData implements AssistantMessag
60
75
  this._suggestedQuestions = Array.isArray(attrs.suggestedQuestions) ? attrs.suggestedQuestions : [];
61
76
  this._inputTokens = attrs.inputTokens;
62
77
  this._outputTokens = attrs.outputTokens;
78
+ this._messageType = attrs.messageType as AssistantMessageType | undefined;
79
+ // The AssistantAction module is app-registered, so only the raw
80
+ // relationship id is exposed (no hydration of the included resource here).
81
+ const actionRelationship = data.jsonApi.relationships?.action?.data;
82
+ this._actionId = Array.isArray(actionRelationship) ? actionRelationship[0]?.id : actionRelationship?.id;
63
83
  const refs = this._readIncludedPolymorphic<ApiDataInterface>(data, "references", resolveReferenceableModules());
64
84
  this._references = Array.isArray(refs) ? refs : refs ? [refs] : [];
65
85
  if (data.jsonApi.relationships?.citations?.data) {
@@ -3,6 +3,14 @@ import { ChunkInterface, ChunkRelationshipMeta } from "../../chunk/data/ChunkInt
3
3
 
4
4
  export type AssistantMessageRole = "user" | "assistant" | "system";
5
5
 
6
+ /**
7
+ * "text" is the default chat message; "approval-request" marks the message
8
+ * created while an operator run is frozen on a destructive tool call,
9
+ * awaiting human approval (linked AssistantAction via the `action`
10
+ * relationship).
11
+ */
12
+ export type AssistantMessageType = "text" | "approval-request";
13
+
6
14
  export type AssistantMessageInput = {
7
15
  id: string;
8
16
  role: AssistantMessageRole;
@@ -21,4 +29,12 @@ export interface AssistantMessageInterface extends ApiDataInterface {
21
29
  get references(): ApiDataInterface[];
22
30
  get citations(): (ChunkInterface & ChunkRelationshipMeta)[];
23
31
  get isOptimistic(): boolean;
32
+ get messageType(): AssistantMessageType;
33
+ /**
34
+ * Id of the AssistantAction linked through the `action` relationship.
35
+ * Only present on `approval-request` messages. Exposed as a raw id (not a
36
+ * hydrated model) because the AssistantAction module is registered by the
37
+ * consuming app, not by this library.
38
+ */
39
+ get actionId(): string | undefined;
24
40
  }