@modelrelay/sdk 0.30.0 → 1.3.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 +257 -19
- package/dist/index.cjs +7443 -1198
- package/dist/index.d.cts +4465 -515
- package/dist/index.d.ts +4465 -515
- package/dist/index.js +7405 -1193
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -4,17 +4,69 @@
|
|
|
4
4
|
bun add @modelrelay/sdk
|
|
5
5
|
```
|
|
6
6
|
|
|
7
|
-
##
|
|
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)
|
|
8
12
|
|
|
9
13
|
```ts
|
|
10
|
-
import { ModelRelay } from "@modelrelay/sdk";
|
|
14
|
+
import { ModelRelay, OIDCExchangeTokenProvider, parseSecretKey } from "@modelrelay/sdk";
|
|
11
15
|
|
|
12
|
-
const
|
|
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
|
+
```
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
},
|
|
17
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
|
+
});
|
|
52
|
+
|
|
53
|
+
const mr = new ModelRelay({ tokenProvider });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Streaming Responses
|
|
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);
|
|
18
70
|
|
|
19
71
|
for await (const event of stream) {
|
|
20
72
|
if (event.type === "message_delta" && event.textDelta) {
|
|
@@ -23,20 +75,197 @@ for await (const event of stream) {
|
|
|
23
75
|
}
|
|
24
76
|
```
|
|
25
77
|
|
|
78
|
+
## Customer-Scoped Convenience
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { ModelRelay } from "@modelrelay/sdk";
|
|
82
|
+
|
|
83
|
+
const mr = ModelRelay.fromSecretKey("mr_sk_...");
|
|
84
|
+
const customer = mr.forCustomer("cust_abc123");
|
|
85
|
+
|
|
86
|
+
const text = await customer.responses.text(
|
|
87
|
+
"You are a helpful assistant.",
|
|
88
|
+
"Summarize Q4 results",
|
|
89
|
+
);
|
|
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
|
+
|
|
98
|
+
const mr = ModelRelay.fromSecretKey("mr_sk_...");
|
|
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.value);
|
|
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
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
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
|
+
## Chat-Like Text Helpers
|
|
216
|
+
|
|
217
|
+
For the most common path (**system + user → assistant text**):
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const text = await mr.responses.text(
|
|
221
|
+
"claude-sonnet-4-20250514",
|
|
222
|
+
"Answer concisely.",
|
|
223
|
+
"Say hi.",
|
|
224
|
+
);
|
|
225
|
+
console.log(text);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
For customer-attributed requests where the backend selects the model:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
const text = await mr.responses.textForCustomer(
|
|
232
|
+
"customer-123",
|
|
233
|
+
"Answer concisely.",
|
|
234
|
+
"Say hi.",
|
|
235
|
+
);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
To stream only message text deltas:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const deltas = await mr.responses.streamTextDeltas(
|
|
242
|
+
"claude-sonnet-4-20250514",
|
|
243
|
+
"Answer concisely.",
|
|
244
|
+
"Say hi.",
|
|
245
|
+
);
|
|
246
|
+
for await (const delta of deltas) {
|
|
247
|
+
process.stdout.write(delta);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
26
251
|
## Structured Outputs with Zod
|
|
27
252
|
|
|
28
253
|
```ts
|
|
254
|
+
import { ModelRelay, parseSecretKey } from "@modelrelay/sdk";
|
|
29
255
|
import { z } from "zod";
|
|
30
256
|
|
|
257
|
+
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
258
|
+
|
|
31
259
|
const Person = z.object({
|
|
32
260
|
name: z.string(),
|
|
33
261
|
age: z.number(),
|
|
34
262
|
});
|
|
35
263
|
|
|
36
|
-
const result = await mr.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
264
|
+
const result = await mr.responses.structured(
|
|
265
|
+
Person,
|
|
266
|
+
mr.responses.new().model("claude-sonnet-4-20250514").user("Extract: John Doe is 30").build(),
|
|
267
|
+
{ maxRetries: 2 },
|
|
268
|
+
);
|
|
40
269
|
|
|
41
270
|
console.log(result.value); // { name: "John Doe", age: 30 }
|
|
42
271
|
```
|
|
@@ -46,16 +275,21 @@ console.log(result.value); // { name: "John Doe", age: 30 }
|
|
|
46
275
|
Build progressive UIs that render fields as they complete:
|
|
47
276
|
|
|
48
277
|
```ts
|
|
278
|
+
import { ModelRelay, parseSecretKey } from "@modelrelay/sdk";
|
|
279
|
+
import { z } from "zod";
|
|
280
|
+
|
|
281
|
+
const mr = new ModelRelay({ key: parseSecretKey("mr_sk_...") });
|
|
282
|
+
|
|
49
283
|
const Article = z.object({
|
|
50
284
|
title: z.string(),
|
|
51
285
|
summary: z.string(),
|
|
52
286
|
body: z.string(),
|
|
53
287
|
});
|
|
54
288
|
|
|
55
|
-
const stream = await mr.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
289
|
+
const stream = await mr.responses.streamStructured(
|
|
290
|
+
Article,
|
|
291
|
+
mr.responses.new().model("claude-sonnet-4-20250514").user("Write an article about TypeScript").build(),
|
|
292
|
+
);
|
|
59
293
|
|
|
60
294
|
for await (const event of stream) {
|
|
61
295
|
// Render fields as soon as they're complete
|
|
@@ -75,12 +309,16 @@ for await (const event of stream) {
|
|
|
75
309
|
|
|
76
310
|
## Customer-Attributed Requests
|
|
77
311
|
|
|
78
|
-
For metered billing, use `
|
|
312
|
+
For metered billing, use `customerId()` — the customer's tier determines the model and `model` can be omitted:
|
|
79
313
|
|
|
80
314
|
```ts
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
315
|
+
const req = mr.responses
|
|
316
|
+
.new()
|
|
317
|
+
.customerId("customer-123")
|
|
318
|
+
.user("Hello")
|
|
319
|
+
.build();
|
|
320
|
+
|
|
321
|
+
const stream = await mr.responses.stream(req);
|
|
84
322
|
```
|
|
85
323
|
|
|
86
324
|
## Customer Management (Backend)
|
|
@@ -107,7 +345,7 @@ const status = await mr.customers.getSubscription(customer.id);
|
|
|
107
345
|
|
|
108
346
|
```ts
|
|
109
347
|
const mr = new ModelRelay({
|
|
110
|
-
key: "mr_sk_...",
|
|
348
|
+
key: parseSecretKey("mr_sk_..."),
|
|
111
349
|
environment: "production", // or "staging", "sandbox"
|
|
112
350
|
timeoutMs: 30_000,
|
|
113
351
|
retry: { maxAttempts: 3 },
|