@gram-ai/elements 1.26.1 → 1.27.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 (90) hide show
  1. package/README.md +83 -15
  2. package/dist/components/Chat/stories/ConnectionConfiguration.stories.d.ts +1 -1
  3. package/dist/components/ui/calendar.d.ts +25 -0
  4. package/dist/components/ui/time-range-picker.d.ts +46 -0
  5. package/dist/components/ui/time-range-picker.stories.d.ts +37 -0
  6. package/dist/core-Cqad6-xW.js +36 -0
  7. package/dist/core-Cqad6-xW.js.map +1 -0
  8. package/dist/core-DBxmxwCi.cjs +2 -0
  9. package/dist/core-DBxmxwCi.cjs.map +1 -0
  10. package/dist/elements.cjs +1 -1
  11. package/dist/elements.css +1 -1
  12. package/dist/elements.js +18 -14
  13. package/dist/hooks/useModel.d.ts +2 -0
  14. package/dist/index-CP-wWZCV.cjs +172 -0
  15. package/dist/index-CP-wWZCV.cjs.map +1 -0
  16. package/dist/{index-DfqYP0CD.js → index-oO5BAmPI.js} +12578 -11964
  17. package/dist/index-oO5BAmPI.js.map +1 -0
  18. package/dist/index.d.ts +5 -1
  19. package/dist/lib/auth.d.ts +12 -4
  20. package/dist/lib/models.d.ts +1 -1
  21. package/dist/{profiler-ZLr2-8s7.cjs → profiler-CEpc7O5Q.cjs} +2 -2
  22. package/dist/{profiler-ZLr2-8s7.cjs.map → profiler-CEpc7O5Q.cjs.map} +1 -1
  23. package/dist/{profiler-WoFj2UH8.js → profiler-ECh1zoXF.js} +2 -2
  24. package/dist/{profiler-WoFj2UH8.js.map → profiler-ECh1zoXF.js.map} +1 -1
  25. package/dist/server/bun.cjs +2 -0
  26. package/dist/server/bun.cjs.map +1 -0
  27. package/dist/server/bun.d.ts +8 -0
  28. package/dist/server/bun.js +26 -0
  29. package/dist/server/bun.js.map +1 -0
  30. package/dist/server/core.d.ts +37 -0
  31. package/dist/server/express.cjs +2 -0
  32. package/dist/server/express.cjs.map +1 -0
  33. package/dist/server/express.d.ts +9 -0
  34. package/dist/server/express.js +21 -0
  35. package/dist/server/express.js.map +1 -0
  36. package/dist/server/fastify.cjs +2 -0
  37. package/dist/server/fastify.cjs.map +1 -0
  38. package/dist/server/fastify.d.ts +9 -0
  39. package/dist/server/fastify.js +19 -0
  40. package/dist/server/fastify.js.map +1 -0
  41. package/dist/server/hono.cjs +2 -0
  42. package/dist/server/hono.cjs.map +1 -0
  43. package/dist/server/hono.d.ts +9 -0
  44. package/dist/server/hono.js +20 -0
  45. package/dist/server/hono.js.map +1 -0
  46. package/dist/server/nextjs.cjs +2 -0
  47. package/dist/server/nextjs.cjs.map +1 -0
  48. package/dist/server/nextjs.d.ts +8 -0
  49. package/dist/server/nextjs.js +26 -0
  50. package/dist/server/nextjs.js.map +1 -0
  51. package/dist/server/tanstack-start.cjs +2 -0
  52. package/dist/server/tanstack-start.cjs.map +1 -0
  53. package/dist/server/tanstack-start.d.ts +25 -0
  54. package/dist/server/tanstack-start.js +39 -0
  55. package/dist/server/tanstack-start.js.map +1 -0
  56. package/dist/server.cjs +1 -1
  57. package/dist/server.cjs.map +1 -1
  58. package/dist/server.d.ts +10 -16
  59. package/dist/server.js +22 -29
  60. package/dist/server.js.map +1 -1
  61. package/dist/{startRecording-DzQo16WK.js → startRecording-CmZjjJoz.js} +2 -2
  62. package/dist/{startRecording-DzQo16WK.js.map → startRecording-CmZjjJoz.js.map} +1 -1
  63. package/dist/{startRecording-BGnWDInp.cjs → startRecording-qDCAu4Q0.cjs} +2 -2
  64. package/dist/{startRecording-BGnWDInp.cjs.map → startRecording-qDCAu4Q0.cjs.map} +1 -1
  65. package/dist/types/index.d.ts +22 -10
  66. package/package.json +63 -3
  67. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -8
  68. package/src/components/assistant-ui/thread.tsx +8 -14
  69. package/src/components/ui/calendar.tsx +262 -0
  70. package/src/components/ui/time-range-picker.stories.tsx +249 -0
  71. package/src/components/ui/time-range-picker.tsx +675 -0
  72. package/src/hooks/useAuth.ts +59 -7
  73. package/src/hooks/useFollowOnSuggestions.ts +7 -14
  74. package/src/hooks/useModel.ts +30 -0
  75. package/src/index.ts +17 -0
  76. package/src/lib/api.test.ts +4 -4
  77. package/src/lib/auth.ts +34 -4
  78. package/src/lib/models.ts +1 -0
  79. package/src/server/bun.ts +63 -0
  80. package/src/server/core.ts +84 -0
  81. package/src/server/express.ts +60 -0
  82. package/src/server/fastify.ts +61 -0
  83. package/src/server/hono.ts +55 -0
  84. package/src/server/nextjs.ts +58 -0
  85. package/src/server/tanstack-start.ts +110 -0
  86. package/src/server.ts +37 -49
  87. package/src/types/index.ts +25 -9
  88. package/dist/index-DdrZQXwQ.cjs +0 -147
  89. package/dist/index-DdrZQXwQ.cjs.map +0 -1
  90. package/dist/index-DfqYP0CD.js.map +0 -1
