@worktango/ai-assistant 0.0.25 → 0.0.28

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 (28) hide show
  1. package/dist/express.js +3 -2
  2. package/package.json +3 -7
  3. package/types.d.ts +24 -2
  4. package/express.d.ts +0 -1
  5. package/src/backend/express.test.ts +0 -208
  6. package/src/backend/express.ts +0 -98
  7. package/src/frontend/AiAssistant.test.tsx +0 -19
  8. package/src/frontend/AiAssistant.tsx +0 -26
  9. package/src/frontend/components/AiAssistantComponent.scss +0 -24
  10. package/src/frontend/components/AiAssistantComponent.test.tsx +0 -270
  11. package/src/frontend/components/AiAssistantComponent.tsx +0 -246
  12. package/src/frontend/components/messageRenderers/AssistantMessage.test.tsx +0 -154
  13. package/src/frontend/components/messageRenderers/AssistantMessage.tsx +0 -104
  14. package/src/frontend/components/messageRenderers/UserMessage.test.tsx +0 -103
  15. package/src/frontend/components/messageRenderers/UserMessage.tsx +0 -43
  16. package/src/frontend/config.ts +0 -54
  17. package/src/frontend/reactComponent.ts +0 -6
  18. package/src/frontend/tools/generatedSdks.ts +0 -44
  19. package/src/frontend/tools/llm-tools/getCurrentUser.test.ts +0 -100
  20. package/src/frontend/tools/llm-tools/getCurrentUser.ts +0 -158
  21. package/src/frontend/tools/llm-tools/index.ts +0 -6
  22. package/src/frontend/tools/llm-tools/kazooPlatform/getRewardsAndRecognitionCurrentUser.test.ts +0 -76
  23. package/src/frontend/tools/llm-tools/kazooPlatform/getRewardsAndRecognitionCurrentUser.ts +0 -36
  24. package/src/frontend/tools/llm-tools/pulsePlatform/.gitkeep +0 -0
  25. package/src/frontend/tools/llm-tools/shared/.gitkeep +0 -0
  26. package/src/frontend/tools/useAiAssistantSession.test.ts +0 -768
  27. package/src/frontend/tools/useAiAssistantSession.ts +0 -374
  28. package/src/frontend/useStyles.ts +0 -36
