nitrostack 1.0.74 → 1.0.75
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/dist/auth/__tests__/pkce.test.js +53 -4
- package/dist/auth/__tests__/pkce.test.js.map +1 -1
- package/dist/auth/__tests__/simple-jwt.test.js +153 -2
- package/dist/auth/__tests__/simple-jwt.test.js.map +1 -1
- package/dist/cli/commands/install.d.ts +10 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +79 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +8 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/__tests__/errors.test.d.ts +2 -0
- package/dist/core/__tests__/errors.test.d.ts.map +1 -0
- package/dist/core/__tests__/errors.test.js +109 -0
- package/dist/core/__tests__/errors.test.js.map +1 -0
- package/dist/core/__tests__/logger.test.d.ts +2 -0
- package/dist/core/__tests__/logger.test.d.ts.map +1 -0
- package/dist/core/__tests__/logger.test.js +83 -0
- package/dist/core/__tests__/logger.test.js.map +1 -0
- package/dist/core/__tests__/prompt.test.d.ts +2 -0
- package/dist/core/__tests__/prompt.test.d.ts.map +1 -0
- package/dist/core/__tests__/prompt.test.js +126 -0
- package/dist/core/__tests__/prompt.test.js.map +1 -0
- package/dist/core/__tests__/resource.test.d.ts +2 -0
- package/dist/core/__tests__/resource.test.d.ts.map +1 -0
- package/dist/core/__tests__/resource.test.js +173 -0
- package/dist/core/__tests__/resource.test.js.map +1 -0
- package/dist/core/pipes/__tests__/pipes.test.js +113 -1
- package/dist/core/pipes/__tests__/pipes.test.js.map +1 -1
- package/dist/widgets/hooks/__tests__/hooks.test.d.ts +2 -0
- package/dist/widgets/hooks/__tests__/hooks.test.d.ts.map +1 -0
- package/dist/widgets/hooks/__tests__/hooks.test.js +129 -0
- package/dist/widgets/hooks/__tests__/hooks.test.js.map +1 -0
- package/dist/widgets/hooks/__tests__/use-widget-state.test.d.ts +2 -0
- package/dist/widgets/hooks/__tests__/use-widget-state.test.d.ts.map +1 -0
- package/dist/widgets/hooks/__tests__/use-widget-state.test.js +66 -0
- package/dist/widgets/hooks/__tests__/use-widget-state.test.js.map +1 -0
- package/dist/widgets/runtime/__tests__/widget-polyfill.test.d.ts +2 -0
- package/dist/widgets/runtime/__tests__/widget-polyfill.test.d.ts.map +1 -0
- package/dist/widgets/runtime/__tests__/widget-polyfill.test.js +66 -0
- package/dist/widgets/runtime/__tests__/widget-polyfill.test.js.map +1 -0
- package/package.json +6 -2
- package/src/studio/app/chat/page.tsx +21 -0
- package/src/studio/lib/llm-service.ts +77 -10
- package/templates/typescript-oauth/OAUTH_SETUP.md +592 -0
- package/templates/typescript-oauth/README.md +186 -235
- package/templates/typescript-oauth/package.json +5 -3
- package/templates/typescript-pizzaz/README.md +78 -57
- package/templates/typescript-pizzaz/package.json +32 -30
- package/templates/typescript-starter/README.md +22 -14
- package/templates/typescript-starter/package.json +4 -1
- package/templates/typescript-starter/package-lock.json +0 -4112
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
// Mock React with any type to avoid strict typing issues
|
|
3
|
+
const mockUseSyncExternalStore = jest.fn();
|
|
4
|
+
const mockUseState = jest.fn();
|
|
5
|
+
const mockUseEffect = jest.fn();
|
|
6
|
+
jest.unstable_mockModule('react', () => ({
|
|
7
|
+
useSyncExternalStore: mockUseSyncExternalStore,
|
|
8
|
+
useState: mockUseState,
|
|
9
|
+
useEffect: mockUseEffect,
|
|
10
|
+
}));
|
|
11
|
+
// Mock window.openai
|
|
12
|
+
const mockOpenai = {
|
|
13
|
+
theme: 'dark',
|
|
14
|
+
displayMode: 'inline',
|
|
15
|
+
maxHeight: 500,
|
|
16
|
+
locale: 'en-US',
|
|
17
|
+
userAgent: 'test-agent',
|
|
18
|
+
};
|
|
19
|
+
// Set up window with openai
|
|
20
|
+
global.window = {
|
|
21
|
+
openai: mockOpenai,
|
|
22
|
+
addEventListener: jest.fn(),
|
|
23
|
+
removeEventListener: jest.fn(),
|
|
24
|
+
};
|
|
25
|
+
describe('Widget Hooks', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
// Reset useSyncExternalStore to call getSnapshot
|
|
29
|
+
mockUseSyncExternalStore.mockImplementation((_sub, getSnapshot) => {
|
|
30
|
+
return getSnapshot();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('useOpenAiGlobal', () => {
|
|
34
|
+
it('should return theme from window.openai', async () => {
|
|
35
|
+
const { useOpenAiGlobal } = await import('../use-openai-global.js');
|
|
36
|
+
const result = useOpenAiGlobal('theme');
|
|
37
|
+
expect(result).toBe('dark');
|
|
38
|
+
});
|
|
39
|
+
it('should return displayMode from window.openai', async () => {
|
|
40
|
+
const { useOpenAiGlobal } = await import('../use-openai-global.js');
|
|
41
|
+
const result = useOpenAiGlobal('displayMode');
|
|
42
|
+
expect(result).toBe('inline');
|
|
43
|
+
});
|
|
44
|
+
it('should return maxHeight from window.openai', async () => {
|
|
45
|
+
const { useOpenAiGlobal } = await import('../use-openai-global.js');
|
|
46
|
+
const result = useOpenAiGlobal('maxHeight');
|
|
47
|
+
expect(result).toBe(500);
|
|
48
|
+
});
|
|
49
|
+
it('should return locale from window.openai', async () => {
|
|
50
|
+
const { useOpenAiGlobal } = await import('../use-openai-global.js');
|
|
51
|
+
const result = useOpenAiGlobal('locale');
|
|
52
|
+
expect(result).toBe('en-US');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('useTheme', () => {
|
|
56
|
+
it('should return theme value', async () => {
|
|
57
|
+
const { useTheme } = await import('../use-theme.js');
|
|
58
|
+
const result = useTheme();
|
|
59
|
+
expect(result).toBe('dark');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('useDisplayMode', () => {
|
|
63
|
+
it('should return displayMode value', async () => {
|
|
64
|
+
const { useDisplayMode } = await import('../use-display-mode.js');
|
|
65
|
+
const result = useDisplayMode();
|
|
66
|
+
expect(result).toBe('inline');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('useMaxHeight', () => {
|
|
70
|
+
it('should return maxHeight value', async () => {
|
|
71
|
+
const { useMaxHeight } = await import('../use-max-height.js');
|
|
72
|
+
const result = useMaxHeight();
|
|
73
|
+
expect(result).toBe(500);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('useWidgetSDK', () => {
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
// Mock useState to return initialized values
|
|
79
|
+
let sdkInstance = null;
|
|
80
|
+
mockUseState.mockImplementation((initializer) => {
|
|
81
|
+
if (typeof initializer === 'function') {
|
|
82
|
+
if (!sdkInstance) {
|
|
83
|
+
sdkInstance = initializer();
|
|
84
|
+
}
|
|
85
|
+
return [sdkInstance, jest.fn()];
|
|
86
|
+
}
|
|
87
|
+
return [initializer, jest.fn()];
|
|
88
|
+
});
|
|
89
|
+
// Mock useEffect to run synchronously
|
|
90
|
+
mockUseEffect.mockImplementation((effect) => {
|
|
91
|
+
effect();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
it('should return SDK methods', async () => {
|
|
95
|
+
const { useWidgetSDK } = await import('../useWidgetSDK.js');
|
|
96
|
+
const result = useWidgetSDK();
|
|
97
|
+
expect(result.sdk).toBeDefined();
|
|
98
|
+
expect(typeof result.isReady).toBe('boolean');
|
|
99
|
+
expect(typeof result.setState).toBe('function');
|
|
100
|
+
expect(typeof result.getState).toBe('function');
|
|
101
|
+
expect(typeof result.callTool).toBe('function');
|
|
102
|
+
expect(typeof result.requestFullscreen).toBe('function');
|
|
103
|
+
expect(typeof result.requestInline).toBe('function');
|
|
104
|
+
expect(typeof result.requestPip).toBe('function');
|
|
105
|
+
expect(typeof result.requestDisplayMode).toBe('function');
|
|
106
|
+
expect(typeof result.requestClose).toBe('function');
|
|
107
|
+
});
|
|
108
|
+
it('should return data access methods', async () => {
|
|
109
|
+
const { useWidgetSDK } = await import('../useWidgetSDK.js');
|
|
110
|
+
const result = useWidgetSDK();
|
|
111
|
+
expect(typeof result.getToolInput).toBe('function');
|
|
112
|
+
expect(typeof result.getToolOutput).toBe('function');
|
|
113
|
+
expect(typeof result.getToolResponseMetadata).toBe('function');
|
|
114
|
+
expect(typeof result.getTheme).toBe('function');
|
|
115
|
+
expect(typeof result.getMaxHeight).toBe('function');
|
|
116
|
+
expect(typeof result.getDisplayMode).toBe('function');
|
|
117
|
+
expect(typeof result.getUserAgent).toBe('function');
|
|
118
|
+
expect(typeof result.getLocale).toBe('function');
|
|
119
|
+
expect(typeof result.getSafeArea).toBe('function');
|
|
120
|
+
});
|
|
121
|
+
it('should return navigation methods', async () => {
|
|
122
|
+
const { useWidgetSDK } = await import('../useWidgetSDK.js');
|
|
123
|
+
const result = useWidgetSDK();
|
|
124
|
+
expect(typeof result.openExternal).toBe('function');
|
|
125
|
+
expect(typeof result.sendFollowUpMessage).toBe('function');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=hooks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.test.js","sourceRoot":"","sources":["../../../../src/widgets/hooks/__tests__/hooks.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-widget-state.test.d.ts","sourceRoot":"","sources":["../../../../src/widgets/hooks/__tests__/use-widget-state.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
// Mock React hooks
|
|
3
|
+
const mockUseSyncExternalStore = jest.fn();
|
|
4
|
+
const mockUseState = jest.fn();
|
|
5
|
+
const mockUseEffect = jest.fn();
|
|
6
|
+
const mockUseCallback = jest.fn();
|
|
7
|
+
jest.unstable_mockModule('react', () => ({
|
|
8
|
+
useSyncExternalStore: mockUseSyncExternalStore,
|
|
9
|
+
useState: mockUseState,
|
|
10
|
+
useEffect: mockUseEffect,
|
|
11
|
+
useCallback: mockUseCallback,
|
|
12
|
+
}));
|
|
13
|
+
// Mock window.openai
|
|
14
|
+
global.window = {
|
|
15
|
+
openai: {
|
|
16
|
+
widgetState: undefined,
|
|
17
|
+
setWidgetState: jest.fn(),
|
|
18
|
+
},
|
|
19
|
+
addEventListener: jest.fn(),
|
|
20
|
+
removeEventListener: jest.fn(),
|
|
21
|
+
};
|
|
22
|
+
describe('useWidgetState', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
// Mock useSyncExternalStore to return null (no widget state from window)
|
|
26
|
+
mockUseSyncExternalStore.mockImplementation(() => null);
|
|
27
|
+
// Mock useState with proper state management
|
|
28
|
+
mockUseState.mockImplementation((initializer) => {
|
|
29
|
+
const value = typeof initializer === 'function' ? initializer() : initializer;
|
|
30
|
+
return [value, jest.fn()];
|
|
31
|
+
});
|
|
32
|
+
// Mock useEffect to run synchronously
|
|
33
|
+
mockUseEffect.mockImplementation((effect) => {
|
|
34
|
+
effect();
|
|
35
|
+
});
|
|
36
|
+
// Mock useCallback to return the function directly
|
|
37
|
+
mockUseCallback.mockImplementation((fn) => fn);
|
|
38
|
+
});
|
|
39
|
+
it('should return default state', async () => {
|
|
40
|
+
const { useWidgetState } = await import('../use-widget-state.js');
|
|
41
|
+
const [state] = useWidgetState({ count: 0 });
|
|
42
|
+
expect(state).toEqual({ count: 0 });
|
|
43
|
+
});
|
|
44
|
+
it('should return state from function initializer', async () => {
|
|
45
|
+
const { useWidgetState } = await import('../use-widget-state.js');
|
|
46
|
+
const [state] = useWidgetState(() => ({ items: [] }));
|
|
47
|
+
expect(state).toEqual({ items: [] });
|
|
48
|
+
});
|
|
49
|
+
it('should return null when no default provided', async () => {
|
|
50
|
+
const { useWidgetState } = await import('../use-widget-state.js');
|
|
51
|
+
const [state] = useWidgetState(null);
|
|
52
|
+
expect(state).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
it('should return widget state from window if available', async () => {
|
|
55
|
+
mockUseSyncExternalStore.mockImplementation(() => ({ savedData: 'value' }));
|
|
56
|
+
const { useWidgetState } = await import('../use-widget-state.js');
|
|
57
|
+
const [state] = useWidgetState({ default: 'value' });
|
|
58
|
+
expect(state).toEqual({ savedData: 'value' });
|
|
59
|
+
});
|
|
60
|
+
it('should return setState function', async () => {
|
|
61
|
+
const { useWidgetState } = await import('../use-widget-state.js');
|
|
62
|
+
const [, setState] = useWidgetState({ count: 0 });
|
|
63
|
+
expect(typeof setState).toBe('function');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=use-widget-state.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-widget-state.test.js","sourceRoot":"","sources":["../../../../src/widgets/hooks/__tests__/use-widget-state.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-polyfill.test.d.ts","sourceRoot":"","sources":["../../../../src/widgets/runtime/__tests__/widget-polyfill.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
describe('Widget Polyfill', () => {
|
|
3
|
+
let messageHandler = null;
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
// Reset
|
|
6
|
+
messageHandler = null;
|
|
7
|
+
global.window = {
|
|
8
|
+
addEventListener: jest.fn((event, handler) => {
|
|
9
|
+
if (event === 'message') {
|
|
10
|
+
messageHandler = handler;
|
|
11
|
+
}
|
|
12
|
+
}),
|
|
13
|
+
dispatchEvent: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
global.CustomEvent = jest.fn().mockImplementation((name) => ({ type: name }));
|
|
16
|
+
});
|
|
17
|
+
it('should be a module', async () => {
|
|
18
|
+
// Widget polyfill is an IIFE, we just need to check it can be loaded
|
|
19
|
+
// The actual functionality sets up window listeners
|
|
20
|
+
expect(true).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('should handle NITRO_INJECT_OPENAI message', async () => {
|
|
23
|
+
// Manually simulate what the polyfill does
|
|
24
|
+
const mockOpenai = { theme: 'dark' };
|
|
25
|
+
const event = {
|
|
26
|
+
data: {
|
|
27
|
+
type: 'NITRO_INJECT_OPENAI',
|
|
28
|
+
openai: mockOpenai
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
// Set up window
|
|
32
|
+
global.window = {
|
|
33
|
+
addEventListener: jest.fn(),
|
|
34
|
+
dispatchEvent: jest.fn(),
|
|
35
|
+
openai: undefined,
|
|
36
|
+
};
|
|
37
|
+
// Simulate the message handler behavior
|
|
38
|
+
if (event.data?.type === 'NITRO_INJECT_OPENAI') {
|
|
39
|
+
window.openai = event.data.openai;
|
|
40
|
+
}
|
|
41
|
+
expect(window.openai).toEqual(mockOpenai);
|
|
42
|
+
});
|
|
43
|
+
it('should handle TOOL_OUTPUT message', async () => {
|
|
44
|
+
const mockData = { key: 'value' };
|
|
45
|
+
const event = {
|
|
46
|
+
data: {
|
|
47
|
+
type: 'TOOL_OUTPUT',
|
|
48
|
+
data: mockData
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
// Set up window with openai already set
|
|
52
|
+
global.window = {
|
|
53
|
+
addEventListener: jest.fn(),
|
|
54
|
+
dispatchEvent: jest.fn(),
|
|
55
|
+
openai: { toolOutput: undefined },
|
|
56
|
+
};
|
|
57
|
+
// Simulate the message handler behavior
|
|
58
|
+
if (event.data?.type === 'TOOL_OUTPUT' && event.data?.data) {
|
|
59
|
+
if (window.openai) {
|
|
60
|
+
window.openai.toolOutput = event.data.data;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
expect(window.openai.toolOutput).toEqual(mockData);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=widget-polyfill.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-polyfill.test.js","sourceRoot":"","sources":["../../../../src/widgets/runtime/__tests__/widget-polyfill.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEvE,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC7B,IAAI,cAAc,GAAkC,IAAI,CAAC;IAEzD,UAAU,CAAC,GAAG,EAAE;QACZ,QAAQ;QACR,cAAc,GAAG,IAAI,CAAC;QACrB,MAAc,CAAC,MAAM,GAAG;YACrB,gBAAgB,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,KAAa,EAAE,OAAY,EAAE,EAAE;gBACtD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBACtB,cAAc,GAAG,OAAO,CAAC;gBAC7B,CAAC;YACL,CAAC,CAAC;YACF,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE;SAC3B,CAAC;QACD,MAAc,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QAChC,qEAAqE;QACrE,oDAAoD;QACpD,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACvD,2CAA2C;QAC3C,MAAM,UAAU,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG;YACV,IAAI,EAAE;gBACF,IAAI,EAAE,qBAAqB;gBAC3B,MAAM,EAAE,UAAU;aACrB;SACJ,CAAC;QAEF,gBAAgB;QACf,MAAc,CAAC,MAAM,GAAG;YACrB,gBAAgB,EAAE,IAAI,CAAC,EAAE,EAAE;YAC3B,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE;YACxB,MAAM,EAAE,SAAS;SACpB,CAAC;QAEF,wCAAwC;QACxC,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,KAAK,qBAAqB,EAAE,CAAC;YAC5C,MAAc,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;QAC/C,CAAC;QAED,MAAM,CAAE,MAAc,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG;YACV,IAAI,EAAE;gBACF,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,QAAQ;aACjB;SACJ,CAAC;QAEF,wCAAwC;QACvC,MAAc,CAAC,MAAM,GAAG;YACrB,gBAAgB,EAAE,IAAI,CAAC,EAAE,EAAE;YAC3B,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE;YACxB,MAAM,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;SACpC,CAAC;QAEF,wCAAwC;QACxC,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;YACzD,IAAK,MAAc,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAc,CAAC,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;YACxD,CAAC;QACL,CAAC;QAED,MAAM,CAAE,MAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitrostack",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.75",
|
|
4
4
|
"description": "NitroStack - Build powerful MCP servers with TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/core/index.js",
|
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
"zod-to-json-schema": "^3.24.6"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
91
|
+
"@testing-library/react": "^16.3.1",
|
|
90
92
|
"@types/bcryptjs": "^2.4.6",
|
|
91
93
|
"@types/better-sqlite3": "^7.6.12",
|
|
92
94
|
"@types/cors": "^2.8.19",
|
|
@@ -100,6 +102,8 @@
|
|
|
100
102
|
"@types/uuid": "^10.0.0",
|
|
101
103
|
"@types/ws": "^8.18.1",
|
|
102
104
|
"jest": "^29.7.0",
|
|
105
|
+
"react": "^19.2.3",
|
|
106
|
+
"react-dom": "^19.2.3",
|
|
103
107
|
"ts-jest": "^29.2.5",
|
|
104
108
|
"typescript": "^5.7.2"
|
|
105
109
|
},
|
|
@@ -115,4 +119,4 @@
|
|
|
115
119
|
"url": "https://github.com/abhishekpanditofficial/nitrostack/issues"
|
|
116
120
|
},
|
|
117
121
|
"homepage": "https://nitrostack.vercel.app"
|
|
118
|
-
}
|
|
122
|
+
}
|
|
@@ -2181,6 +2181,27 @@ function ChatMessageComponent({ message, tools }: { message: ChatMessage; tools:
|
|
|
2181
2181
|
if (message.role === 'tool') return null;
|
|
2182
2182
|
const isUser = message.role === 'user';
|
|
2183
2183
|
|
|
2184
|
+
// Check if this message has any visible content to show
|
|
2185
|
+
const hasContent = !!message.content;
|
|
2186
|
+
const hasFile = !!message.file;
|
|
2187
|
+
const hasToolCalls = message.toolCalls && message.toolCalls.length > 0;
|
|
2188
|
+
|
|
2189
|
+
// Check if any tool call has a widget to display
|
|
2190
|
+
const hasVisibleWidgets = hasToolCalls && message.toolCalls?.some((tc: ToolCall) => {
|
|
2191
|
+
const tool = tools.find((t) => t.name === tc.name);
|
|
2192
|
+
const componentUri = tool?.widget?.route ||
|
|
2193
|
+
tool?.outputTemplate ||
|
|
2194
|
+
tool?._meta?.['openai/outputTemplate'] ||
|
|
2195
|
+
tool?._meta?.['ui/template'];
|
|
2196
|
+
return !!componentUri && !!(tc.result || tc.arguments);
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// For assistant messages with tool calls but no widgets and no content, skip rendering
|
|
2200
|
+
// This prevents the empty bubble from showing
|
|
2201
|
+
if (!isUser && !hasContent && !hasFile && hasToolCalls && !hasVisibleWidgets) {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2184
2205
|
return (
|
|
2185
2206
|
<div className="flex gap-4 items-start animate-fade-in group">
|
|
2186
2207
|
{!isUser && (
|
|
@@ -466,10 +466,7 @@ User: "list all resources"
|
|
|
466
466
|
} else {
|
|
467
467
|
// Regular user or assistant message
|
|
468
468
|
const parts: GeminiFunctionPart[] = [];
|
|
469
|
-
|
|
470
|
-
if (msg.content) {
|
|
471
|
-
parts.push({ text: msg.content });
|
|
472
|
-
}
|
|
469
|
+
let hasImageData = false;
|
|
473
470
|
|
|
474
471
|
if (msg.role === 'user' && msg.file) {
|
|
475
472
|
// Extract base64 and mime type
|
|
@@ -486,6 +483,7 @@ User: "list all resources"
|
|
|
486
483
|
mimeType.startsWith('audio/');
|
|
487
484
|
|
|
488
485
|
if (isSupported) {
|
|
486
|
+
hasImageData = true;
|
|
489
487
|
parts.push({
|
|
490
488
|
inlineData: {
|
|
491
489
|
mimeType: mimeType,
|
|
@@ -516,10 +514,22 @@ User: "list all resources"
|
|
|
516
514
|
}
|
|
517
515
|
}
|
|
518
516
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
parts
|
|
522
|
-
})
|
|
517
|
+
// Add text content - IMPORTANT: Gemini REQUIRES a text prompt when processing images
|
|
518
|
+
if (msg.content) {
|
|
519
|
+
parts.push({ text: msg.content });
|
|
520
|
+
} else if (hasImageData) {
|
|
521
|
+
// If we have image data but no text, add a default prompt
|
|
522
|
+
// Gemini requires at least some text instruction with images
|
|
523
|
+
parts.push({ text: `Please analyze this image (${msg.file?.name || 'uploaded file'}) and process it with the appropriate tool if needed.` });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Only add content if we have parts
|
|
527
|
+
if (parts.length > 0) {
|
|
528
|
+
contents.push({
|
|
529
|
+
role: msg.role === 'assistant' ? 'model' : 'user',
|
|
530
|
+
parts,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
523
533
|
i++;
|
|
524
534
|
}
|
|
525
535
|
}
|
|
@@ -582,8 +592,36 @@ User: "list all resources"
|
|
|
582
592
|
// console.log('Sending tools to Gemini:', JSON.stringify(requestBody.tools, null, 2));
|
|
583
593
|
}
|
|
584
594
|
|
|
595
|
+
// Add generation config to ensure we get a response
|
|
596
|
+
requestBody.generationConfig = {
|
|
597
|
+
temperature: 0.7,
|
|
598
|
+
topP: 0.95,
|
|
599
|
+
topK: 40,
|
|
600
|
+
maxOutputTokens: 8192,
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// Add safety settings to be more permissive (for image processing)
|
|
604
|
+
requestBody.safetySettings = [
|
|
605
|
+
{ category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_ONLY_HIGH' },
|
|
606
|
+
{ category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_ONLY_HIGH' },
|
|
607
|
+
{ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_ONLY_HIGH' },
|
|
608
|
+
{ category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_ONLY_HIGH' },
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
// Log request for debugging (truncate large data)
|
|
612
|
+
const debugRequest = JSON.parse(JSON.stringify(requestBody));
|
|
613
|
+
if (debugRequest.contents) {
|
|
614
|
+
debugRequest.contents = debugRequest.contents.map((c: { parts?: Array<{ inlineData?: { data?: string } }> }) => ({
|
|
615
|
+
...c,
|
|
616
|
+
parts: c.parts?.map((p: { inlineData?: { data?: string } }) =>
|
|
617
|
+
p.inlineData ? { ...p, inlineData: { ...p.inlineData, data: '[BASE64_TRUNCATED]' } } : p
|
|
618
|
+
)
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
621
|
+
console.log('Gemini request body (truncated):', JSON.stringify(debugRequest, null, 2));
|
|
622
|
+
|
|
585
623
|
// Use Gemini 2.0 Flash Experimental (latest model with function calling)
|
|
586
|
-
//
|
|
624
|
+
// Fallback to gemini-1.5-flash if issues persist
|
|
587
625
|
const model = 'gemini-2.0-flash-exp';
|
|
588
626
|
const response = await fetch(
|
|
589
627
|
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,
|
|
@@ -605,7 +643,36 @@ User: "list all resources"
|
|
|
605
643
|
|
|
606
644
|
const candidate = data.candidates?.[0];
|
|
607
645
|
if (!candidate) {
|
|
608
|
-
|
|
646
|
+
// Check for prompt feedback (content filtering)
|
|
647
|
+
if (data.promptFeedback) {
|
|
648
|
+
const feedback = data.promptFeedback;
|
|
649
|
+
if (feedback.blockReason) {
|
|
650
|
+
throw new Error(`Gemini blocked the request: ${feedback.blockReason}. ${feedback.blockReasonMessage || ''}`);
|
|
651
|
+
}
|
|
652
|
+
if (feedback.safetyRatings) {
|
|
653
|
+
const blockedRatings = feedback.safetyRatings.filter(
|
|
654
|
+
(r: { probability: string }) => r.probability === 'HIGH' || r.probability === 'MEDIUM'
|
|
655
|
+
);
|
|
656
|
+
if (blockedRatings.length > 0) {
|
|
657
|
+
throw new Error(`Gemini safety filter triggered: ${JSON.stringify(blockedRatings)}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Check if there's a candidates array but it's empty with finish reason
|
|
663
|
+
if (data.candidates && data.candidates.length === 0) {
|
|
664
|
+
throw new Error('Gemini returned empty candidates - content may have been filtered');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Generic error with full response for debugging
|
|
668
|
+
console.error('Full Gemini response without candidates:', JSON.stringify(data, null, 2));
|
|
669
|
+
throw new Error(`No response from Gemini. Response metadata: ${JSON.stringify(data.usageMetadata || {})}`);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Check if candidate was blocked
|
|
673
|
+
if (candidate.finishReason === 'SAFETY' || candidate.finishReason === 'BLOCKED') {
|
|
674
|
+
const safetyRatings = candidate.safetyRatings || [];
|
|
675
|
+
throw new Error(`Gemini blocked response due to safety: ${JSON.stringify(safetyRatings)}`);
|
|
609
676
|
}
|
|
610
677
|
|
|
611
678
|
const parts = candidate.content?.parts || [];
|