@@ -1,9 +1,17 @@
1
1
  import { useReplayContext } from '@/contexts/ReplayContext'
2
- import { hasExplicitSessionAuth, isStaticSessionAuth } from '@/lib/auth'
2
+ import {
3
+ hasExplicitSessionAuth,
4
+ isDangerousApiKeyAuth,
5
+ isStaticSessionAuth,
6
+ isUnifiedFunctionSession,
7
+ isUnifiedStaticSession,
8
+ } from '@/lib/auth'
3
9
  import { useMemo } from 'react'
4
- import { ApiConfig } from '../types'
10
+ import { ApiConfig, GetSessionFn } from '../types'
5
11
  import { useSession } from './useSession'
6
12
 
13
+ declare const __GRAM_API_URL__: string | undefined
14
+
7
15
  export type Auth =
8
16
  | {
9
17
  headers: Record<string, string>
@@ -30,6 +38,28 @@ async function defaultGetSession(init: {
30
38
  return data.client_token
31
39
  }
32
40
 
41
+ function createDangerousApiKeySessionFn(
42
+ apiKey: string,
43
+ apiUrl: string
44
+ ): GetSessionFn {
45
+ return async ({ projectSlug }) => {
46
+ const response = await fetch(`${apiUrl}/rpc/chatSessions.create`, {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ 'Gram-Key': apiKey,
51
+ 'Gram-Project': projectSlug,
52
+ },
53
+ body: JSON.stringify({
54
+ embed_origin: window.location.origin,
55
+ user_identifier: 'elements-dev',
56
+ }),
57
+ })
58
+ const data = await response.json()
59
+ return data.client_token
60
+ }
61
+ }
62
+
33
63
  /**
34
64
  * Hook to fetch or retrieve the session token for the chat.
35
65
  * @returns The session token string or null
@@ -44,20 +74,42 @@ export const useAuth = ({
44
74
  const replayCtx = useReplayContext()
45
75
  const isReplay = replayCtx?.isReplay ?? false
46
76
 
77
+ const apiUrl = useMemo(() => {
78
+ const envUrl =
79
+ typeof __GRAM_API_URL__ !== 'undefined' ? __GRAM_API_URL__ : undefined
80
+ const url = auth?.url || envUrl || 'https://app.getgram.ai'
81
+ return url.replace(/\/+$/, '')
82
+ }, [auth?.url])
83
+
47
84
  const getSession = useMemo(() => {
48
85
  // In replay mode, skip session fetching entirely
49
86
  if (isReplay) {
50
87
  return null
51
88
  }
89
+ // dangerousApiKey — exchange key for session via API
90
+ if (isDangerousApiKeyAuth(auth)) {
91
+ return createDangerousApiKeySessionFn(auth.dangerousApiKey, apiUrl)
92
+ }
93
+ // Unified session: static string
94
+ if (isUnifiedStaticSession(auth)) {
95
+ return () => Promise.resolve(auth.session)
96
+ }
97
+ // Unified session: function
98
+ if (isUnifiedFunctionSession(auth)) {
99
+ return auth.session
100
+ }
101
+ // Legacy: static sessionToken (deprecated)
52
102
  if (isStaticSessionAuth(auth)) {
53
103
  return () => Promise.resolve(auth.sessionToken)
54
104
  }
55
- return !isStaticSessionAuth(auth) && hasExplicitSessionAuth(auth)
56
- ? auth.sessionFn
57
- : defaultGetSession
58
- }, [auth, isReplay])
105
+ // Legacy: explicit sessionFn (deprecated)
106
+ if (hasExplicitSessionAuth(auth)) {
107
+ return auth.sessionFn
108
+ }
109
+ return defaultGetSession
110
+ }, [auth, isReplay, apiUrl])
59
111
 
60
- // The session request is only neccessary if we are not using static session auth
112
+ // The session request is only necessary if we are not using static session auth
61
113
  // configuration. If a custom session fetcher is provided, we use it,
62
114
  // otherwise we fallback to the default session fetcher
63
115
  const session = useSession({
@@ -1,12 +1,12 @@
1
1
  import { useReplayContext } from '@/contexts/ReplayContext'
2
- import { useAssistantState } from '@assistant-ui/react'
3
- import { useCallback, useEffect, useRef, useState } from 'react'
4
- import { useElements } from './useElements'
5
2
  import { getApiUrl } from '@/lib/api'
6
- import { useAuth } from './useAuth'
7
- import { createOpenRouter } from '@openrouter/ai-sdk-provider'
3
+ import { useAssistantState } from '@assistant-ui/react'
8
4
  import { generateObject } from 'ai'
5
+ import { useCallback, useEffect, useRef, useState } from 'react'
9
6
  import { z } from 'zod'
7
+ import { useAuth } from './useAuth'
8
+ import { useElements } from './useElements'
9
+ import { useModel } from './useModel'
10
10
 
11
11
  export interface FollowOnSuggestion {
12
12
  id: string
@@ -46,6 +46,8 @@ export function useFollowOnSuggestions(): {
46
46
  projectSlug: config.projectSlug,
47
47
  })
48
48
 
49
+ const model = useModel(SUGGESTIONS_MODEL)
50
+
49
51
  // Check if follow-up suggestions are enabled (default: true)
50
52
  // Disable in replay mode since we don't need AI-generated suggestions
51
53
  const isEnabled = !isReplay && config.thread?.followUpSuggestions !== false
@@ -104,15 +106,6 @@ export function useFollowOnSuggestions(): {
104
106
  setIsLoading(true)
105
107
 
106
108
  try {
107
- // Create OpenRouter client
108
- const openRouter = createOpenRouter({
109
- baseURL: apiUrl,
110
- apiKey: 'unused, but must be set',
111
- headers: auth.headers,
112
- })
113
-
114
- const model = openRouter.chat(SUGGESTIONS_MODEL)
115
-
116
109
  // Check if the assistant is asking a question
117
110
  if (lastAssistantMessage) {
118
111
  try {
@@ -0,0 +1,30 @@
1
+ import { getApiUrl } from '@/lib/api'
2
+ import { createOpenRouter } from '@openrouter/ai-sdk-provider'
3
+ import { LanguageModel } from 'ai'
4
+ import { useAuth } from './useAuth'
5
+ import { useElements } from './useElements'
6
+
7
+ // Creates an OpenRouter client to be used for "internal Gram" usage, such as follow-on suggestions
8
+ export const useModel = (
9
+ model: string = 'openai/gpt-4o-mini'
10
+ ): LanguageModel => {
11
+ const { config } = useElements()
12
+
13
+ const auth = useAuth({
14
+ auth: config.api,
15
+ projectSlug: config.projectSlug,
16
+ })
17
+
18
+ const apiUrl = getApiUrl(config)
19
+
20
+ const openRouter = createOpenRouter({
21
+ baseURL: apiUrl,
22
+ apiKey: 'unused, but must be set',
23
+ headers: {
24
+ ...auth.headers,
25
+ 'X-Gram-Source': 'gram',
26
+ },
27
+ })
28
+
29
+ return openRouter.chat(model)
30
+ }
package/src/index.ts CHANGED
@@ -43,6 +43,7 @@ export type {
43
43
  ColorScheme,
44
44
  ComponentOverrides,
45
45
  ComposerConfig,
46
+ DangerousApiKeyAuthConfig,
46
47
  DENSITIES,
47
48
  Density,
48
49
  Dimension,
@@ -62,6 +63,7 @@ export type {
62
63
  ThemeConfig,
63
64
  ToolMentionsConfig,
64
65
  ToolsConfig,
66
+ UnifiedSessionAuthConfig,
65
67
  Variant,
66
68
  VARIANTS,
67
69
  WelcomeConfig,
@@ -70,3 +72,18 @@ export type {
70
72
  export { MODELS } from './lib/models'
71
73
 
72
74
  export type { Plugin } from './types/plugins'
75
+
76
+ // Time Range Picker
77
+ export {
78
+ TimeRangePicker,
79
+ getPresetRange,
80
+ PRESETS,
81
+ } from '@/components/ui/time-range-picker'
82
+ export type {
83
+ TimeRange,
84
+ TimeRangePreset,
85
+ TimeRangePickerProps,
86
+ DateRangePreset,
87
+ } from '@/components/ui/time-range-picker'
88
+ export { Calendar } from '@/components/ui/calendar'
89
+ export type { CalendarProps } from '@/components/ui/calendar'
@@ -16,7 +16,7 @@ describe('getApiUrl', () => {
16
16
  const getApiUrl = await loadGetApiUrl('https://env.example.com')
17
17
  const config: ElementsConfig = {
18
18
  projectSlug: 'test',
19
- api: { url: 'https://config.example.com', sessionToken: 'test-key' },
19
+ api: { url: 'https://config.example.com', session: 'test-key' },
20
20
  }
21
21
 
22
22
  expect(getApiUrl(config)).toBe('https://config.example.com')
@@ -26,7 +26,7 @@ describe('getApiUrl', () => {
26
26
  const getApiUrl = await loadGetApiUrl('https://env.example.com')
27
27
  const config: ElementsConfig = {
28
28
  projectSlug: 'test',
29
- api: { sessionToken: 'test-key' },
29
+ api: { session: 'test-key' },
30
30
  }
31
31
 
32
32
  expect(getApiUrl(config)).toBe('https://env.example.com')
@@ -63,7 +63,7 @@ describe('getApiUrl', () => {
63
63
  const getApiUrl = await loadGetApiUrl('https://env.example.com')
64
64
  const config: ElementsConfig = {
65
65
  projectSlug: 'test',
66
- api: { url: '', sessionToken: 'test-key' },
66
+ api: { url: '', session: 'test-key' },
67
67
  }
68
68
 
69
69
  expect(getApiUrl(config)).toBe('https://env.example.com')
@@ -73,7 +73,7 @@ describe('getApiUrl', () => {
73
73
  const getApiUrl = await loadGetApiUrl('')
74
74
  const config: ElementsConfig = {
75
75
  projectSlug: 'test',
76
- api: { url: 'https://config.example.com///', sessionToken: 'test-key' },
76
+ api: { url: 'https://config.example.com///', session: 'test-key' },
77
77
  }
78
78
 
79
79
  expect(getApiUrl(config)).toBe('https://config.example.com')
package/src/lib/auth.ts CHANGED
@@ -1,17 +1,47 @@
1
- import { ApiConfig, SessionAuthConfig, StaticSessionAuthConfig } from '@/types'
1
+ import {
2
+ ApiConfig,
3
+ BaseApiConfig,
4
+ DangerousApiKeyAuthConfig,
5
+ GetSessionFn,
6
+ SessionAuthConfig,
7
+ StaticSessionAuthConfig,
8
+ UnifiedSessionAuthConfig,
9
+ } from '@/types'
2
10
 
3
- /**
4
- * Checks if the auth config is an API key auth config
5
- */
11
+ export function isDangerousApiKeyAuth(
12
+ auth: ApiConfig | undefined
13
+ ): auth is DangerousApiKeyAuthConfig {
14
+ return !!auth && 'dangerousApiKey' in auth
15
+ }
16
+
17
+ export function isUnifiedStaticSession(
18
+ auth: ApiConfig | undefined
19
+ ): auth is UnifiedSessionAuthConfig & { session: string } {
20
+ return !!auth && 'session' in auth && typeof auth.session === 'string'
21
+ }
22
+
23
+ export function isUnifiedFunctionSession(
24
+ auth: ApiConfig | undefined
25
+ ): auth is BaseApiConfig & { session: GetSessionFn } {
26
+ return !!auth && 'session' in auth && typeof auth.session === 'function'
27
+ }
28
+
29
+ /** @deprecated Legacy check for `{ sessionToken }` configs. */
6
30
  export function isStaticSessionAuth(
7
31
  auth: ApiConfig | undefined
8
32
  ): auth is StaticSessionAuthConfig {
9
33
  return !!auth && 'sessionToken' in auth
10
34
  }
11
35
 
36
+ /** @deprecated Legacy check for `{ sessionFn }` configs. */
12
37
  export function hasExplicitSessionAuth(
13
38
  auth: ApiConfig | undefined
14
39
  ): auth is SessionAuthConfig {
15
40
  if (!auth) return false
16
41
  return 'sessionFn' in auth
17
42
  }
43
+
44
+ /** Returns true when either the legacy `sessionToken` or the unified static `session` string is used. */
45
+ export function isAnyStaticSession(auth: ApiConfig | undefined): boolean {
46
+ return isStaticSessionAuth(auth) || isUnifiedStaticSession(auth)
47
+ }
package/src/lib/models.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // List of openrouter models available to the user
2
2
  // This list should be updated to match the model whitelist on the backend side.
3
3
  export const MODELS = [
4
+ 'anthropic/claude-opus-4.6',
4
5
  'anthropic/claude-sonnet-4.5',
5
6
  'anthropic/claude-haiku-4.5',
6
7
  'anthropic/claude-sonnet-4',
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Bun adapter for Gram Elements server handlers.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { createBunHandler } from '@gram-ai/elements/server/bun'
7
+ *
8
+ * const handler = createBunHandler({
9
+ * embedOrigin: 'http://localhost:3000',
10
+ * userIdentifier: 'user-123',
11
+ * expiresAfter: 3600,
12
+ * })
13
+ *
14
+ * Bun.serve({
15
+ * routes: {
16
+ * '/chat/session': { POST: handler },
17
+ * },
18
+ * })
19
+ * ```
20
+ */
21
+
22
+ import { createChatSession, type SessionHandlerOptions } from './core'
23
+
24
+ /**
25
+ * Create a Bun route handler for the chat session endpoint.
26
+ *
27
+ * @param options - Session configuration options
28
+ * @returns Bun route handler
29
+ */
30
+ export function createBunHandler(
31
+ options:
32
+ | SessionHandlerOptions
33
+ | ((
34
+ request: Request
35
+ ) => SessionHandlerOptions | Promise<SessionHandlerOptions>)
36
+ ) {
37
+ return async (request: Request) => {
38
+ const projectSlug = request.headers.get('gram-project')
39
+
40
+ if (!projectSlug) {
41
+ return new Response(
42
+ JSON.stringify({ error: 'Missing Gram-Project header' }),
43
+ {
44
+ status: 400,
45
+ headers: { 'Content-Type': 'application/json' },
46
+ }
47
+ )
48
+ }
49
+
50
+ const sessionOptions =
51
+ typeof options === 'function' ? await options(request) : options
52
+
53
+ const result = await createChatSession({
54
+ projectSlug,
55
+ options: sessionOptions,
56
+ })
57
+
58
+ return new Response(result.body, {
59
+ status: result.status,
60
+ headers: result.headers,
61
+ })
62
+ }
63
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Core session handler logic shared across all server adapters.
3
+ * This module contains the framework-agnostic business logic for creating chat sessions.
4
+ */
5
+
6
+ export interface SessionHandlerOptions {
7
+ /**
8
+ * The origin from which the token will be used
9
+ */
10
+ embedOrigin: string
11
+
12
+ /**
13
+ * Free-form user identifier
14
+ */
15
+ userIdentifier: string
16
+
17
+ /**
18
+ * Token expiration in seconds (max / default 3600)
19
+ * @default 3600
20
+ */
21
+ expiresAfter?: number
22
+
23
+ /**
24
+ * Gram API key. If not provided, falls back to the `GRAM_API_KEY` environment variable.
25
+ */
26
+ apiKey?: string
27
+ }
28
+
29
+ export interface CreateSessionRequest {
30
+ projectSlug: string
31
+ options: SessionHandlerOptions
32
+ }
33
+
34
+ export interface CreateSessionResponse {
35
+ status: number
36
+ body: string
37
+ headers: Record<string, string>
38
+ }
39
+
40
+ /**
41
+ * Core function to create a chat session by calling Gram's API.
42
+ * This is framework-agnostic and can be used by any adapter.
43
+ */
44
+ export async function createChatSession(
45
+ request: CreateSessionRequest
46
+ ): Promise<CreateSessionResponse> {
47
+ const base = process.env.GRAM_API_URL ?? 'https://app.getgram.ai'
48
+
49
+ try {
50
+ const response = await fetch(base + '/rpc/chatSessions.create', {
51
+ method: 'POST',
52
+ body: JSON.stringify({
53
+ embed_origin: request.options.embedOrigin,
54
+ user_identifier: request.options.userIdentifier,
55
+ expires_after: request.options.expiresAfter,
56
+ }),
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ 'Gram-Project': request.projectSlug,
60
+ 'Gram-Key': request.options.apiKey ?? process.env.GRAM_API_KEY ?? '',
61
+ },
62
+ })
63
+
64
+ const body = await response.text()
65
+
66
+ return {
67
+ status: response.status,
68
+ body,
69
+ headers: { 'Content-Type': 'application/json' },
70
+ }
71
+ } catch (error) {
72
+ const errorMessage =
73
+ error instanceof Error ? error.message : 'Unknown error'
74
+ console.error('Failed to create chat session:', error)
75
+
76
+ return {
77
+ status: 500,
78
+ body: JSON.stringify({
79
+ error: 'Failed to create chat session: ' + errorMessage,
80
+ }),
81
+ headers: { 'Content-Type': 'application/json' },
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Express adapter for Gram Elements server handlers.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { createExpressHandler } from '@gram-ai/elements/server/express'
7
+ * import express from 'express'
8
+ *
9
+ * const app = express()
10
+ * app.use(express.json())
11
+ *
12
+ * app.post('/chat/session', createExpressHandler({
13
+ * embedOrigin: 'http://localhost:3000',
14
+ * userIdentifier: 'user-123',
15
+ * expiresAfter: 3600,
16
+ * }))
17
+ *
18
+ * app.listen(3000)
19
+ * ```
20
+ */
21
+
22
+ import type { Request, Response } from 'express'
23
+ import { createChatSession, type SessionHandlerOptions } from './core'
24
+
25
+ /**
26
+ * Create an Express request handler for the chat session endpoint.
27
+ *
28
+ * @param options - Session configuration options
29
+ * @returns Express request handler
30
+ */
31
+ export function createExpressHandler(
32
+ options:
33
+ | SessionHandlerOptions
34
+ | ((req: Request) => SessionHandlerOptions | Promise<SessionHandlerOptions>)
35
+ ) {
36
+ return async (req: Request, res: Response) => {
37
+ const projectSlug = Array.isArray(req.headers['gram-project'])
38
+ ? req.headers['gram-project'][0]
39
+ : req.headers['gram-project']
40
+
41
+ if (!projectSlug) {
42
+ res.status(400).json({ error: 'Missing Gram-Project header' })
43
+ return
44
+ }
45
+
46
+ const sessionOptions =
47
+ typeof options === 'function' ? await options(req) : options
48
+
49
+ const result = await createChatSession({
50
+ projectSlug,
51
+ options: sessionOptions,
52
+ })
53
+
54
+ res.status(result.status)
55
+ Object.entries(result.headers).forEach(([key, value]) => {
56
+ res.setHeader(key, value)
57
+ })
58
+ res.send(result.body)
59
+ }
60
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Fastify adapter for Gram Elements server handlers.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { createFastifyHandler } from '@gram-ai/elements/server/fastify'
7
+ * import Fastify from 'fastify'
8
+ *
9
+ * const fastify = Fastify()
10
+ *
11
+ * fastify.post('/chat/session', createFastifyHandler({
12
+ * embedOrigin: 'http://localhost:3000',
13
+ * userIdentifier: 'user-123',
14
+ * expiresAfter: 3600,
15
+ * }))
16
+ *
17
+ * fastify.listen({ port: 3000 })
18
+ * ```
19
+ */
20
+
21
+ import type { FastifyRequest, FastifyReply } from 'fastify'
22
+ import { createChatSession, type SessionHandlerOptions } from './core'
23
+
24
+ /**
25
+ * Create a Fastify route handler for the chat session endpoint.
26
+ *
27
+ * @param options - Session configuration options
28
+ * @returns Fastify route handler
29
+ */
30
+ export function createFastifyHandler(
31
+ options:
32
+ | SessionHandlerOptions
33
+ | ((
34
+ request: FastifyRequest
35
+ ) => SessionHandlerOptions | Promise<SessionHandlerOptions>)
36
+ ) {
37
+ return async (request: FastifyRequest, reply: FastifyReply) => {
38
+ const projectSlug = Array.isArray(request.headers['gram-project'])
39
+ ? request.headers['gram-project'][0]
40
+ : request.headers['gram-project']
41
+
42
+ if (!projectSlug) {
43
+ reply.code(400).send({ error: 'Missing Gram-Project header' })
44
+ return
45
+ }
46
+
47
+ const sessionOptions =
48
+ typeof options === 'function' ? await options(request) : options
49
+
50
+ const result = await createChatSession({
51
+ projectSlug,
52
+ options: sessionOptions,
53
+ })
54
+
55
+ reply
56
+ .code(result.status)
57
+ .headers(result.headers)
58
+ .type('application/json')
59
+ .send(result.body)
60
+ }
61
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Hono adapter for Gram Elements server handlers.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { createHonoHandler } from '@gram-ai/elements/server/hono'
7
+ * import { Hono } from 'hono'
8
+ *
9
+ * const app = new Hono()
10
+ *
11
+ * app.post('/chat/session', createHonoHandler({
12
+ * embedOrigin: 'http://localhost:3000',
13
+ * userIdentifier: 'user-123',
14
+ * expiresAfter: 3600,
15
+ * }))
16
+ *
17
+ * export default app
18
+ * ```
19
+ */
20
+
21
+ import type { Context } from 'hono'
22
+ import { createChatSession, type SessionHandlerOptions } from './core'
23
+
24
+ /**
25
+ * Create a Hono route handler for the chat session endpoint.
26
+ *
27
+ * @param options - Session configuration options
28
+ * @returns Hono route handler
29
+ */
30
+ export function createHonoHandler(
31
+ options:
32
+ | SessionHandlerOptions
33
+ | ((c: Context) => SessionHandlerOptions | Promise<SessionHandlerOptions>)
34
+ ) {
35
+ return async (c: Context) => {
36
+ const projectSlug = c.req.header('gram-project')
37
+
38
+ if (!projectSlug) {
39
+ return c.json({ error: 'Missing Gram-Project header' }, 400)
40
+ }
41
+
42
+ const sessionOptions =
43
+ typeof options === 'function' ? await options(c) : options
44
+
45
+ const result = await createChatSession({
46
+ projectSlug,
47
+ options: sessionOptions,
48
+ })
49
+
50
+ return new Response(result.body, {
51
+ status: result.status,
52
+ headers: result.headers,
53
+ })
54
+ }
55
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Next.js App Router adapter for Gram Elements server handlers.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * // app/api/chat/session/route.ts
7
+ * import { createNextHandler } from '@gram-ai/elements/server/nextjs'
8
+ *
9
+ * export const POST = createNextHandler({
10
+ * embedOrigin: 'http://localhost:3000',
11
+ * userIdentifier: 'user-123',
12
+ * expiresAfter: 3600,
13
+ * })
14
+ * ```
15
+ */
16
+
17
+ import { createChatSession, type SessionHandlerOptions } from './core'
18
+
19
+ /**
20
+ * Create a Next.js App Router route handler for the chat session endpoint.
21
+ *
22
+ * @param options - Session configuration options
23
+ * @returns Next.js route handler
24
+ */
25
+ export function createNextHandler(
26
+ options:
27
+ | SessionHandlerOptions
28
+ | ((
29
+ request: Request
30
+ ) => SessionHandlerOptions | Promise<SessionHandlerOptions>)
31
+ ) {
32
+ return async (request: Request) => {
33
+ const projectSlug = request.headers.get('gram-project')
34
+
35
+ if (!projectSlug) {
36
+ return new Response(
37
+ JSON.stringify({ error: 'Missing Gram-Project header' }),
38
+ {
39
+ status: 400,
40
+ headers: { 'Content-Type': 'application/json' },
41
+ }
42
+ )
43
+ }
44
+
45
+ const sessionOptions =
46
+ typeof options === 'function' ? await options(request) : options
47
+
48
+ const result = await createChatSession({
49
+ projectSlug,
50
+ options: sessionOptions,
51
+ })
52
+
53
+ return new Response(result.body, {
54
+ status: result.status,
55
+ headers: result.headers,
56
+ })
57
+ }
58
+ }