package/dist/express.js CHANGED
@@ -50,17 +50,18 @@ function setupAiAssistantRoutes(args) {
50
50
  if (!bearerToken) {
51
51
  return;
52
52
  }
53
- const parsedUrl = new URL(req.url);
53
+ const targetPath = args.getTargetPath(req);
54
54
  const options = {
55
55
  hostname: args.targetHost,
56
56
  port: args.targetPort,
57
- path: parsedUrl.pathname,
57
+ path: targetPath,
58
58
  method: req.method,
59
59
  headers: { ...req.headers }
60
60
  };
61
61
  options.headers["Authorization"] = `Bearer ${bearerToken}`;
62
62
  delete options.headers.host;
63
63
  logger.debug("Proxying request:", {
64
+ targetPath,
64
65
  method: req.method,
65
66
  headers: options.headers,
66
67
  proxyOptions: options
package/package.json CHANGED
@@ -1,15 +1,11 @@
1
1
  {
2
2
  "name": "@worktango/ai-assistant",
3
- "version": "0.0.25",
4
- "types": "./types.d.ts",
3
+ "version": "0.0.28",
4
+ "types": "types.d.ts",
5
5
  "files": [
6
6
  "dist",
7
- "src",
8
- "main.js",
9
7
  "types.d.ts",
10
8
  "express.js",
11
- "express.d.ts",
12
- "react.ts",
13
9
  "react.js"
14
10
  ],
15
11
  "scripts": {
@@ -18,7 +14,7 @@
18
14
  "build:backend": "esbuild src/backend/express.ts --bundle --platform=node --outfile=dist/express.js \"--external:*\"",
19
15
  "build:frontend": "vite build",
20
16
  "build:all": "rm -rf dist && yarn build:backend && yarn build:frontend && yarn ts-node -T scripts/injectCssIntoBuild.ts",
21
- "prepareBuildForPublish": "yarn ts-node -T scripts/prepareBuildForPublish.ts && mv react.public.js react.js && mv express.public.js express.js && mv react.ts react.d.ts",
17
+ "prepareBuildForPublish": "yarn ts-node -T scripts/prepareBuildForPublish.ts",
22
18
  "publish": "yarn build:all && yarn prepareBuildForPublish && yarn actuallyPublishPackage",
23
19
  "actuallyPublishPackage": "yarn npm publish --access public --tolerate-republish",
24
20
  "gql:generate": "gql-generate"
package/types.d.ts CHANGED
@@ -32,8 +32,24 @@ declare module "@worktango/ai-assistant/react" {
32
32
  tools?: ToolDeclaration[];
33
33
  }): JSX.Element;
34
34
 
35
- export * as tools from "./src/frontend/tools/llm-tools/index";
36
- export * as config from "./src/frontend/config";
35
+ export namespace tools {
36
+ export const kazooTools: {
37
+ getRewardsAndRecognitionCurrentUserTool: ToolDeclaration;
38
+ };
39
+ }
40
+
41
+ export namespace config {
42
+ export function getLlmCallUrl(): string;
43
+ export function getKazooUrl(urlPath: string): string;
44
+ export function getPulseUrl(urlPath: string): string;
45
+ export function setKazooUrlStrategy(
46
+ strategy: (urlPath: string) => string
47
+ ): void;
48
+ export function setLlmCallUrlStrategy(strategy: () => string): void;
49
+ export function setPulseUrlStrategy(
50
+ strategy: (urlPath: string) => string
51
+ ): void;
52
+ }
37
53
  }
38
54
 
39
55
  declare module "@worktango/ai-assistant/express" {
@@ -68,5 +84,11 @@ declare module "@worktango/ai-assistant/express" {
68
84
  * Middleware functions to run before the proxy request.
69
85
  */
70
86
  preflightMiddlewares?: RequestHandler[];
87
+ /**
88
+ * A function that should return the target path for the proxy request, based
89
+ * on the incoming request object. The path returned will be appended to the
90
+ * target host and port to form the final URL.
91
+ */
92
+ getTargetPath: (req: Request) => string;
71
93
  }): void;
72
94
  }
package/express.d.ts DELETED
@@ -1 +0,0 @@
1
- export { setupAiAssistantRoutes } from "./src/backend/express";
@@ -1,208 +0,0 @@
1
- import http from "http";
2
-
3
- import { Express } from "express";
4
-
5
- import { setupAiAssistantRoutes } from "./express";
6
-
7
- jest.mock("http", () => ({
8
- request: jest.fn().mockImplementation((options, callback) => {
9
- const mockProxyRes = {
10
- statusCode: 200,
11
- headers: { "content-type": "application/json" },
12
- pipe: jest.fn(),
13
- };
14
- callback(mockProxyRes);
15
- return {
16
- on: jest.fn(),
17
- end: jest.fn(),
18
- };
19
- }),
20
- }));
21
-
22
- describe("setupAiAssistantRoutes", () => {
23
- let mockApp: jest.Mocked<Express>;
24
- let mockPost: jest.Mock;
25
- let mockGetBearerToken: jest.Mock;
26
- let mockReq: any;
27
- let mockRes: any;
28
- let handlerPromise: Promise<void>;
29
-
30
- const mockLogger = {
31
- log: jest.fn(),
32
- error: jest.fn(),
33
- debug: jest.fn(),
34
- };
35
-
36
- beforeEach(() => {
37
- mockReq = {
38
- url: "http://localhost/ai-assistant",
39
- method: "POST",
40
- headers: { "content-type": "application/json" },
41
- readable: false,
42
- };
43
- mockRes = {
44
- statusCode: 200,
45
- setHeader: jest.fn(),
46
- status: jest.fn().mockReturnThis(),
47
- send: jest.fn(),
48
- };
49
- mockPost = jest.fn().mockImplementation((path, handler) => {
50
- handlerPromise = handler(mockReq, mockRes);
51
- });
52
- mockGetBearerToken = jest.fn().mockResolvedValue("mock-token");
53
-
54
- mockApp = {
55
- post: mockPost,
56
- all: mockPost,
57
- } as unknown as jest.Mocked<Express>;
58
-
59
- (http.request as jest.Mock).mockClear();
60
- });
61
-
62
- it("sets up the default route correctly", async () => {
63
- setupAiAssistantRoutes({
64
- app: mockApp,
65
- getBearerToken: mockGetBearerToken,
66
- targetHost: "localhost",
67
- logger: mockLogger,
68
- });
69
-
70
- expect(mockPost).toHaveBeenCalledWith(
71
- "/ai-assistant",
72
- expect.any(Function)
73
- );
74
-
75
- await handlerPromise;
76
-
77
- expect(http.request).toHaveBeenCalledWith(
78
- expect.objectContaining({
79
- hostname: "localhost",
80
- path: "/ai-assistant",
81
- headers: expect.objectContaining({
82
- Authorization: "Bearer mock-token",
83
- }),
84
- }),
85
- expect.any(Function)
86
- );
87
- });
88
-
89
- it("sets up a custom route when provided", async () => {
90
- mockReq.url = "http://localhost/custom-route";
91
-
92
- setupAiAssistantRoutes({
93
- app: mockApp,
94
- getBearerToken: mockGetBearerToken,
95
- targetHost: "localhost",
96
- route: "/custom-route",
97
- logger: mockLogger,
98
- });
99
-
100
- expect(mockPost).toHaveBeenCalledWith(
101
- "/custom-route",
102
- expect.any(Function)
103
- );
104
-
105
- await handlerPromise;
106
-
107
- expect(http.request).toHaveBeenCalledWith(
108
- expect.objectContaining({
109
- hostname: "localhost",
110
- path: "/custom-route",
111
- headers: expect.objectContaining({
112
- Authorization: "Bearer mock-token",
113
- }),
114
- }),
115
- expect.any(Function)
116
- );
117
- });
118
-
119
- it("handles errors when getting the bearer token", async () => {
120
- mockGetBearerToken.mockRejectedValueOnce(
121
- new Error("Error getting bearer token")
122
- );
123
-
124
- setupAiAssistantRoutes({
125
- app: mockApp,
126
- getBearerToken: mockGetBearerToken,
127
- targetHost: "localhost",
128
- logger: mockLogger,
129
- });
130
-
131
- await handlerPromise;
132
-
133
- expect(mockRes.status).toHaveBeenCalledWith(500);
134
- expect(mockRes.send).toHaveBeenCalledWith("Error getting bearer token");
135
- });
136
-
137
- it("handles proxy errors correctly", async () => {
138
- const mockError = new Error("Proxy error");
139
- (http.request as jest.Mock).mockImplementationOnce(() => ({
140
- on: jest.fn().mockImplementation((event, handler) => {
141
- if (event === "error") {
142
- handler(mockError);
143
- }
144
- }),
145
- end: jest.fn(),
146
- }));
147
-
148
- setupAiAssistantRoutes({
149
- app: mockApp,
150
- getBearerToken: mockGetBearerToken,
151
- targetHost: "localhost",
152
- logger: mockLogger,
153
- });
154
-
155
- await handlerPromise;
156
-
157
- expect(mockRes.status).toHaveBeenCalledWith(500);
158
- expect(mockRes.send).toHaveBeenCalledWith("Proxy Error: Proxy error");
159
- });
160
-
161
- it("handles readable request bodies", async () => {
162
- mockReq.readable = true;
163
- mockReq.pipe = jest.fn();
164
-
165
- setupAiAssistantRoutes({
166
- app: mockApp,
167
- getBearerToken: mockGetBearerToken,
168
- targetHost: "localhost",
169
- logger: mockLogger,
170
- });
171
-
172
- await handlerPromise;
173
-
174
- expect(mockReq.pipe).toHaveBeenCalled();
175
- });
176
-
177
- it("handles response headers and status code correctly", async () => {
178
- const mockProxyRes = {
179
- statusCode: undefined,
180
- headers: {
181
- "content-type": undefined,
182
- "x-custom-header": "value",
183
- },
184
- pipe: jest.fn(),
185
- };
186
-
187
- (http.request as jest.Mock).mockImplementationOnce((options, callback) => {
188
- callback(mockProxyRes);
189
- return {
190
- on: jest.fn(),
191
- end: jest.fn(),
192
- };
193
- });
194
-
195
- setupAiAssistantRoutes({
196
- app: mockApp,
197
- getBearerToken: mockGetBearerToken,
198
- targetHost: "localhost",
199
- logger: mockLogger,
200
- });
201
-
202
- await handlerPromise;
203
-
204
- expect(mockRes.statusCode).toBe(500);
205
- expect(mockRes.setHeader).toHaveBeenCalledWith("x-custom-header", "value");
206
- expect(mockRes.setHeader).toHaveBeenCalledWith("content-type", "");
207
- });
208
- });
@@ -1,98 +0,0 @@
1
- import http from "http";
2
-
3
- import { Express, RequestHandler } from "express";
4
-
5
- import { coalesce } from "@kazoohr/helpers";
6
-
7
- /**
8
- * Sets up the AI assistant routes on the given Express app.
9
- */
10
- export function setupAiAssistantRoutes(args: {
11
- app: Express;
12
- /**
13
- * A function that returns the bearer token for the AI assistant to use for
14
- * back-channel request proxying. This should be a JWT token that can be
15
- * decoded on the `kazoo-web` application to authenticate the request.
16
- */
17
- getBearerToken: () => Promise<string>;
18
- /**
19
- * The host of the application to proxy requests to.
20
- */
21
- targetHost: string;
22
- /**
23
- * The port of the application to proxy requests to. Defaults to 80.
24
- */
25
- targetPort?: number;
26
- /**
27
- * The route path to the local service's AI assistant. Defaults to
28
- * "/ai-assistant".
29
- */
30
- route?: string;
31
- /**
32
- * The logger to use for logging.
33
- */
34
- logger?: Pick<typeof console, "log" | "error" | "debug">;
35
- /**
36
- * Middleware functions to run before the proxy request.
37
- */
38
- preflightMiddlewares?: RequestHandler[];
39
- }) {
40
- const logger = coalesce(args.logger, console);
41
- const routePath = args.route ?? "/ai-assistant";
42
-
43
- args.app.all(
44
- routePath,
45
- ...coalesce(args.preflightMiddlewares, []),
46
- async (req, res) => {
47
- const bearerToken = await args.getBearerToken().catch((error) => {
48
- logger.error("Error getting bearer token:", error);
49
- res.status(500).send("Error getting bearer token");
50
- return null;
51
- });
52
-
53
- if (!bearerToken) {
54
- return;
55
- }
56
-
57
- const parsedUrl = new URL(req.url);
58
-
59
- const options = {
60
- hostname: args.targetHost,
61
- port: args.targetPort,
62
- path: parsedUrl.pathname,
63
- method: req.method,
64
- headers: { ...req.headers },
65
- };
66
-
67
- options.headers["Authorization"] = `Bearer ${bearerToken}`;
68
- delete options.headers.host;
69
-
70
- logger.debug("Proxying request:", {
71
- method: req.method,
72
- headers: options.headers,
73
- proxyOptions: options,
74
- });
75
-
76
- const proxyReq = http.request(options, (proxyRes) => {
77
- res.statusCode = proxyRes.statusCode || 500;
78
-
79
- Object.keys(proxyRes.headers).forEach((key) => {
80
- res.setHeader(key, proxyRes.headers[key] || "");
81
- });
82
-
83
- proxyRes.pipe(res);
84
- });
85
-
86
- proxyReq.on("error", (error) => {
87
- logger.error("Proxy request error:", error);
88
- res.status(500).send("Proxy Error: " + error.message);
89
- });
90
-
91
- if (req.readable) {
92
- req.pipe(proxyReq);
93
- } else {
94
- proxyReq.end();
95
- }
96
- }
97
- );
98
- }
@@ -1,19 +0,0 @@
1
- import React from "react";
2
-
3
- import { render, screen, waitFor } from "@kazoohr/test";
4
-
5
- import { AiAssistant } from "./AiAssistant";
6
-
7
- jest.mock("./components/AiAssistantComponent", () => ({
8
- AiAssistantComponent: () => <div>AiAssistantComponent</div>,
9
- }));
10
-
11
- describe("AiAssistant", () => {
12
- it("should render", async () => {
13
- const { rerender } = render(<AiAssistant tools={[]} />);
14
- await waitFor(() => {
15
- rerender(<AiAssistant />);
16
- expect(screen.queryByText("AiAssistantComponent")).toBeTruthy();
17
- });
18
- });
19
- });
@@ -1,26 +0,0 @@
1
- import React from "react";
2
-
3
- import { ToastProvider } from "@kazoohr/confetti";
4
- import { ToolDeclaration } from "@kazoohr/llm/types";
5
-
6
- import { AiAssistantComponent } from "./components/AiAssistantComponent";
7
- import { useStyles } from "./useStyles";
8
-
9
- /**
10
- * This function loads the CSS styles for Confetti (and anything else we styled
11
- * custom in this app) and injects them into the page. While it's doing that, we
12
- * show a very minimal loader.
13
- */
14
- export function AiAssistant(props: { tools?: ToolDeclaration[] }) {
15
- const { isLoaded } = useStyles();
16
-
17
- if (!isLoaded) {
18
- return null;
19
- }
20
-
21
- return (
22
- <ToastProvider>
23
- <AiAssistantComponent tools={props.tools} />
24
- </ToastProvider>
25
- );
26
- }
@@ -1,24 +0,0 @@
1
- .ai-assistant-textarea {
2
- border-radius: 0;
3
- background-color: $color--system-gray-6;
4
- border-radius: 8px;
5
- }
6
-
7
- .ai-assistant-textarea * {
8
- border: none !important;
9
- background-color: transparent !important;
10
- border-radius: 8px !important;
11
- }
12
-
13
- .ai-assistant-textarea [class*="TextArea_textarea__highlighter"] {
14
- min-height: 200px;
15
- }
16
-
17
- .ai-assistant-debug-textarea {
18
- min-width: 100%;
19
- height: auto;
20
- max-width: 100%;
21
- aspect-ratio: 3/1;
22
- background-color: $color--system-gray-6;
23
- padding: 8px;
24
- }