@modelrelay/sdk 1.3.2 → 1.3.3
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 +145 -218
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,220 +1,40 @@
|
|
|
1
1
|
# ModelRelay TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
bun add @modelrelay/sdk
|
|
5
|
-
```
|
|
6
|
-
|
|
7
|
-
## Token Providers (Automatic Bearer Auth)
|
|
8
|
-
|
|
9
|
-
Use token providers when you want the SDK to automatically obtain/refresh **bearer tokens** for data-plane calls like `/responses` and `/runs`.
|
|
10
|
-
|
|
11
|
-
### OIDC id_token → customer bearer token (exchange)
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { ModelRelay, OIDCExchangeTokenProvider, parseSecretKey } from "@modelrelay/sdk";
|
|
15
|
-
|
|
16
|
-
const tokenProvider = new OIDCExchangeTokenProvider({
|
|
17
|
-
apiKey: parseSecretKey(process.env.MODELRELAY_API_KEY!),
|
|
18
|
-
idTokenProvider: async () => {
|
|
19
|
-
// Return an OIDC id_token from your auth system (web login, device flow, etc).
|
|
20
|
-
return process.env.OIDC_ID_TOKEN!;
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const mr = new ModelRelay({ tokenProvider });
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
If you need an `id_token` in a CLI-like context, you can use the OAuth device flow helper:
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
import { runOAuthDeviceFlowForIDToken } from "@modelrelay/sdk";
|
|
31
|
-
|
|
32
|
-
const idToken = await runOAuthDeviceFlowForIDToken({
|
|
33
|
-
deviceAuthorizationEndpoint: "https://issuer.example.com/oauth/device/code",
|
|
34
|
-
tokenEndpoint: "https://issuer.example.com/oauth/token",
|
|
35
|
-
clientId: "your-client-id",
|
|
36
|
-
scope: "openid email profile",
|
|
37
|
-
onUserCode: ({ verificationUri, userCode }) => {
|
|
38
|
-
console.log(`Open ${verificationUri} and enter code: ${userCode}`);
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Secret key → customer bearer token (mint)
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
import { CustomerTokenProvider, ModelRelay } from "@modelrelay/sdk";
|
|
47
|
-
|
|
48
|
-
const tokenProvider = new CustomerTokenProvider({
|
|
49
|
-
secretKey: process.env.MODELRELAY_API_KEY!,
|
|
50
|
-
request: { projectId: "proj_...", customerId: "cust_..." },
|
|
51
|
-
});
|
|
3
|
+
The ModelRelay TypeScript SDK is a **responses-first**, **streaming-first** client for building cross-provider LLM features without committing to any single vendor API.
|
|
52
4
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
import { ModelRelay } from "@modelrelay/sdk";
|
|
60
|
-
|
|
61
|
-
const mr = ModelRelay.fromSecretKey("mr_sk_...");
|
|
62
|
-
|
|
63
|
-
const req = mr.responses
|
|
64
|
-
.new()
|
|
65
|
-
.model("claude-sonnet-4-20250514")
|
|
66
|
-
.user("Hello")
|
|
67
|
-
.build();
|
|
68
|
-
|
|
69
|
-
const stream = await mr.responses.stream(req);
|
|
5
|
+
It's designed to feel great in TypeScript:
|
|
6
|
+
- One fluent builder for **streaming/non-streaming**, **text/structured**, and **customer-attributed** requests
|
|
7
|
+
- Structured outputs powered by Zod schemas with validation and retry
|
|
8
|
+
- A practical tool-use toolkit for "LLM + tools" apps
|
|
70
9
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
10
|
+
```bash
|
|
11
|
+
npm install @modelrelay/sdk
|
|
12
|
+
# or
|
|
13
|
+
bun add @modelrelay/sdk
|
|
76
14
|
```
|
|
77
15
|
|
|
78
|
-
##
|
|
16
|
+
## Quick Start
|
|
79
17
|
|
|
80
18
|
```ts
|
|
81
19
|
import { ModelRelay } from "@modelrelay/sdk";
|
|
82
20
|
|
|
83
|
-
const mr = ModelRelay.fromSecretKey(
|
|
84
|
-
const customer = mr.forCustomer("cust_abc123");
|
|
21
|
+
const mr = ModelRelay.fromSecretKey(process.env.MODELRELAY_API_KEY!);
|
|
85
22
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
23
|
+
const response = await mr.responses.create(
|
|
24
|
+
mr.responses
|
|
25
|
+
.new()
|
|
26
|
+
.model("claude-sonnet-4-20250514")
|
|
27
|
+
.system("Answer concisely.")
|
|
28
|
+
.user("Write one line about TypeScript.")
|
|
29
|
+
.build()
|
|
89
30
|
);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
You can also stream structured JSON for a specific customer:
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
import { z } from "zod";
|
|
96
|
-
import { ModelRelay, outputFormatFromZod } from "@modelrelay/sdk";
|
|
97
31
|
|
|
98
|
-
|
|
99
|
-
const customer = mr.forCustomer("cust_abc123");
|
|
100
|
-
|
|
101
|
-
const schema = z.object({
|
|
102
|
-
summary: z.string(),
|
|
103
|
-
highlights: z.array(z.string()),
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const req = customer.responses
|
|
107
|
-
.new()
|
|
108
|
-
.outputFormat(outputFormatFromZod(schema))
|
|
109
|
-
.system("You are a helpful assistant.")
|
|
110
|
-
.user("Summarize Q4 results")
|
|
111
|
-
.build();
|
|
112
|
-
|
|
113
|
-
const stream = await customer.responses.streamJSON<z.infer<typeof schema>>(req);
|
|
114
|
-
for await (const event of stream) {
|
|
115
|
-
if (event.type === "completion") {
|
|
116
|
-
console.log(event.payload);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
You can also pass a single object to `textForCustomer`:
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
const text = await mr.responses.textForCustomer({
|
|
125
|
-
customerId: "cust_abc123",
|
|
126
|
-
system: "You are a helpful assistant.",
|
|
127
|
-
user: "Summarize Q4 results",
|
|
128
|
-
});
|
|
32
|
+
console.log(response.text());
|
|
129
33
|
```
|
|
130
34
|
|
|
131
|
-
## Workflow Runs (workflow.v0)
|
|
132
|
-
|
|
133
|
-
```ts
|
|
134
|
-
import {
|
|
135
|
-
ModelRelay,
|
|
136
|
-
type LLMResponsesBindingV0,
|
|
137
|
-
parseNodeId,
|
|
138
|
-
parseOutputName,
|
|
139
|
-
parseSecretKey,
|
|
140
|
-
workflowV0,
|
|
141
|
-
} from "@modelrelay/sdk";
|
|
142
|
-
|
|
143
|
-
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
144
|
-
|
|
145
|
-
const spec = workflowV0()
|
|
146
|
-
.name("multi_agent_v0_example")
|
|
147
|
-
.execution({ max_parallelism: 3, node_timeout_ms: 20_000, run_timeout_ms: 30_000 })
|
|
148
|
-
.llmResponses(parseNodeId("agent_a"), {
|
|
149
|
-
model: "claude-sonnet-4-20250514",
|
|
150
|
-
input: [
|
|
151
|
-
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent A." }] },
|
|
152
|
-
{ type: "message", role: "user", content: [{ type: "text", text: "Write 3 ideas for a landing page." }] },
|
|
153
|
-
],
|
|
154
|
-
})
|
|
155
|
-
.llmResponses(parseNodeId("agent_b"), {
|
|
156
|
-
model: "claude-sonnet-4-20250514",
|
|
157
|
-
input: [
|
|
158
|
-
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent B." }] },
|
|
159
|
-
{ type: "message", role: "user", content: [{ type: "text", text: "Write 3 objections a user might have." }] },
|
|
160
|
-
],
|
|
161
|
-
})
|
|
162
|
-
.llmResponses(parseNodeId("agent_c"), {
|
|
163
|
-
model: "claude-sonnet-4-20250514",
|
|
164
|
-
input: [
|
|
165
|
-
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent C." }] },
|
|
166
|
-
{ type: "message", role: "user", content: [{ type: "text", text: "Write 3 alternative headlines." }] },
|
|
167
|
-
],
|
|
168
|
-
})
|
|
169
|
-
.joinAll(parseNodeId("join"))
|
|
170
|
-
.llmResponses(
|
|
171
|
-
parseNodeId("aggregate"),
|
|
172
|
-
{
|
|
173
|
-
model: "claude-sonnet-4-20250514",
|
|
174
|
-
input: [
|
|
175
|
-
{
|
|
176
|
-
type: "message",
|
|
177
|
-
role: "system",
|
|
178
|
-
content: [{ type: "text", text: "Synthesize the best answer from the following agent outputs (JSON)." }],
|
|
179
|
-
},
|
|
180
|
-
{ type: "message", role: "user", content: [{ type: "text", text: "" }] }, // overwritten by bindings
|
|
181
|
-
],
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
// Bind the join output into the aggregator prompt (fan-in).
|
|
185
|
-
bindings: [
|
|
186
|
-
{
|
|
187
|
-
from: parseNodeId("join"),
|
|
188
|
-
to: "/input/1/content/0/text",
|
|
189
|
-
encoding: "json_string",
|
|
190
|
-
} satisfies LLMResponsesBindingV0,
|
|
191
|
-
],
|
|
192
|
-
},
|
|
193
|
-
)
|
|
194
|
-
.edge(parseNodeId("agent_a"), parseNodeId("join"))
|
|
195
|
-
.edge(parseNodeId("agent_b"), parseNodeId("join"))
|
|
196
|
-
.edge(parseNodeId("agent_c"), parseNodeId("join"))
|
|
197
|
-
.edge(parseNodeId("join"), parseNodeId("aggregate"))
|
|
198
|
-
.output(parseOutputName("result"), parseNodeId("aggregate"))
|
|
199
|
-
.build();
|
|
200
|
-
|
|
201
|
-
const { run_id } = await mr.runs.create(spec);
|
|
202
|
-
|
|
203
|
-
const events = await mr.runs.events(run_id);
|
|
204
|
-
for await (const ev of events) {
|
|
205
|
-
if (ev.type === "run_completed") {
|
|
206
|
-
const status = await mr.runs.get(run_id);
|
|
207
|
-
console.log("outputs:", status.outputs);
|
|
208
|
-
console.log("cost_summary:", status.cost_summary);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
See the full example in `sdk/ts/examples/workflows_multi_agent.ts`.
|
|
214
|
-
|
|
215
35
|
## Chat-Like Text Helpers
|
|
216
36
|
|
|
217
|
-
For the most common path (**system + user → assistant text**):
|
|
37
|
+
For the most common path (**system + user → assistant text**), use the built-in convenience:
|
|
218
38
|
|
|
219
39
|
```ts
|
|
220
40
|
const text = await mr.responses.text(
|
|
@@ -228,14 +48,16 @@ console.log(text);
|
|
|
228
48
|
For customer-attributed requests where the backend selects the model:
|
|
229
49
|
|
|
230
50
|
```ts
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
"
|
|
234
|
-
"
|
|
51
|
+
const customer = mr.forCustomer("customer-123");
|
|
52
|
+
const text = await customer.responses.text(
|
|
53
|
+
"You are a helpful assistant.",
|
|
54
|
+
"Summarize Q4 results",
|
|
235
55
|
);
|
|
236
56
|
```
|
|
237
57
|
|
|
238
|
-
|
|
58
|
+
## Streaming
|
|
59
|
+
|
|
60
|
+
Stream text deltas for real-time output:
|
|
239
61
|
|
|
240
62
|
```ts
|
|
241
63
|
const deltas = await mr.responses.streamTextDeltas(
|
|
@@ -248,14 +70,31 @@ for await (const delta of deltas) {
|
|
|
248
70
|
}
|
|
249
71
|
```
|
|
250
72
|
|
|
73
|
+
For full control, stream typed events:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const req = mr.responses
|
|
77
|
+
.new()
|
|
78
|
+
.model("claude-sonnet-4-20250514")
|
|
79
|
+
.user("Hello")
|
|
80
|
+
.build();
|
|
81
|
+
|
|
82
|
+
const stream = await mr.responses.stream(req);
|
|
83
|
+
|
|
84
|
+
for await (const event of stream) {
|
|
85
|
+
if (event.type === "message_delta" && event.textDelta) {
|
|
86
|
+
process.stdout.write(event.textDelta);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
251
91
|
## Structured Outputs with Zod
|
|
252
92
|
|
|
93
|
+
Get typed, validated responses from the model:
|
|
94
|
+
|
|
253
95
|
```ts
|
|
254
|
-
import { ModelRelay, parseSecretKey } from "@modelrelay/sdk";
|
|
255
96
|
import { z } from "zod";
|
|
256
97
|
|
|
257
|
-
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
258
|
-
|
|
259
98
|
const Person = z.object({
|
|
260
99
|
name: z.string(),
|
|
261
100
|
age: z.number(),
|
|
@@ -270,16 +109,11 @@ const result = await mr.responses.structured(
|
|
|
270
109
|
console.log(result.value); // { name: "John Doe", age: 30 }
|
|
271
110
|
```
|
|
272
111
|
|
|
273
|
-
|
|
112
|
+
### Streaming Structured Outputs
|
|
274
113
|
|
|
275
114
|
Build progressive UIs that render fields as they complete:
|
|
276
115
|
|
|
277
116
|
```ts
|
|
278
|
-
import { ModelRelay, parseSecretKey } from "@modelrelay/sdk";
|
|
279
|
-
import { z } from "zod";
|
|
280
|
-
|
|
281
|
-
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
282
|
-
|
|
283
117
|
const Article = z.object({
|
|
284
118
|
title: z.string(),
|
|
285
119
|
summary: z.string(),
|
|
@@ -292,15 +126,12 @@ const stream = await mr.responses.streamStructured(
|
|
|
292
126
|
);
|
|
293
127
|
|
|
294
128
|
for await (const event of stream) {
|
|
295
|
-
// Render fields as soon as they're complete
|
|
296
129
|
if (event.completeFields.has("title")) {
|
|
297
130
|
renderTitle(event.payload.title); // Safe to display
|
|
298
131
|
}
|
|
299
132
|
if (event.completeFields.has("summary")) {
|
|
300
133
|
renderSummary(event.payload.summary);
|
|
301
134
|
}
|
|
302
|
-
|
|
303
|
-
// Show streaming preview of incomplete fields
|
|
304
135
|
if (!event.completeFields.has("body")) {
|
|
305
136
|
renderBodyPreview(event.payload.body + "▋");
|
|
306
137
|
}
|
|
@@ -309,7 +140,7 @@ for await (const event of stream) {
|
|
|
309
140
|
|
|
310
141
|
## Customer-Attributed Requests
|
|
311
142
|
|
|
312
|
-
For metered billing, use `customerId()
|
|
143
|
+
For metered billing, use `customerId()`. The customer's tier determines the model, so `model()` can be omitted:
|
|
313
144
|
|
|
314
145
|
```ts
|
|
315
146
|
const req = mr.responses
|
|
@@ -321,6 +152,16 @@ const req = mr.responses
|
|
|
321
152
|
const stream = await mr.responses.stream(req);
|
|
322
153
|
```
|
|
323
154
|
|
|
155
|
+
Or use the convenience method:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const text = await mr.responses.textForCustomer(
|
|
159
|
+
"customer-123",
|
|
160
|
+
"Answer concisely.",
|
|
161
|
+
"Say hi.",
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
324
165
|
## Customer Management (Backend)
|
|
325
166
|
|
|
326
167
|
```ts
|
|
@@ -341,6 +182,92 @@ const session = await mr.customers.createCheckoutSession(customer.id, {
|
|
|
341
182
|
const status = await mr.customers.getSubscription(customer.id);
|
|
342
183
|
```
|
|
343
184
|
|
|
185
|
+
## Workflow Runs
|
|
186
|
+
|
|
187
|
+
Build multi-agent workflows with parallel execution:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { workflowV0, parseNodeId, parseOutputName, type LLMResponsesBindingV0 } from "@modelrelay/sdk";
|
|
191
|
+
|
|
192
|
+
const spec = workflowV0()
|
|
193
|
+
.name("multi_agent_example")
|
|
194
|
+
.execution({ max_parallelism: 3, node_timeout_ms: 20_000, run_timeout_ms: 30_000 })
|
|
195
|
+
.llmResponses(parseNodeId("agent_a"), {
|
|
196
|
+
model: "claude-sonnet-4-20250514",
|
|
197
|
+
input: [
|
|
198
|
+
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent A." }] },
|
|
199
|
+
{ type: "message", role: "user", content: [{ type: "text", text: "Analyze the question." }] },
|
|
200
|
+
],
|
|
201
|
+
})
|
|
202
|
+
.llmResponses(parseNodeId("agent_b"), {
|
|
203
|
+
model: "claude-sonnet-4-20250514",
|
|
204
|
+
input: [
|
|
205
|
+
{ type: "message", role: "system", content: [{ type: "text", text: "You are Agent B." }] },
|
|
206
|
+
{ type: "message", role: "user", content: [{ type: "text", text: "Find edge cases." }] },
|
|
207
|
+
],
|
|
208
|
+
})
|
|
209
|
+
.joinAll(parseNodeId("join"))
|
|
210
|
+
.llmResponses(
|
|
211
|
+
parseNodeId("aggregate"),
|
|
212
|
+
{
|
|
213
|
+
model: "claude-sonnet-4-20250514",
|
|
214
|
+
input: [
|
|
215
|
+
{ type: "message", role: "system", content: [{ type: "text", text: "Synthesize the best answer." }] },
|
|
216
|
+
{ type: "message", role: "user", content: [{ type: "text", text: "" }] },
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
bindings: [
|
|
221
|
+
{ from: parseNodeId("join"), to: "/input/1/content/0/text", encoding: "json_string" } satisfies LLMResponsesBindingV0,
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
.edge(parseNodeId("agent_a"), parseNodeId("join"))
|
|
226
|
+
.edge(parseNodeId("agent_b"), parseNodeId("join"))
|
|
227
|
+
.edge(parseNodeId("join"), parseNodeId("aggregate"))
|
|
228
|
+
.output(parseOutputName("result"), parseNodeId("aggregate"))
|
|
229
|
+
.build();
|
|
230
|
+
|
|
231
|
+
const { run_id } = await mr.runs.create(spec);
|
|
232
|
+
|
|
233
|
+
for await (const ev of await mr.runs.events(run_id)) {
|
|
234
|
+
if (ev.type === "run_completed") {
|
|
235
|
+
const status = await mr.runs.get(run_id);
|
|
236
|
+
console.log("outputs:", status.outputs);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Token Providers (Advanced)
|
|
242
|
+
|
|
243
|
+
For automatic bearer token management in data-plane calls:
|
|
244
|
+
|
|
245
|
+
### Secret key → customer bearer token
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { CustomerTokenProvider, ModelRelay } from "@modelrelay/sdk";
|
|
249
|
+
|
|
250
|
+
const tokenProvider = new CustomerTokenProvider({
|
|
251
|
+
secretKey: process.env.MODELRELAY_API_KEY!,
|
|
252
|
+
request: { projectId: "proj_...", customerId: "cust_..." },
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const mr = new ModelRelay({ tokenProvider });
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### OIDC exchange
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { ModelRelay, OIDCExchangeTokenProvider, parseSecretKey } from "@modelrelay/sdk";
|
|
262
|
+
|
|
263
|
+
const tokenProvider = new OIDCExchangeTokenProvider({
|
|
264
|
+
apiKey: parseSecretKey(process.env.MODELRELAY_API_KEY!),
|
|
265
|
+
idTokenProvider: async () => process.env.OIDC_ID_TOKEN!,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const mr = new ModelRelay({ tokenProvider });
|
|
269
|
+
```
|
|
270
|
+
|
|
344
271
|
## Configuration
|
|
345
272
|
|
|
346
273
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -818,7 +818,7 @@ function isTokenReusable(token) {
|
|
|
818
818
|
// package.json
|
|
819
819
|
var package_default = {
|
|
820
820
|
name: "@modelrelay/sdk",
|
|
821
|
-
version: "1.3.
|
|
821
|
+
version: "1.3.3",
|
|
822
822
|
description: "TypeScript SDK for the ModelRelay API",
|
|
823
823
|
type: "module",
|
|
824
824
|
main: "dist/index.cjs",
|
package/dist/index.js
CHANGED
|
@@ -688,7 +688,7 @@ function isTokenReusable(token) {
|
|
|
688
688
|
// package.json
|
|
689
689
|
var package_default = {
|
|
690
690
|
name: "@modelrelay/sdk",
|
|
691
|
-
version: "1.3.
|
|
691
|
+
version: "1.3.3",
|
|
692
692
|
description: "TypeScript SDK for the ModelRelay API",
|
|
693
693
|
type: "module",
|
|
694
694
|
main: "dist/index.cjs",
|