@to11ai/sdk 0.0.2 → 0.1.1
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/LICENSE +373 -0
- package/README.md +91 -1
- package/dist/client.cjs +20 -5
- package/dist/client.d.ts +10 -1
- package/dist/client.js +26 -2
- package/dist/errors.cjs +89 -1
- package/dist/errors.d.ts +96 -0
- package/dist/errors.js +89 -1
- package/dist/gateway.cjs +8 -1
- package/dist/gateway.d.ts +19 -0
- package/dist/gateway.js +8 -1
- package/dist/projectEnvironments/resource.cjs +110 -0
- package/dist/projectEnvironments/resource.d.ts +67 -0
- package/dist/projectEnvironments/resource.js +70 -0
- package/dist/projectEnvironments/types.d.ts +121 -0
- package/dist/prompts/resource.cjs +81 -0
- package/dist/prompts/resource.d.ts +61 -2
- package/dist/prompts/resource.js +81 -0
- package/dist/prompts/types.d.ts +217 -3
- package/dist/routingRules/resource.cjs +131 -0
- package/dist/routingRules/resource.d.ts +22 -0
- package/dist/routingRules/resource.js +91 -0
- package/dist/routingRules/types.d.ts +169 -0
- package/dist/traceparent.d.ts +61 -0
- package/dist/transport.d.ts +6 -1
- package/package.json +25 -5
- package/src/client.ts +60 -1
- package/src/errors.ts +172 -0
- package/src/gateway.ts +29 -1
- package/src/projectEnvironments/resource.ts +220 -0
- package/src/projectEnvironments/types.ts +151 -0
- package/src/prompts/resource.ts +232 -0
- package/src/prompts/types.ts +270 -3
- package/src/routingRules/resource.ts +180 -0
- package/src/routingRules/types.ts +222 -0
- package/src/traceparent.ts +124 -0
- package/src/transport.ts +191 -5
package/src/transport.ts
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
+
EnvNotEnabledForProviderError,
|
|
3
|
+
NoApplicableRouteError,
|
|
4
|
+
PromptLabelNotRegisteredError,
|
|
5
|
+
RuleNotFoundError,
|
|
6
|
+
RuleVersionInvalidSnapshotError,
|
|
2
7
|
To11aiApiError,
|
|
3
8
|
To11aiAuthError,
|
|
4
9
|
To11aiNetworkError,
|
|
5
10
|
To11aiRateLimitError,
|
|
6
11
|
To11aiValidationError,
|
|
7
12
|
} from "./errors";
|
|
13
|
+
import { activeTraceContext } from "./traceparent";
|
|
14
|
+
|
|
15
|
+
const INVALID_SNAPSHOT_ERROR_CODES = new Set([
|
|
16
|
+
"INVALID_VARIANT_PARAMS",
|
|
17
|
+
"INVALID_VARIANT_WEIGHTS",
|
|
18
|
+
"INVALID_RULE_STRATEGY",
|
|
19
|
+
"INVALID_STEP_COUNT",
|
|
20
|
+
"RULE_ENDPOINT_MISMATCH",
|
|
21
|
+
"INVALID_WHEN_CLAUSE",
|
|
22
|
+
"INVALID_STRATEGY_VARIANTS",
|
|
23
|
+
"INVALID_RETRY_POLICY",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const RULE_NOT_FOUND_DETAILS_ERRORS = new Set([
|
|
27
|
+
"rule_not_found",
|
|
28
|
+
"rule_not_found_in_env",
|
|
29
|
+
]);
|
|
8
30
|
|
|
9
31
|
export type AuthConfig =
|
|
10
32
|
| { readonly apiKey: string; readonly getAccessToken?: never }
|
|
@@ -15,7 +37,12 @@ export interface TransportConfig {
|
|
|
15
37
|
readonly auth: AuthConfig;
|
|
16
38
|
/** Request timeout in milliseconds. Default: 30_000 */
|
|
17
39
|
readonly timeoutMs?: number;
|
|
18
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Max number of retries for network errors / 5xx on idempotent methods
|
|
42
|
+
* (GET/HEAD/OPTIONS/PUT/DELETE/TRACE per RFC 9110 §9.2.2). POST/PATCH are
|
|
43
|
+
* never auto-retried regardless of this value. Not counting the initial
|
|
44
|
+
* attempt. Default: 3
|
|
45
|
+
*/
|
|
19
46
|
readonly maxRetries?: number;
|
|
20
47
|
}
|
|
21
48
|
|
|
@@ -36,6 +63,22 @@ export interface ApiEnvelope<T = unknown> {
|
|
|
36
63
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
37
64
|
const DEFAULT_MAX_RETRIES = 3;
|
|
38
65
|
|
|
66
|
+
/**
|
|
67
|
+
* HTTP methods classified as idempotent by RFC 9110 §9.2.2. The SDK only
|
|
68
|
+
* auto-retries 5xx, network failures, and timeouts on these methods because
|
|
69
|
+
* a write that timed out or returned 5xx may have already committed on the
|
|
70
|
+
* server — replaying a non-idempotent method (POST/PATCH) can create
|
|
71
|
+
* duplicates or double-apply state.
|
|
72
|
+
*/
|
|
73
|
+
const IDEMPOTENT_METHODS = new Set([
|
|
74
|
+
"GET",
|
|
75
|
+
"HEAD",
|
|
76
|
+
"OPTIONS",
|
|
77
|
+
"PUT",
|
|
78
|
+
"DELETE",
|
|
79
|
+
"TRACE",
|
|
80
|
+
]);
|
|
81
|
+
|
|
39
82
|
/** Exponential backoff with jitter: 100ms * 2^attempt + random(0,100ms) */
|
|
40
83
|
const backoffMs = (attempt: number): number =>
|
|
41
84
|
100 * 2 ** attempt + Math.random() * 100;
|
|
@@ -76,9 +119,132 @@ function mapHttpError(
|
|
|
76
119
|
details,
|
|
77
120
|
);
|
|
78
121
|
}
|
|
122
|
+
if (
|
|
123
|
+
(status === 400 || status === 422) &&
|
|
124
|
+
errorCode &&
|
|
125
|
+
INVALID_SNAPSHOT_ERROR_CODES.has(errorCode)
|
|
126
|
+
) {
|
|
127
|
+
const d = (details && typeof details === "object" ? details : {}) as Record<
|
|
128
|
+
string,
|
|
129
|
+
unknown
|
|
130
|
+
>;
|
|
131
|
+
return new RuleVersionInvalidSnapshotError(
|
|
132
|
+
message,
|
|
133
|
+
errorCode,
|
|
134
|
+
{
|
|
135
|
+
field: typeof d.field === "string" ? d.field : undefined,
|
|
136
|
+
reason: typeof d.reason === "string" ? d.reason : undefined,
|
|
137
|
+
variantName:
|
|
138
|
+
typeof d.variantName === "string" ? d.variantName : undefined,
|
|
139
|
+
},
|
|
140
|
+
details,
|
|
141
|
+
status,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
79
144
|
if (status === 400) {
|
|
80
145
|
return new To11aiValidationError(message, errorCode, details);
|
|
81
146
|
}
|
|
147
|
+
if (status === 404) {
|
|
148
|
+
const d = (details && typeof details === "object" ? details : {}) as Record<
|
|
149
|
+
string,
|
|
150
|
+
unknown
|
|
151
|
+
>;
|
|
152
|
+
const detailsError = typeof d.error === "string" ? d.error : undefined;
|
|
153
|
+
const isRuleNotFound =
|
|
154
|
+
errorCode === "RULE_NOT_FOUND" ||
|
|
155
|
+
errorCode === "RULE_NOT_FOUND_IN_ENV" ||
|
|
156
|
+
(detailsError !== undefined &&
|
|
157
|
+
RULE_NOT_FOUND_DETAILS_ERRORS.has(detailsError));
|
|
158
|
+
if (isRuleNotFound) {
|
|
159
|
+
const resolvedCode =
|
|
160
|
+
errorCode === "RULE_NOT_FOUND" || errorCode === "RULE_NOT_FOUND_IN_ENV"
|
|
161
|
+
? errorCode
|
|
162
|
+
: detailsError === "rule_not_found_in_env"
|
|
163
|
+
? "RULE_NOT_FOUND_IN_ENV"
|
|
164
|
+
: "RULE_NOT_FOUND";
|
|
165
|
+
return new RuleNotFoundError(
|
|
166
|
+
message,
|
|
167
|
+
resolvedCode,
|
|
168
|
+
{
|
|
169
|
+
ruleId: typeof d.ruleId === "string" ? d.ruleId : undefined,
|
|
170
|
+
model: typeof d.model === "string" ? d.model : undefined,
|
|
171
|
+
env: typeof d.env === "string" ? d.env : undefined,
|
|
172
|
+
},
|
|
173
|
+
details,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (status === 422 && errorCode === "NO_APPLICABLE_ROUTE") {
|
|
178
|
+
const d = (details && typeof details === "object" ? details : {}) as Record<
|
|
179
|
+
string,
|
|
180
|
+
unknown
|
|
181
|
+
>;
|
|
182
|
+
const ruleId =
|
|
183
|
+
typeof d.rule === "string"
|
|
184
|
+
? d.rule
|
|
185
|
+
: typeof d.ruleId === "string"
|
|
186
|
+
? d.ruleId
|
|
187
|
+
: "";
|
|
188
|
+
const versionId =
|
|
189
|
+
typeof d.version === "string"
|
|
190
|
+
? d.version
|
|
191
|
+
: typeof d.versionId === "string"
|
|
192
|
+
? d.versionId
|
|
193
|
+
: "";
|
|
194
|
+
return new NoApplicableRouteError(
|
|
195
|
+
message,
|
|
196
|
+
{
|
|
197
|
+
ruleId,
|
|
198
|
+
versionId,
|
|
199
|
+
reason: typeof d.reason === "string" ? d.reason : "",
|
|
200
|
+
},
|
|
201
|
+
details,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (status === 422 && errorCode === "ENV_NOT_ENABLED_FOR_PROVIDER") {
|
|
205
|
+
const d = (details && typeof details === "object" ? details : {}) as Record<
|
|
206
|
+
string,
|
|
207
|
+
unknown
|
|
208
|
+
>;
|
|
209
|
+
const ruleId =
|
|
210
|
+
typeof d.rule === "string"
|
|
211
|
+
? d.rule
|
|
212
|
+
: typeof d.ruleId === "string"
|
|
213
|
+
? d.ruleId
|
|
214
|
+
: "";
|
|
215
|
+
const envSlug =
|
|
216
|
+
typeof d.env === "string"
|
|
217
|
+
? d.env
|
|
218
|
+
: typeof d.envSlug === "string"
|
|
219
|
+
? d.envSlug
|
|
220
|
+
: "";
|
|
221
|
+
return new EnvNotEnabledForProviderError(
|
|
222
|
+
message,
|
|
223
|
+
{
|
|
224
|
+
ruleId,
|
|
225
|
+
envSlug,
|
|
226
|
+
providerId: typeof d.providerId === "string" ? d.providerId : "",
|
|
227
|
+
},
|
|
228
|
+
details,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if (status === 422 && errorCode === "PROMPT_LABEL_NOT_REGISTERED") {
|
|
232
|
+
// TO11-664: strict-mode rejection. Pull projectId / label out of the
|
|
233
|
+
// structured `details` payload the API attaches so call sites can use
|
|
234
|
+
// the typed attributes without re-parsing.
|
|
235
|
+
const d = (details && typeof details === "object" ? details : {}) as Record<
|
|
236
|
+
string,
|
|
237
|
+
unknown
|
|
238
|
+
>;
|
|
239
|
+
const projectId = typeof d.projectId === "string" ? d.projectId : "";
|
|
240
|
+
const label = typeof d.label === "string" ? d.label : "";
|
|
241
|
+
return new PromptLabelNotRegisteredError(
|
|
242
|
+
message,
|
|
243
|
+
projectId,
|
|
244
|
+
label,
|
|
245
|
+
details,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
82
248
|
return new To11aiApiError(message, status, errorCode, details);
|
|
83
249
|
}
|
|
84
250
|
|
|
@@ -108,6 +274,12 @@ export class Transport {
|
|
|
108
274
|
headers.authorization = `Bearer ${token}`;
|
|
109
275
|
}
|
|
110
276
|
|
|
277
|
+
const tp = activeTraceContext();
|
|
278
|
+
if (tp) {
|
|
279
|
+
headers.traceparent = tp.traceparent;
|
|
280
|
+
if (tp.tracestate) headers.tracestate = tp.tracestate;
|
|
281
|
+
}
|
|
282
|
+
|
|
111
283
|
return headers;
|
|
112
284
|
}
|
|
113
285
|
|
|
@@ -120,6 +292,7 @@ export class Transport {
|
|
|
120
292
|
},
|
|
121
293
|
): Promise<ApiEnvelope<T>> {
|
|
122
294
|
const url = new URL(this.baseUrl + path);
|
|
295
|
+
const isIdempotent = IDEMPOTENT_METHODS.has(method.toUpperCase());
|
|
123
296
|
|
|
124
297
|
if (options?.query) {
|
|
125
298
|
for (const [key, value] of Object.entries(options.query)) {
|
|
@@ -149,8 +322,14 @@ export class Transport {
|
|
|
149
322
|
|
|
150
323
|
clearTimeout(timeoutId);
|
|
151
324
|
|
|
152
|
-
// 5xx — retry if
|
|
153
|
-
|
|
325
|
+
// 5xx — retry only if the method is idempotent (RFC 9110 §9.2.2).
|
|
326
|
+
// POST/PATCH may have already committed server-side, so replaying
|
|
327
|
+
// them can create duplicates.
|
|
328
|
+
if (
|
|
329
|
+
response.status >= 500 &&
|
|
330
|
+
isIdempotent &&
|
|
331
|
+
attempt < this.maxRetries
|
|
332
|
+
) {
|
|
154
333
|
// Drain the body to free the underlying connection before sleeping
|
|
155
334
|
await response.body?.cancel().catch(() => undefined);
|
|
156
335
|
attempt++;
|
|
@@ -191,14 +370,21 @@ export class Transport {
|
|
|
191
370
|
throw err;
|
|
192
371
|
}
|
|
193
372
|
|
|
194
|
-
// Timeout / network errors — retry if
|
|
373
|
+
// Timeout / network errors — retry only if the method is idempotent
|
|
374
|
+
// (RFC 9110 §9.2.2). A POST/PATCH whose request body was fully sent
|
|
375
|
+
// before a connection drop or timeout may have committed server-side,
|
|
376
|
+
// so a replay can duplicate the write.
|
|
195
377
|
const isTimeout =
|
|
196
378
|
err instanceof DOMException && err.name === "AbortError";
|
|
197
379
|
// Only retry on genuine network failures (fetch throws TypeError) or timeouts.
|
|
198
380
|
// Unexpected errors (e.g. getAccessToken rejections) propagate immediately.
|
|
199
381
|
const isNetworkError = err instanceof TypeError;
|
|
200
382
|
|
|
201
|
-
if (
|
|
383
|
+
if (
|
|
384
|
+
(isTimeout || isNetworkError) &&
|
|
385
|
+
isIdempotent &&
|
|
386
|
+
attempt < this.maxRetries
|
|
387
|
+
) {
|
|
202
388
|
attempt++;
|
|
203
389
|
await sleep(backoffMs(attempt));
|
|
204
390
|
continue;
|