@cobbl-ai/sdk 0.1.0 → 0.1.2

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/src/public.ts ADDED
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Cobbl SDK - Public API Client
3
+ *
4
+ * A lightweight client for public-facing feedback operations.
5
+ * Use this in client-side or user-facing contexts.
6
+ * No authentication required - designed for end-user feedback submission.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { CobblPublicClient } from '@cobbl-ai/sdk/public'
11
+ *
12
+ * const client = new CobblPublicClient({
13
+ * baseUrl: 'https://api.cobbl.ai' // Optional, defaults to production
14
+ * })
15
+ *
16
+ * // Submit feedback
17
+ * const { id } = await client.createFeedback({
18
+ * runId: 'run_abc123',
19
+ * helpful: 'not_helpful'
20
+ * })
21
+ *
22
+ * // Update feedback with details later
23
+ * await client.updateFeedback(id, {
24
+ * userFeedback: 'Too formal'
25
+ * })
26
+ * ```
27
+ */
28
+
29
+ import type {
30
+ CreateFeedbackRequest,
31
+ CreateFeedbackResponse,
32
+ UpdateFeedbackRequest,
33
+ UpdateFeedbackResponse,
34
+ } from './types'
35
+ import { CobblError, handleErrorResponse } from './errors'
36
+ import { validateCreateFeedback, validateUpdateFeedback } from './validation'
37
+
38
+ /**
39
+ * Configuration options for CobblPublicClient
40
+ */
41
+ export interface CobblPublicConfig {
42
+ /**
43
+ * Base URL for the API
44
+ * @optional
45
+ * @default 'https://api.cobbl.ai'
46
+ * @internal
47
+ * @example 'http://localhost:5001/your-project/us-central1/externalApi' // For local development
48
+ */
49
+ baseUrl?: string
50
+ }
51
+
52
+ const REQUEST_TIMEOUT_MS = 30_000
53
+
54
+ /**
55
+ * Public API Client for Cobbl
56
+ *
57
+ * Provides methods to submit and update feedback via the public API.
58
+ * This client is suitable for use in client-side or user-facing contexts.
59
+ * No authentication required - designed for end-user feedback submission.
60
+ */
61
+ export class CobblPublicClient {
62
+ private readonly baseUrl: string
63
+
64
+ /**
65
+ * Initialize the Cobbl Public SDK client
66
+ *
67
+ * @param config - Configuration object
68
+ * @param config.baseUrl - Optional base URL for the API (defaults to 'https://api.cobbl.ai')
69
+ */
70
+ constructor(config: CobblPublicConfig = {}) {
71
+ this.baseUrl = config.baseUrl?.trim() || 'https://api.cobbl.ai'
72
+ }
73
+
74
+ /**
75
+ * Create user feedback for a prompt run
76
+ * Supports segmented feedback - you can provide just helpful, just userFeedback, or both
77
+ * Use updateFeedback to add additional data later
78
+ *
79
+ * @param feedback - Feedback submission data
80
+ * @param feedback.runId - The run ID from a previous runPrompt call
81
+ * @param feedback.helpful - Whether the output was helpful ('helpful' or 'not_helpful') - optional
82
+ * @param feedback.userFeedback - Detailed feedback message from the user - optional
83
+ * @returns Promise containing the created feedback ID
84
+ *
85
+ * @throws {CobblError} When the request fails or API returns an error
86
+ *
87
+ * @example Submit just thumbs down first
88
+ * ```typescript
89
+ * const { id } = await client.createFeedback({
90
+ * runId: result.runId,
91
+ * helpful: 'not_helpful'
92
+ * })
93
+ * // Later, add details
94
+ * await client.updateFeedback(id, {
95
+ * userFeedback: 'The response was too formal and lengthy'
96
+ * })
97
+ * ```
98
+ *
99
+ * @example Submit both at once
100
+ * ```typescript
101
+ * await client.createFeedback({
102
+ * runId: result.runId,
103
+ * helpful: 'not_helpful',
104
+ * userFeedback: 'The response was too formal and lengthy'
105
+ * })
106
+ * ```
107
+ */
108
+ async createFeedback(
109
+ feedback: CreateFeedbackRequest
110
+ ): Promise<CreateFeedbackResponse> {
111
+ const validationError = validateCreateFeedback(feedback)
112
+ if (validationError) {
113
+ throw new CobblError(validationError, 'INVALID_REQUEST')
114
+ }
115
+
116
+ try {
117
+ // Build request body with only provided fields
118
+ const body: CreateFeedbackRequest = {
119
+ runId: feedback.runId.trim(),
120
+ helpful: feedback.helpful,
121
+ userFeedback: feedback.userFeedback?.trim(),
122
+ }
123
+
124
+ const response = await this.makeRequest('/public/v1/feedback', {
125
+ method: 'POST',
126
+ body: JSON.stringify(body),
127
+ })
128
+
129
+ if (!response.ok) {
130
+ await handleErrorResponse(response)
131
+ }
132
+
133
+ const data = (await response.json()) as {
134
+ id: string
135
+ message: string
136
+ }
137
+
138
+ return {
139
+ id: data.id,
140
+ message: data.message,
141
+ }
142
+ } catch (error) {
143
+ if (error instanceof CobblError) {
144
+ throw error
145
+ }
146
+ throw new CobblError(
147
+ `Failed to create feedback: ${error instanceof Error ? error.message : 'Unknown error'}`,
148
+ 'NETWORK_ERROR'
149
+ )
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Update existing feedback with additional data
155
+ * Use this to add helpful sentiment or detailed feedback after initial submission
156
+ *
157
+ * @param id - The ID returned from createFeedback
158
+ * @param update - The data to update
159
+ * @param update.helpful - Whether the output was helpful ('helpful' or 'not_helpful') - optional
160
+ * @param update.userFeedback - Detailed feedback message from the user - optional
161
+ * @returns Promise containing the updated feedback ID
162
+ *
163
+ * @throws {CobblError} When the request fails or API returns an error
164
+ *
165
+ * @example Add details after initial thumbs down
166
+ * ```typescript
167
+ * // First, submit just the rating
168
+ * const { id } = await client.createFeedback({
169
+ * runId: result.runId,
170
+ * helpful: 'not_helpful'
171
+ * })
172
+ *
173
+ * // Later, add detailed feedback
174
+ * await client.updateFeedback(id, {
175
+ * userFeedback: 'The response was too formal and lengthy'
176
+ * })
177
+ * ```
178
+ */
179
+ async updateFeedback(
180
+ id: string,
181
+ update: UpdateFeedbackRequest
182
+ ): Promise<UpdateFeedbackResponse> {
183
+ if (!id || id.trim().length === 0) {
184
+ throw new CobblError('id is required', 'INVALID_REQUEST')
185
+ }
186
+
187
+ const validationError = validateUpdateFeedback(update)
188
+ if (validationError) {
189
+ throw new CobblError(validationError, 'INVALID_REQUEST')
190
+ }
191
+
192
+ try {
193
+ // Build request body with only provided fields
194
+ const body: UpdateFeedbackRequest = {
195
+ helpful: update.helpful,
196
+ userFeedback: update.userFeedback?.trim(),
197
+ }
198
+
199
+ const response = await this.makeRequest(
200
+ `/public/v1/feedback/${id.trim()}`,
201
+ {
202
+ method: 'PATCH',
203
+ body: JSON.stringify(body),
204
+ }
205
+ )
206
+
207
+ if (!response.ok) {
208
+ await handleErrorResponse(response)
209
+ }
210
+
211
+ const data = (await response.json()) as {
212
+ id: string
213
+ message: string
214
+ }
215
+
216
+ return {
217
+ id: data.id,
218
+ message: data.message,
219
+ }
220
+ } catch (error) {
221
+ if (error instanceof CobblError) {
222
+ throw error
223
+ }
224
+ throw new CobblError(
225
+ `Failed to update feedback: ${error instanceof Error ? error.message : 'Unknown error'}`,
226
+ 'NETWORK_ERROR'
227
+ )
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Make an HTTP request to the Cobbl API
233
+ * @private
234
+ */
235
+ private async makeRequest(
236
+ path: string,
237
+ options: RequestInit
238
+ ): Promise<Response> {
239
+ const url = `${this.baseUrl}${path}`
240
+
241
+ const controller = new AbortController()
242
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)
243
+
244
+ try {
245
+ const response = await fetch(url, {
246
+ ...options,
247
+ headers: {
248
+ 'Content-Type': 'application/json',
249
+ ...options.headers,
250
+ },
251
+ signal: controller.signal,
252
+ })
253
+
254
+ return response
255
+ } finally {
256
+ clearTimeout(timeoutId)
257
+ }
258
+ }
259
+ }
260
+
261
+ // Export the client
262
+ export { CobblError } from './errors'
263
+ export type { CobblErrorCode } from './errors'
264
+ export type {
265
+ CreateFeedbackRequest,
266
+ CreateFeedbackResponse,
267
+ UpdateFeedbackRequest,
268
+ UpdateFeedbackResponse,
269
+ Helpful,
270
+ } from './types'
package/src/types.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Type definitions for the Cobbl SDK
3
+ *
4
+ * All types are defined locally to keep the SDK self-contained.
5
+ * These match the API contract and are used as type hints for SDK consumers.
3
6
  */
4
7
 
5
- import type { PromptVersionClient, TokenUsage, Helpful } from '@prompti/shared'
8
+
9
+ // ─── SDK Config ──────────────────────────────────────────────
6
10
 
7
11
  /**
8
12
  * Configuration options for the CobblClient
@@ -13,82 +17,184 @@ export interface CobblConfig {
13
17
  * @required
14
18
  */
15
19
  apiKey: string
20
+
21
+ /**
22
+ * Base URL for the API
23
+ * @optional
24
+ * @default 'https://api.cobbl.ai'
25
+ * @internal
26
+ * @example 'http://localhost:5001/your-project/us-central1/externalApi' // For local development
27
+ */
28
+ baseUrl?: string
16
29
  }
17
30
 
31
+ // ─── Feedback Types ──────────────────────────────────────────
32
+
18
33
  /**
19
- * Response from running a prompt
34
+ * Whether the AI output was helpful
20
35
  */
21
- export interface RunPromptResponse {
22
- /**
23
- * Unique ID for this prompt run - use this to link feedback
24
- */
36
+ export type Helpful = 'helpful' | 'not_helpful'
37
+
38
+ /**
39
+ * Request payload for creating feedback
40
+ * Supports segmented feedback submission - at least one of helpful or userFeedback required
41
+ */
42
+ export interface CreateFeedbackRequest {
25
43
  runId: string
44
+ helpful?: Helpful
45
+ userFeedback?: string
46
+ }
26
47
 
27
- /**
28
- * The AI-generated output
29
- */
30
- output: string
48
+ /**
49
+ * Response from creating feedback
50
+ */
51
+ export interface CreateFeedbackResponse {
52
+ id: string
53
+ message: string
54
+ }
31
55
 
32
- /**
33
- * Token usage statistics
34
- */
35
- tokenUsage: TokenUsage
56
+ /**
57
+ * Request payload for updating feedback
58
+ * At least one of helpful or userFeedback required
59
+ */
60
+ export interface UpdateFeedbackRequest {
61
+ helpful?: Helpful
62
+ userFeedback?: string
63
+ }
36
64
 
37
- /**
38
- * The rendered prompt that was sent to the LLM (with variables substituted)
39
- */
40
- renderedPrompt: string
65
+ /**
66
+ * Response from updating feedback
67
+ */
68
+ export interface UpdateFeedbackResponse {
69
+ id: string
70
+ message: string
71
+ }
41
72
 
42
- /**
43
- * Information about the prompt version that was used
44
- */
45
- promptVersion: PromptVersionClient
73
+ // ─── Prompt Types ────────────────────────────────────────────
74
+
75
+ /**
76
+ * Supported LLM providers
77
+ */
78
+ export type Provider = 'openai' | 'anthropic' | 'google'
79
+
80
+ /**
81
+ * Configuration for a variable in the prompt
82
+ */
83
+ export interface VariableConfig {
84
+ key: string
85
+ type: 'string' | 'number' | 'boolean' | 'list' | 'object'
86
+ required: boolean
87
+ description?: string
46
88
  }
47
89
 
48
90
  /**
49
- * Feedback submission data
91
+ * Input values provided when executing a prompt
50
92
  */
51
- export interface FeedbackSubmission {
52
- /**
53
- * The run ID from a previous runPrompt call
54
- * @required
55
- */
56
- runId: string
93
+ export type PromptInput = Record<
94
+ string,
95
+ string | number | boolean | unknown[] | Record<string, unknown>
96
+ >
57
97
 
58
- /**
59
- * Whether the output was helpful
60
- * @required
61
- */
62
- helpful: Helpful
98
+ // ─── Token Usage ─────────────────────────────────────────────
63
99
 
64
- /**
65
- * Detailed feedback message from the user
66
- * @required
67
- */
68
- userFeedback: string
100
+ /**
101
+ * Token usage breakdown for a completed prompt run
102
+ */
103
+ export interface TokenUsage {
104
+ inputTokens: number
105
+ outputTokens: number
106
+ totalTokens: number
69
107
  }
70
108
 
109
+ // ─── Analytics ───────────────────────────────────────────────
110
+
71
111
  /**
72
- * Response from submitting feedback
112
+ * Aggregated analytics for a prompt or prompt version
73
113
  */
74
- export interface SubmitFeedbackResponse {
75
- /**
76
- * Unique ID for the created feedback item
77
- */
78
- feedbackId: string
114
+ export interface PromptAggregatedAnalytics {
115
+ runs: {
116
+ totalCount: number
117
+ successCount: number
118
+ errorCount: number
119
+ totalProviderMs: number
120
+ totalRequestMs: number
121
+ successRate: number
122
+ errorRate: number
123
+ avgProviderMs: number
124
+ avgRequestMs: number
125
+ }
126
+ feedback: {
127
+ totalCount: number
128
+ helpfulCount: number
129
+ notHelpfulCount: number
130
+ helpfulRate: number
131
+ }
132
+ }
79
133
 
80
- /**
81
- * Success message
82
- */
83
- message: string
134
+ // ─── Prompt Version ──────────────────────────────────────────
135
+
136
+ /**
137
+ * Base fields shared across all prompt version sources
138
+ */
139
+ interface PromptVersionBase {
140
+ id: string
141
+ schemaVersion: number
142
+ createdAt: Date
143
+ updatedAt: Date
144
+ tenantId: string
145
+ environmentId: string
146
+ promptId: string
147
+ versionNumber: number
148
+ template: string
149
+ variables: VariableConfig[]
150
+ provider: Provider
151
+ model: string
152
+ status: 'draft' | 'active' | 'inactive'
153
+ parentVersionId: string | null
154
+ analytics: PromptAggregatedAnalytics
155
+ publishedAt: Date | null
156
+ }
157
+
158
+ /**
159
+ * Manually created prompt version (by a user or from feedback edits)
160
+ */
161
+ interface PromptVersionManual extends PromptVersionBase {
162
+ source: 'manual'
163
+ authorId: string
164
+ authorName: string | null
165
+ }
166
+
167
+ /**
168
+ * AI-generated prompt version suggestion from feedback issue assessment
169
+ */
170
+ interface PromptVersionIssueLlmSuggestion extends PromptVersionBase {
171
+ source: 'issue_llm_suggestion'
172
+ }
173
+
174
+ /**
175
+ * Prompt version - discriminated union by source
176
+ */
177
+ export type PromptVersionClient =
178
+ | PromptVersionManual
179
+ | PromptVersionIssueLlmSuggestion
180
+
181
+ // ─── API Request/Response Types ──────────────────────────────
182
+
183
+ /**
184
+ * Request payload for running a prompt
185
+ */
186
+ export interface RunPromptRequest {
187
+ promptSlug: string
188
+ input: PromptInput
84
189
  }
85
190
 
86
- // Re-export commonly used types from shared
87
- export type {
88
- PromptInput,
89
- PromptVersionClient,
90
- Provider,
91
- VariableConfig,
92
- TokenUsage,
93
- Helpful,
94
- } from '@prompti/shared'
191
+ /**
192
+ * Response from running a prompt
193
+ */
194
+ export interface RunPromptResponse {
195
+ runId: string
196
+ output: string
197
+ tokenUsage: TokenUsage
198
+ renderedPrompt: string
199
+ promptVersion: PromptVersionClient
200
+ }
@@ -0,0 +1,52 @@
1
+ import type {
2
+ CreateFeedbackRequest,
3
+ UpdateFeedbackRequest,
4
+ Helpful,
5
+ } from './types'
6
+
7
+ const VALID_HELPFUL_VALUES: Helpful[] = ['helpful', 'not_helpful']
8
+
9
+ export function validateCreateFeedback(
10
+ feedback: CreateFeedbackRequest
11
+ ): string | null {
12
+ if (!feedback.runId || feedback.runId.trim().length === 0) {
13
+ return 'runId is required'
14
+ }
15
+ if (
16
+ feedback.helpful !== undefined &&
17
+ !VALID_HELPFUL_VALUES.includes(feedback.helpful)
18
+ ) {
19
+ return 'helpful must be "helpful" or "not_helpful"'
20
+ }
21
+ if (
22
+ feedback.userFeedback !== undefined &&
23
+ feedback.userFeedback.trim().length === 0
24
+ ) {
25
+ return 'userFeedback cannot be empty'
26
+ }
27
+ if (feedback.helpful === undefined && feedback.userFeedback === undefined) {
28
+ return 'At least one of helpful or userFeedback is required'
29
+ }
30
+ return null
31
+ }
32
+
33
+ export function validateUpdateFeedback(
34
+ update: UpdateFeedbackRequest
35
+ ): string | null {
36
+ if (
37
+ update.helpful !== undefined &&
38
+ !VALID_HELPFUL_VALUES.includes(update.helpful)
39
+ ) {
40
+ return 'helpful must be "helpful" or "not_helpful"'
41
+ }
42
+ if (
43
+ update.userFeedback !== undefined &&
44
+ update.userFeedback.trim().length === 0
45
+ ) {
46
+ return 'userFeedback cannot be empty'
47
+ }
48
+ if (update.helpful === undefined && update.userFeedback === undefined) {
49
+ return 'At least one of helpful or userFeedback is required'
50
+ }
51
+ return null
52
+ }