@to11ai/sdk 0.0.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.
@@ -0,0 +1,202 @@
1
+ // ─── Shared primitives ─────────────────────────────────────────────────────
2
+
3
+ export type PromptStatus = "active" | "archived";
4
+ export type PromptFormat = "text" | "chat";
5
+
6
+ export interface ChatMessage {
7
+ role: "system" | "user" | "assistant" | "tool";
8
+ content: string;
9
+ }
10
+
11
+ // ─── Prompt ────────────────────────────────────────────────────────────────
12
+
13
+ export interface Prompt {
14
+ id: string;
15
+ projectId: string;
16
+ name: string;
17
+ slug: string;
18
+ description: string | null;
19
+ tags: string[];
20
+ status: PromptStatus;
21
+ createdByUserId: string;
22
+ createdAt: string;
23
+ updatedAt: string;
24
+ }
25
+
26
+ // ─── PromptVersion ─────────────────────────────────────────────────────────
27
+
28
+ export interface PromptVersion {
29
+ id: string;
30
+ promptId: string;
31
+ version: number;
32
+ format: PromptFormat;
33
+ templateJson: unknown;
34
+ variablesSchema: unknown;
35
+ modelConfig: unknown;
36
+ changelog: string | null;
37
+ createdByUserId: string;
38
+ createdAt: string;
39
+ }
40
+
41
+ // ─── PromptLabelPointer ────────────────────────────────────────────────────
42
+
43
+ export interface PromptLabelPointer {
44
+ promptId: string;
45
+ label: string;
46
+ versionId: string;
47
+ updatedByUserId: string;
48
+ updatedAt: string;
49
+ }
50
+
51
+ // ─── ResolvedPrompt ────────────────────────────────────────────────────────
52
+
53
+ export type RenderedPrompt =
54
+ | { format: "text"; text: string }
55
+ | { format: "chat"; messages: ChatMessage[] };
56
+
57
+ export interface ResolvedPrompt {
58
+ promptId: string;
59
+ versionId: string;
60
+ version: number;
61
+ labels: string[];
62
+ contentHash: string;
63
+ rendered: RenderedPrompt;
64
+ }
65
+
66
+ // ─── Pagination ────────────────────────────────────────────────────────────
67
+
68
+ export interface PaginationInfo {
69
+ page: number;
70
+ limit: number;
71
+ total: number;
72
+ totalPages: number;
73
+ }
74
+
75
+ export interface PaginatedResult<T> {
76
+ items: T[];
77
+ pagination: PaginationInfo;
78
+ }
79
+
80
+ // ─── Input types ──────────────────────────────────────────────────────────
81
+
82
+ export interface CreatePromptInput {
83
+ projectId: string;
84
+ name: string;
85
+ slug: string;
86
+ description?: string;
87
+ tags?: string[];
88
+ }
89
+
90
+ export type CreatePromptResult = Prompt;
91
+
92
+ // ─────────────────────────────────────────────────────────────────────────
93
+
94
+ export interface ListPromptsInput {
95
+ projectId: string;
96
+ status?: PromptStatus;
97
+ sortBy?: "createdAt" | "updatedAt" | "name";
98
+ tags?: string;
99
+ page?: number;
100
+ limit?: number;
101
+ }
102
+
103
+ export type ListPromptsResult = Prompt;
104
+
105
+ // ─────────────────────────────────────────────────────────────────────────
106
+
107
+ export interface GetPromptInput {
108
+ projectId: string;
109
+ promptId: string;
110
+ }
111
+
112
+ export type GetPromptResult = Prompt;
113
+
114
+ // ─────────────────────────────────────────────────────────────────────────
115
+
116
+ export interface UpdatePromptInput {
117
+ projectId: string;
118
+ promptId: string;
119
+ name?: string;
120
+ description?: string | null;
121
+ tags?: string[];
122
+ }
123
+
124
+ export type UpdatePromptResult = Prompt;
125
+
126
+ // ─────────────────────────────────────────────────────────────────────────
127
+
128
+ export interface ArchivePromptInput {
129
+ projectId: string;
130
+ promptId: string;
131
+ }
132
+
133
+ export type ArchivePromptResult = Prompt;
134
+
135
+ // ─────────────────────────────────────────────────────────────────────────
136
+
137
+ export interface CreatePromptVersionInput {
138
+ projectId: string;
139
+ promptId: string;
140
+ format: PromptFormat;
141
+ templateJson: unknown;
142
+ variablesSchema: unknown;
143
+ modelConfig?: unknown;
144
+ changelog?: string;
145
+ }
146
+
147
+ export type CreatePromptVersionResult = PromptVersion;
148
+
149
+ // ─────────────────────────────────────────────────────────────────────────
150
+
151
+ export interface ListPromptVersionsInput {
152
+ projectId: string;
153
+ promptId: string;
154
+ }
155
+
156
+ export type ListPromptVersionsResult = PromptVersion[];
157
+
158
+ // ─────────────────────────────────────────────────────────────────────────
159
+
160
+ export interface GetPromptVersionInput {
161
+ projectId: string;
162
+ promptId: string;
163
+ versionNumber: number;
164
+ }
165
+
166
+ export type GetPromptVersionResult = PromptVersion;
167
+
168
+ // ─────────────────────────────────────────────────────────────────────────
169
+
170
+ export interface MovePromptLabelInput {
171
+ projectId: string;
172
+ promptId: string;
173
+ label: string;
174
+ versionId: string;
175
+ reason?: string;
176
+ }
177
+
178
+ export type MovePromptLabelResult = PromptLabelPointer;
179
+
180
+ // ─────────────────────────────────────────────────────────────────────────
181
+
182
+ export interface ListPromptLabelsInput {
183
+ projectId: string;
184
+ promptId: string;
185
+ }
186
+
187
+ export type ListPromptLabelsResult = PromptLabelPointer[];
188
+
189
+ // ─────────────────────────────────────────────────────────────────────────
190
+
191
+ export interface ResolvePromptInput {
192
+ projectId: string;
193
+ slug: string;
194
+ labels: string[];
195
+ variables?: Record<string, unknown>;
196
+ /** Set false to bypass the LRU cache for this call. Default: true */
197
+ cache?: boolean;
198
+ /** Fallback prompt served if the API is unreachable and cache is empty. */
199
+ fallback?: ResolvedPrompt;
200
+ }
201
+
202
+ export type ResolvePromptResult = ResolvedPrompt;
@@ -0,0 +1,223 @@
1
+ import {
2
+ To11aiApiError,
3
+ To11aiAuthError,
4
+ To11aiNetworkError,
5
+ To11aiRateLimitError,
6
+ To11aiValidationError,
7
+ } from "./errors";
8
+
9
+ export type AuthConfig =
10
+ | { readonly apiKey: string; readonly getAccessToken?: never }
11
+ | { readonly getAccessToken: () => Promise<string>; readonly apiKey?: never };
12
+
13
+ export interface TransportConfig {
14
+ readonly baseUrl: string;
15
+ readonly auth: AuthConfig;
16
+ /** Request timeout in milliseconds. Default: 30_000 */
17
+ readonly timeoutMs?: number;
18
+ /** Max number of retries for network errors / 5xx (not counting the initial attempt). Default: 3 */
19
+ readonly maxRetries?: number;
20
+ }
21
+
22
+ /** Standard API envelope returned by the to11ai server. */
23
+ export interface ApiEnvelope<T = unknown> {
24
+ readonly success: boolean;
25
+ readonly code: number;
26
+ readonly data: T | null;
27
+ readonly error: { code?: string; message: string; details?: unknown } | null;
28
+ readonly pagination: {
29
+ readonly page: number;
30
+ readonly limit: number;
31
+ readonly total: number;
32
+ readonly totalPages: number;
33
+ } | null;
34
+ }
35
+
36
+ const DEFAULT_TIMEOUT_MS = 30_000;
37
+ const DEFAULT_MAX_RETRIES = 3;
38
+
39
+ /** Exponential backoff with jitter: 100ms * 2^attempt + random(0,100ms) */
40
+ const backoffMs = (attempt: number): number =>
41
+ 100 * 2 ** attempt + Math.random() * 100;
42
+
43
+ const sleep = (ms: number) =>
44
+ new Promise<void>((resolve) => setTimeout(resolve, ms));
45
+
46
+ /**
47
+ * Map HTTP status codes and error envelopes to typed SDK errors.
48
+ */
49
+ function mapHttpError(
50
+ status: number,
51
+ envelope: { code?: string; message?: string; details?: unknown } | null,
52
+ rawMessage: string,
53
+ headers?: Headers,
54
+ ): To11aiApiError {
55
+ const message = envelope?.message ?? rawMessage;
56
+ const errorCode = envelope?.code;
57
+ const details = envelope?.details;
58
+
59
+ if (status === 401 || status === 403) {
60
+ return new To11aiAuthError(
61
+ message,
62
+ status as 401 | 403,
63
+ errorCode,
64
+ details,
65
+ );
66
+ }
67
+ if (status === 429) {
68
+ const retryAfterHeader = headers?.get("retry-after");
69
+ const retryAfterSeconds = retryAfterHeader
70
+ ? Number(retryAfterHeader)
71
+ : undefined;
72
+ return new To11aiRateLimitError(
73
+ message,
74
+ Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined,
75
+ errorCode,
76
+ details,
77
+ );
78
+ }
79
+ if (status === 400) {
80
+ return new To11aiValidationError(message, errorCode, details);
81
+ }
82
+ return new To11aiApiError(message, status, errorCode, details);
83
+ }
84
+
85
+ export class Transport {
86
+ private readonly baseUrl: string;
87
+ private readonly auth: AuthConfig;
88
+ private readonly timeoutMs: number;
89
+ private readonly maxRetries: number;
90
+
91
+ constructor(config: TransportConfig) {
92
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
93
+ this.auth = config.auth;
94
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
95
+ this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
96
+ }
97
+
98
+ private async buildHeaders(): Promise<Record<string, string>> {
99
+ const headers: Record<string, string> = {
100
+ "content-type": "application/json",
101
+ accept: "application/json",
102
+ };
103
+
104
+ if (this.auth.apiKey !== undefined) {
105
+ headers.authorization = `Bearer ${this.auth.apiKey}`;
106
+ } else if (this.auth.getAccessToken) {
107
+ const token = await this.auth.getAccessToken();
108
+ headers.authorization = `Bearer ${token}`;
109
+ }
110
+
111
+ return headers;
112
+ }
113
+
114
+ async request<T>(
115
+ method: string,
116
+ path: string,
117
+ options?: {
118
+ query?: Record<string, string | number | boolean | undefined>;
119
+ body?: unknown;
120
+ },
121
+ ): Promise<ApiEnvelope<T>> {
122
+ const url = new URL(this.baseUrl + path);
123
+
124
+ if (options?.query) {
125
+ for (const [key, value] of Object.entries(options.query)) {
126
+ if (value !== undefined) {
127
+ url.searchParams.set(key, String(value));
128
+ }
129
+ }
130
+ }
131
+
132
+ let attempt = 0;
133
+
134
+ while (true) {
135
+ const headers = await this.buildHeaders();
136
+ const controller = new AbortController();
137
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
138
+
139
+ try {
140
+ const response = await fetch(url.toString(), {
141
+ method,
142
+ headers,
143
+ body:
144
+ options?.body !== undefined
145
+ ? JSON.stringify(options.body)
146
+ : undefined,
147
+ signal: controller.signal,
148
+ });
149
+
150
+ clearTimeout(timeoutId);
151
+
152
+ // 5xx — retry if we have retries left
153
+ if (response.status >= 500 && attempt < this.maxRetries) {
154
+ // Drain the body to free the underlying connection before sleeping
155
+ await response.body?.cancel().catch(() => undefined);
156
+ attempt++;
157
+ await sleep(backoffMs(attempt));
158
+ continue;
159
+ }
160
+
161
+ let json: ApiEnvelope<T> | undefined;
162
+ const text = await response.text();
163
+
164
+ try {
165
+ json = JSON.parse(text) as ApiEnvelope<T>;
166
+ } catch {
167
+ // Not a JSON body — treat as generic API error
168
+ throw mapHttpError(
169
+ response.status,
170
+ null,
171
+ text || response.statusText,
172
+ response.headers,
173
+ );
174
+ }
175
+
176
+ if (!response.ok) {
177
+ throw mapHttpError(
178
+ response.status,
179
+ json?.error ?? null,
180
+ json?.error?.message ?? response.statusText,
181
+ response.headers,
182
+ );
183
+ }
184
+
185
+ return json;
186
+ } catch (err) {
187
+ clearTimeout(timeoutId);
188
+
189
+ // Re-throw typed SDK errors immediately (no retry)
190
+ if (err instanceof To11aiApiError) {
191
+ throw err;
192
+ }
193
+
194
+ // Timeout / network errors — retry if retries remain
195
+ const isTimeout =
196
+ err instanceof DOMException && err.name === "AbortError";
197
+ // Only retry on genuine network failures (fetch throws TypeError) or timeouts.
198
+ // Unexpected errors (e.g. getAccessToken rejections) propagate immediately.
199
+ const isNetworkError = err instanceof TypeError;
200
+
201
+ if ((isTimeout || isNetworkError) && attempt < this.maxRetries) {
202
+ attempt++;
203
+ await sleep(backoffMs(attempt));
204
+ continue;
205
+ }
206
+
207
+ if (isTimeout) {
208
+ throw new To11aiNetworkError(
209
+ `Request timed out after ${this.timeoutMs}ms`,
210
+ err,
211
+ );
212
+ }
213
+
214
+ if (isNetworkError) {
215
+ throw new To11aiNetworkError(`Network error: ${err.message}`, err);
216
+ }
217
+
218
+ // Re-throw unexpected errors (e.g. auth callback failures) as-is
219
+ throw err;
220
+ }
221
+ }
222
+ }
223
+ }