human-in-the-loop 0.1.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.
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "human-in-the-loop",
3
+ "version": "0.1.0",
4
+ "description": "Strongly-typed interface for human functions across multiple platforms",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "keywords": [
16
+ "human",
17
+ "in-the-loop",
18
+ "feedback",
19
+ "slack",
20
+ "teams",
21
+ "react",
22
+ "email"
23
+ ],
24
+ "author": "Drivly",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/drivly/primitives.org.ai.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/drivly/primitives.org.ai/issues"
32
+ },
33
+ "homepage": "https://primitives.org.ai",
34
+ "dependencies": {
35
+ "zod": "^3.24.3",
36
+ "ai-providers": "0.2.0"
37
+ },
38
+ "peerDependencies": {
39
+ "react": "^19.0.0",
40
+ "react-dom": "^19.0.0",
41
+ "react-email": "^1.10.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.13.10",
45
+ "@types/react": "^19.0.0",
46
+ "@types/react-dom": "^19.0.0",
47
+ "eslint": "^9.17.0",
48
+ "prettier": "^3.4.2",
49
+ "tsup": "^8.0.1",
50
+ "typescript": "^5.8.2",
51
+ "vitest": "^3.0.9"
52
+ },
53
+ "type": "module",
54
+ "engines": {
55
+ "node": ">=18"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup src/index.ts --format cjs,esm --dts",
59
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "lint": "eslint .",
63
+ "format": "prettier --write ."
64
+ }
65
+ }
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createHumanFunction } from './factory'
3
+ import { SlackHumanFunction } from '../platforms/slack'
4
+ import { TeamsHumanFunction } from '../platforms/teams'
5
+ import { ReactHumanFunction } from '../platforms/react'
6
+ import { EmailHumanFunction } from '../platforms/email'
7
+
8
+ describe('createHumanFunction', () => {
9
+ it('should create a Slack human function', () => {
10
+ const humanFunction = createHumanFunction<{}, {}>({
11
+ platform: 'slack',
12
+ title: 'Test',
13
+ description: 'Test description'
14
+ })
15
+
16
+ expect(humanFunction).toBeInstanceOf(SlackHumanFunction)
17
+ })
18
+
19
+ it('should create a Teams human function', () => {
20
+ const humanFunction = createHumanFunction<{}, {}>({
21
+ platform: 'teams',
22
+ title: 'Test',
23
+ description: 'Test description'
24
+ })
25
+
26
+ expect(humanFunction).toBeInstanceOf(TeamsHumanFunction)
27
+ })
28
+
29
+ it('should create a React human function', () => {
30
+ const humanFunction = createHumanFunction<{}, {}>({
31
+ platform: 'react',
32
+ title: 'Test',
33
+ description: 'Test description'
34
+ })
35
+
36
+ expect(humanFunction).toBeInstanceOf(ReactHumanFunction)
37
+ })
38
+
39
+ it('should create an Email human function', () => {
40
+ const humanFunction = createHumanFunction<{}, {}>({
41
+ platform: 'email',
42
+ title: 'Test',
43
+ description: 'Test description',
44
+ to: 'test@example.com'
45
+ })
46
+
47
+ expect(humanFunction).toBeInstanceOf(EmailHumanFunction)
48
+ })
49
+
50
+ it('should throw an error for unsupported platforms', () => {
51
+ expect(() => {
52
+ createHumanFunction<{}, {}>({
53
+ platform: 'invalid' as any,
54
+ title: 'Test',
55
+ description: 'Test description'
56
+ })
57
+ }).toThrow('Unsupported platform: invalid')
58
+ })
59
+
60
+ it('should throw an error for email platform without to field', () => {
61
+ expect(() => {
62
+ createHumanFunction<{}, {}>({
63
+ platform: 'email',
64
+ title: 'Test',
65
+ description: 'Test description'
66
+ })
67
+ }).toThrow('Email platform requires a "to" field in options')
68
+ })
69
+ })
@@ -0,0 +1,30 @@
1
+ import type { CreateHumanFunctionOptions, HumanFunction } from './types'
2
+ import { SlackHumanFunction } from '../platforms/slack'
3
+ import { TeamsHumanFunction } from '../platforms/teams'
4
+ import { ReactHumanFunction } from '../platforms/react'
5
+ import { EmailHumanFunction } from '../platforms/email'
6
+
7
+ /**
8
+ * Create a strongly-typed human function
9
+ */
10
+ export function createHumanFunction<TInput, TOutput>(
11
+ options: CreateHumanFunctionOptions
12
+ ): HumanFunction<TInput, TOutput> {
13
+ const { platform } = options
14
+
15
+ switch (platform) {
16
+ case 'slack':
17
+ return new SlackHumanFunction<TInput, TOutput>(options)
18
+ case 'teams':
19
+ return new TeamsHumanFunction<TInput, TOutput>(options)
20
+ case 'react':
21
+ return new ReactHumanFunction<TInput, TOutput>(options)
22
+ case 'email':
23
+ if (!options.to) {
24
+ throw new Error('Email platform requires a "to" field in options')
25
+ }
26
+ return new EmailHumanFunction<TInput, TOutput>(options as any)
27
+ default:
28
+ throw new Error(`Unsupported platform: ${platform}`)
29
+ }
30
+ }
@@ -0,0 +1,191 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Supported platforms for human-in-the-loop interactions
5
+ */
6
+ export type HumanPlatform = 'slack' | 'teams' | 'react' | 'email'
7
+
8
+ /**
9
+ * Status of a human task
10
+ */
11
+ export type HumanTaskStatus = 'pending' | 'completed' | 'timeout'
12
+
13
+ /**
14
+ * Base configuration for Human Functions
15
+ */
16
+ export interface HumanFunctionConfig {
17
+ /**
18
+ * Title of the request shown to humans
19
+ */
20
+ title: string
21
+
22
+ /**
23
+ * Description of the task for humans
24
+ */
25
+ description: string
26
+
27
+ /**
28
+ * Platform to use for human interaction
29
+ */
30
+ platform: HumanPlatform
31
+
32
+ /**
33
+ * Timeout in milliseconds after which the task is marked as timed out
34
+ */
35
+ timeout?: number
36
+
37
+ /**
38
+ * Additional platform-specific options
39
+ */
40
+ [key: string]: any
41
+ }
42
+
43
+ /**
44
+ * A request for human input
45
+ */
46
+ export interface HumanTaskRequest {
47
+ /**
48
+ * Unique identifier for the task
49
+ */
50
+ taskId: string
51
+
52
+ /**
53
+ * Current status of the task
54
+ */
55
+ status: HumanTaskStatus
56
+
57
+ /**
58
+ * Platform-specific message ID
59
+ */
60
+ messageId?: Record<HumanPlatform, string>
61
+ }
62
+
63
+ /**
64
+ * Human Function interface with strongly-typed input and output
65
+ */
66
+ export interface HumanFunction<TInput, TOutput> {
67
+ /**
68
+ * Request human input with the given input data
69
+ */
70
+ request(input: TInput): Promise<HumanTaskRequest>
71
+
72
+ /**
73
+ * Get the human response for a given task
74
+ */
75
+ getResponse(taskId: string): Promise<TOutput | null>
76
+ }
77
+
78
+ /**
79
+ * Platform-specific configurations
80
+ */
81
+ export interface PlatformConfigs {
82
+ slack?: SlackConfig
83
+ teams?: TeamsConfig
84
+ react?: ReactConfig
85
+ email?: EmailConfig
86
+ }
87
+
88
+ /**
89
+ * Slack-specific configuration
90
+ */
91
+ export interface SlackConfig {
92
+ /**
93
+ * Slack channel to send the message to
94
+ */
95
+ channel?: string
96
+
97
+ /**
98
+ * User IDs to mention in the message
99
+ */
100
+ mentions?: string[]
101
+
102
+ /**
103
+ * Whether to use a modal dialog instead of a message
104
+ */
105
+ modal?: boolean
106
+
107
+ /**
108
+ * Custom Slack blocks
109
+ */
110
+ blocks?: any[]
111
+
112
+ /**
113
+ * Webhook URL for callbacks
114
+ */
115
+ webhookUrl?: string
116
+ }
117
+
118
+ /**
119
+ * Microsoft Teams specific configuration
120
+ */
121
+ export interface TeamsConfig {
122
+ /**
123
+ * Teams webhook URL
124
+ */
125
+ webhookUrl?: string
126
+
127
+ /**
128
+ * Whether to use adaptive cards
129
+ */
130
+ useAdaptiveCards?: boolean
131
+ }
132
+
133
+ /**
134
+ * React-specific configuration
135
+ */
136
+ export interface ReactConfig {
137
+ /**
138
+ * Custom component styling
139
+ */
140
+ styles?: Record<string, any>
141
+
142
+ /**
143
+ * Theme configuration
144
+ */
145
+ theme?: 'light' | 'dark' | 'system'
146
+ }
147
+
148
+ /**
149
+ * Email-specific configuration
150
+ */
151
+ export interface EmailConfig {
152
+ /**
153
+ * Recipients of the email
154
+ */
155
+ to: string | string[]
156
+
157
+ /**
158
+ * CC recipients
159
+ */
160
+ cc?: string | string[]
161
+
162
+ /**
163
+ * BCC recipients
164
+ */
165
+ bcc?: string | string[]
166
+
167
+ /**
168
+ * From address
169
+ */
170
+ from?: string
171
+
172
+ /**
173
+ * Reply-to address
174
+ */
175
+ replyTo?: string
176
+
177
+ /**
178
+ * Callback URL for email responses
179
+ */
180
+ callbackUrl?: string
181
+ }
182
+
183
+ /**
184
+ * Options for creating human functions
185
+ */
186
+ export interface CreateHumanFunctionOptions extends HumanFunctionConfig, PlatformConfigs {
187
+ /**
188
+ * Optional validation schema for the output
189
+ */
190
+ outputSchema?: z.ZodType<any>
191
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './core/types'
2
+ export { createHumanFunction } from './core/factory'
3
+
4
+ export * from './platforms/slack'
5
+ export * from './platforms/teams'
6
+ export * from './platforms/react'
7
+ export * from './platforms/email'
@@ -0,0 +1,137 @@
1
+ import React from 'react'
2
+ import type { HumanFunction, HumanTaskRequest, EmailConfig } from '../../core/types'
3
+
4
+ /**
5
+ * Basic Email Template component
6
+ */
7
+ export function EmailTemplate({
8
+ taskId,
9
+ title,
10
+ description,
11
+ options,
12
+ callbackUrl
13
+ }: {
14
+ taskId: string
15
+ title: string
16
+ description: string
17
+ options?: string[] | Array<{ value: string; label: string }>
18
+ callbackUrl?: string
19
+ }) {
20
+ return (
21
+ <div>
22
+ <h1>{title}</h1>
23
+ <p>{description}</p>
24
+
25
+ {options && options.length > 0 && (
26
+ <div>
27
+ <p>Please select one of the following options:</p>
28
+ <ul>
29
+ {options.map((option, index) => {
30
+ const value = typeof option === 'string' ? option : option.value
31
+ const label = typeof option === 'string' ? option : option.label
32
+ const responseUrl = callbackUrl
33
+ ? `${callbackUrl}?taskId=${taskId}&option=${encodeURIComponent(value)}`
34
+ : '#'
35
+
36
+ return (
37
+ <li key={index}>
38
+ <a href={responseUrl}>{label}</a>
39
+ </li>
40
+ )
41
+ })}
42
+ </ul>
43
+ </div>
44
+ )}
45
+
46
+ <p>
47
+ Or, you can reply to this email with your response.
48
+ </p>
49
+ </div>
50
+ )
51
+ }
52
+
53
+ /**
54
+ * Mock function to send an email
55
+ */
56
+ export async function sendEmail(
57
+ config: EmailConfig & {
58
+ title: string
59
+ description: string
60
+ options?: string[] | Array<{ value: string; label: string }>
61
+ taskId: string
62
+ }
63
+ ): Promise<{ messageId: string }> {
64
+ console.log(`Sending email to ${config.to}`)
65
+ console.log(`Title: ${config.title}`)
66
+ console.log(`Description: ${config.description}`)
67
+
68
+
69
+ return { messageId: `email-${config.taskId}-${Date.now()}` }
70
+ }
71
+
72
+ /**
73
+ * Get the response for an email task
74
+ */
75
+ export async function getEmailResponse<TOutput>(taskId: string): Promise<TOutput | null> {
76
+ console.log(`Getting response for email task ${taskId}`)
77
+
78
+ return null
79
+ }
80
+
81
+ /**
82
+ * Implementation of HumanFunction for Email
83
+ */
84
+ export class EmailHumanFunction<TInput, TOutput> implements HumanFunction<TInput, TOutput> {
85
+ private config: EmailConfig & {
86
+ title: string
87
+ description: string
88
+ options?: string[] | Array<{ value: string; label: string }>
89
+ }
90
+
91
+ constructor(config: EmailConfig & {
92
+ title: string
93
+ description: string
94
+ options?: string[] | Array<{ value: string; label: string }>
95
+ }) {
96
+ this.config = config
97
+ }
98
+
99
+ async request(input: TInput): Promise<HumanTaskRequest> {
100
+ const taskId = `task-${Date.now()}`
101
+
102
+ const { messageId } = await sendEmail({
103
+ ...this.config,
104
+ taskId
105
+ })
106
+
107
+ return {
108
+ taskId,
109
+ status: 'pending',
110
+ messageId: {
111
+ slack: '',
112
+ teams: '',
113
+ react: '',
114
+ email: messageId
115
+ }
116
+ }
117
+ }
118
+
119
+ async getResponse(taskId: string): Promise<TOutput | null> {
120
+ return getEmailResponse<TOutput>(taskId)
121
+ }
122
+
123
+ /**
124
+ * Get a React component for this email template
125
+ */
126
+ getEmailComponent(taskId: string): React.ReactNode {
127
+ return (
128
+ <EmailTemplate
129
+ taskId={taskId}
130
+ title={this.config.title}
131
+ description={this.config.description}
132
+ options={this.config.options}
133
+ callbackUrl={this.config.callbackUrl}
134
+ />
135
+ )
136
+ }
137
+ }