@modelrelay/sdk 0.23.0 → 0.25.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/README.md +191 -1
- package/dist/index.cjs +569 -7
- package/dist/index.d.cts +1015 -689
- package/dist/index.d.ts +1015 -689
- package/dist/index.js +562 -7
- package/package.json +25 -27
package/README.md
CHANGED
|
@@ -82,12 +82,53 @@ const stream = await mr.chat.completions.create(
|
|
|
82
82
|
);
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
### Typed models and
|
|
85
|
+
### Typed models, stop reasons, and message roles
|
|
86
86
|
|
|
87
87
|
- Models are plain strings (e.g., `"gpt-4o"`), so new models do not require SDK updates.
|
|
88
88
|
- Stop reasons are parsed into the `StopReason` union (e.g., `StopReasons.EndTurn`); unknown values surface as `{ other: "<raw>" }`.
|
|
89
|
+
- Message roles use a typed union (`MessageRole`) with constants available via `MessageRoles`.
|
|
89
90
|
- Usage backfills `totalTokens` when the backend omits it, ensuring consistent accounting.
|
|
90
91
|
|
|
92
|
+
```ts
|
|
93
|
+
import { MessageRoles } from "@modelrelay/sdk";
|
|
94
|
+
|
|
95
|
+
// Use typed role constants
|
|
96
|
+
const messages = [
|
|
97
|
+
{ role: MessageRoles.System, content: "You are helpful." },
|
|
98
|
+
{ role: MessageRoles.User, content: "Hello!" },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
// Available roles: User, Assistant, System, Tool
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Customer-attributed requests
|
|
105
|
+
|
|
106
|
+
For customer-attributed requests, the customer's tier determines which model to use.
|
|
107
|
+
Use `forCustomer()` instead of providing a model:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// Customer-attributed: tier determines model, no model parameter needed
|
|
111
|
+
const stream = await mr.chat.forCustomer("customer-123").create({
|
|
112
|
+
messages: [{ role: "user", content: "Hello!" }]
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
for await (const event of stream) {
|
|
116
|
+
if (event.type === "message_delta" && event.textDelta) {
|
|
117
|
+
console.log(event.textDelta);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Non-streaming
|
|
122
|
+
const completion = await mr.chat.forCustomer("customer-123").create(
|
|
123
|
+
{ messages: [{ role: "user", content: "Hello!" }] },
|
|
124
|
+
{ stream: false }
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This provides compile-time separation between:
|
|
129
|
+
- **Direct/PAYGO requests** (`chat.completions.create({ model, ... })`) — model is required
|
|
130
|
+
- **Customer-attributed requests** (`chat.forCustomer(id).create(...)`) — tier determines model
|
|
131
|
+
|
|
91
132
|
### Structured outputs (`response_format`)
|
|
92
133
|
|
|
93
134
|
Request structured JSON instead of free-form text when the backend supports it:
|
|
@@ -164,6 +205,155 @@ const final = await stream.collect();
|
|
|
164
205
|
console.log(final.items.length);
|
|
165
206
|
```
|
|
166
207
|
|
|
208
|
+
### Type-safe structured outputs with Zod schemas
|
|
209
|
+
|
|
210
|
+
For automatic schema generation and validation, use `structured()` with Zod:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { ModelRelay } from "@modelrelay/sdk";
|
|
214
|
+
import { z } from "zod";
|
|
215
|
+
|
|
216
|
+
const mr = new ModelRelay({ key: "mr_sk_..." });
|
|
217
|
+
|
|
218
|
+
// Define your output type with Zod
|
|
219
|
+
const PersonSchema = z.object({
|
|
220
|
+
name: z.string(),
|
|
221
|
+
age: z.number(),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// structured() auto-generates JSON schema and validates responses
|
|
225
|
+
const result = await mr.chat.completions.structured(
|
|
226
|
+
PersonSchema,
|
|
227
|
+
{
|
|
228
|
+
model: "claude-sonnet-4-20250514",
|
|
229
|
+
messages: [{ role: "user", content: "Extract: John Doe is 30 years old" }],
|
|
230
|
+
},
|
|
231
|
+
{ maxRetries: 2 } // Retry on validation failures
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
console.log(`Name: ${result.value.name}, Age: ${result.value.age}`);
|
|
235
|
+
console.log(`Succeeded on attempt ${result.attempts}`);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Schema features
|
|
239
|
+
|
|
240
|
+
Zod schemas map to JSON Schema properties:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
const StatusSchema = z.object({
|
|
244
|
+
// Required string field
|
|
245
|
+
code: z.string(),
|
|
246
|
+
|
|
247
|
+
// Optional field (not in "required" array)
|
|
248
|
+
notes: z.string().optional(),
|
|
249
|
+
|
|
250
|
+
// Description for documentation
|
|
251
|
+
email: z.string().email().describe("User's email address"),
|
|
252
|
+
|
|
253
|
+
// Enum constraint
|
|
254
|
+
priority: z.enum(["low", "medium", "high"]),
|
|
255
|
+
|
|
256
|
+
// Nested objects are fully supported
|
|
257
|
+
address: z.object({
|
|
258
|
+
city: z.string(),
|
|
259
|
+
country: z.string(),
|
|
260
|
+
}),
|
|
261
|
+
|
|
262
|
+
// Arrays
|
|
263
|
+
tags: z.array(z.string()),
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### Handling validation errors
|
|
268
|
+
|
|
269
|
+
When validation fails after all retries:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { StructuredExhaustedError } from "@modelrelay/sdk";
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const result = await mr.chat.completions.structured(
|
|
276
|
+
PersonSchema,
|
|
277
|
+
{ model: "claude-sonnet-4-20250514", messages },
|
|
278
|
+
{ maxRetries: 2 }
|
|
279
|
+
);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (err instanceof StructuredExhaustedError) {
|
|
282
|
+
console.log(`Failed after ${err.allAttempts.length} attempts`);
|
|
283
|
+
for (const attempt of err.allAttempts) {
|
|
284
|
+
console.log(`Attempt ${attempt.attempt}: ${attempt.rawJson}`);
|
|
285
|
+
if (attempt.error.kind === "validation" && attempt.error.issues) {
|
|
286
|
+
for (const issue of attempt.error.issues) {
|
|
287
|
+
console.log(` - ${issue.path ?? "root"}: ${issue.message}`);
|
|
288
|
+
}
|
|
289
|
+
} else if (attempt.error.kind === "decode") {
|
|
290
|
+
console.log(` Decode error: ${attempt.error.message}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Custom retry handlers
|
|
298
|
+
|
|
299
|
+
Customize retry behavior:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import type { RetryHandler } from "@modelrelay/sdk";
|
|
303
|
+
|
|
304
|
+
const customHandler: RetryHandler = {
|
|
305
|
+
onValidationError(attempt, rawJson, error, messages) {
|
|
306
|
+
if (attempt >= 3) {
|
|
307
|
+
return null; // Stop retrying
|
|
308
|
+
}
|
|
309
|
+
return [
|
|
310
|
+
{
|
|
311
|
+
role: "user",
|
|
312
|
+
content: `Invalid response. Issues: ${JSON.stringify(error.issues)}. Try again.`,
|
|
313
|
+
},
|
|
314
|
+
];
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const result = await mr.chat.completions.structured(
|
|
319
|
+
PersonSchema,
|
|
320
|
+
{ model: "claude-sonnet-4-20250514", messages },
|
|
321
|
+
{ maxRetries: 3, retryHandler: customHandler }
|
|
322
|
+
);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### Streaming structured outputs
|
|
326
|
+
|
|
327
|
+
For streaming with Zod schema (no retries):
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
const stream = await mr.chat.completions.streamStructured(
|
|
331
|
+
PersonSchema,
|
|
332
|
+
{
|
|
333
|
+
model: "claude-sonnet-4-20250514",
|
|
334
|
+
messages: [{ role: "user", content: "Extract: Jane, 25" }],
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
for await (const evt of stream) {
|
|
339
|
+
if (evt.type === "completion") {
|
|
340
|
+
console.log("Final:", evt.payload);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Customer-attributed structured outputs
|
|
346
|
+
|
|
347
|
+
Works with customer-attributed requests too:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
const result = await mr.chat.forCustomer("customer-123").structured(
|
|
351
|
+
PersonSchema,
|
|
352
|
+
{ messages: [{ role: "user", content: "Extract: John, 30" }] },
|
|
353
|
+
{ maxRetries: 2 }
|
|
354
|
+
);
|
|
355
|
+
```
|
|
356
|
+
|
|
167
357
|
### Telemetry & metrics hooks
|
|
168
358
|
|
|
169
359
|
Provide lightweight callbacks to observe latency and usage without extra deps:
|