@modelrelay/sdk 0.14.1 → 0.18.0
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 +79 -3
- package/dist/index.cjs +334 -72
- package/dist/index.d.cts +112 -43
- package/dist/index.d.ts +112 -43
- package/dist/index.js +331 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,11 +82,87 @@ const stream = await mr.chat.completions.create(
|
|
|
82
82
|
);
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
### Typed models
|
|
85
|
+
### Typed models and stop reasons
|
|
86
86
|
|
|
87
|
-
- Models
|
|
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
|
-
- Usage backfills `totalTokens` when
|
|
89
|
+
- Usage backfills `totalTokens` when the backend omits it, ensuring consistent accounting.
|
|
90
|
+
|
|
91
|
+
### Structured outputs (`response_format`)
|
|
92
|
+
|
|
93
|
+
Request structured JSON instead of free-form text when the backend supports it:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { ModelRelay, type ResponseFormat } from "@modelrelay/sdk";
|
|
97
|
+
|
|
98
|
+
const mr = new ModelRelay({ key: "mr_sk_..." });
|
|
99
|
+
|
|
100
|
+
const format: ResponseFormat = {
|
|
101
|
+
type: "json_schema",
|
|
102
|
+
json_schema: {
|
|
103
|
+
name: "summary",
|
|
104
|
+
schema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: { headline: { type: "string" } },
|
|
107
|
+
additionalProperties: false,
|
|
108
|
+
},
|
|
109
|
+
strict: true,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const completion = await mr.chat.completions.create(
|
|
114
|
+
{
|
|
115
|
+
model: "gpt-4o-mini",
|
|
116
|
+
messages: [{ role: "user", content: "Summarize ModelRelay" }],
|
|
117
|
+
responseFormat: format,
|
|
118
|
+
stream: false,
|
|
119
|
+
},
|
|
120
|
+
{ stream: false },
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
console.log(completion.content[0]); // JSON string matching your schema
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Structured streaming (NDJSON + response_format)
|
|
127
|
+
|
|
128
|
+
Use the structured streaming contract for `/llm/proxy` to stream schema-valid
|
|
129
|
+
JSON payloads over NDJSON:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
type Item = { id: string; label: string };
|
|
133
|
+
type RecommendationPayload = { items: Item[] };
|
|
134
|
+
|
|
135
|
+
const format: ResponseFormat = {
|
|
136
|
+
type: "json_schema",
|
|
137
|
+
json_schema: {
|
|
138
|
+
name: "recommendations",
|
|
139
|
+
schema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: { items: { type: "array", items: { type: "object" } } },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const stream = await mr.chat.completions.streamJSON<RecommendationPayload>({
|
|
147
|
+
model: "grok-4-1-fast",
|
|
148
|
+
messages: [{ role: "user", content: "Recommend items for my user" }],
|
|
149
|
+
responseFormat: format,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
for await (const evt of stream) {
|
|
153
|
+
if (evt.type === "update") {
|
|
154
|
+
// Progressive UI: evt.payload is a partial but schema-valid payload.
|
|
155
|
+
renderPartial(evt.payload.items);
|
|
156
|
+
}
|
|
157
|
+
if (evt.type === "completion") {
|
|
158
|
+
renderFinal(evt.payload.items);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Prefer a single blocking result but still want structured validation?
|
|
163
|
+
const final = await stream.collect();
|
|
164
|
+
console.log(final.items.length);
|
|
165
|
+
```
|
|
90
166
|
|
|
91
167
|
### Telemetry & metrics hooks
|
|
92
168
|
|
package/dist/index.cjs
CHANGED
|
@@ -33,10 +33,10 @@ __export(index_exports, {
|
|
|
33
33
|
ErrorCodes: () => ErrorCodes,
|
|
34
34
|
ModelRelay: () => ModelRelay,
|
|
35
35
|
ModelRelayError: () => ModelRelayError,
|
|
36
|
-
|
|
37
|
-
Providers: () => Providers,
|
|
36
|
+
ResponseFormatTypes: () => ResponseFormatTypes,
|
|
38
37
|
SDK_VERSION: () => SDK_VERSION,
|
|
39
38
|
StopReasons: () => StopReasons,
|
|
39
|
+
StructuredJSONStream: () => StructuredJSONStream,
|
|
40
40
|
TiersClient: () => TiersClient,
|
|
41
41
|
ToolArgsError: () => ToolArgsError,
|
|
42
42
|
ToolCallAccumulator: () => ToolCallAccumulator,
|
|
@@ -44,6 +44,7 @@ __export(index_exports, {
|
|
|
44
44
|
ToolRegistry: () => ToolRegistry,
|
|
45
45
|
ToolTypes: () => ToolTypes,
|
|
46
46
|
TransportError: () => TransportError,
|
|
47
|
+
WebToolModes: () => WebToolModes,
|
|
47
48
|
assistantMessageWithToolCalls: () => assistantMessageWithToolCalls,
|
|
48
49
|
createAccessTokenAuth: () => createAccessTokenAuth,
|
|
49
50
|
createApiKeyAuth: () => createApiKeyAuth,
|
|
@@ -68,12 +69,10 @@ __export(index_exports, {
|
|
|
68
69
|
mergeTrace: () => mergeTrace,
|
|
69
70
|
modelToString: () => modelToString,
|
|
70
71
|
normalizeModelId: () => normalizeModelId,
|
|
71
|
-
normalizeProvider: () => normalizeProvider,
|
|
72
72
|
normalizeStopReason: () => normalizeStopReason,
|
|
73
73
|
parseErrorResponse: () => parseErrorResponse,
|
|
74
74
|
parseToolArgs: () => parseToolArgs,
|
|
75
75
|
parseToolArgsRaw: () => parseToolArgsRaw,
|
|
76
|
-
providerToString: () => providerToString,
|
|
77
76
|
respondToToolCall: () => respondToToolCall,
|
|
78
77
|
stopReasonToString: () => stopReasonToString,
|
|
79
78
|
toolChoiceAuto: () => toolChoiceAuto,
|
|
@@ -348,7 +347,7 @@ function isTokenReusable(token) {
|
|
|
348
347
|
// package.json
|
|
349
348
|
var package_default = {
|
|
350
349
|
name: "@modelrelay/sdk",
|
|
351
|
-
version: "0.
|
|
350
|
+
version: "0.18.0",
|
|
352
351
|
description: "TypeScript SDK for the ModelRelay API",
|
|
353
352
|
type: "module",
|
|
354
353
|
main: "dist/index.cjs",
|
|
@@ -407,30 +406,6 @@ var StopReasons = {
|
|
|
407
406
|
Incomplete: "incomplete",
|
|
408
407
|
Unknown: "unknown"
|
|
409
408
|
};
|
|
410
|
-
var Providers = {
|
|
411
|
-
OpenAI: "openai",
|
|
412
|
-
Anthropic: "anthropic",
|
|
413
|
-
XAI: "xai",
|
|
414
|
-
GoogleAIStudio: "google-ai-studio",
|
|
415
|
-
Echo: "echo"
|
|
416
|
-
};
|
|
417
|
-
var Models = {
|
|
418
|
-
// OpenAI models (provider-agnostic identifiers)
|
|
419
|
-
Gpt4o: "gpt-4o",
|
|
420
|
-
Gpt4oMini: "gpt-4o-mini",
|
|
421
|
-
Gpt51: "gpt-5.1",
|
|
422
|
-
// Anthropic models (provider-agnostic identifiers)
|
|
423
|
-
Claude35HaikuLatest: "claude-3-5-haiku-latest",
|
|
424
|
-
Claude35SonnetLatest: "claude-3-5-sonnet-latest",
|
|
425
|
-
ClaudeOpus45: "claude-opus-4-5",
|
|
426
|
-
Claude35Haiku: "claude-3.5-haiku",
|
|
427
|
-
// xAI / Grok models
|
|
428
|
-
Grok2: "grok-2",
|
|
429
|
-
Grok4_1FastNonReasoning: "grok-4-1-fast-non-reasoning",
|
|
430
|
-
Grok4_1FastReasoning: "grok-4-1-fast-reasoning",
|
|
431
|
-
// Internal echo model for testing.
|
|
432
|
-
Echo1: "echo-1"
|
|
433
|
-
};
|
|
434
409
|
function createUsage(inputTokens, outputTokens, totalTokens) {
|
|
435
410
|
return {
|
|
436
411
|
inputTokens,
|
|
@@ -441,14 +416,24 @@ function createUsage(inputTokens, outputTokens, totalTokens) {
|
|
|
441
416
|
var ToolTypes = {
|
|
442
417
|
Function: "function",
|
|
443
418
|
Web: "web",
|
|
419
|
+
WebSearch: "web_search",
|
|
444
420
|
XSearch: "x_search",
|
|
445
421
|
CodeExecution: "code_execution"
|
|
446
422
|
};
|
|
423
|
+
var WebToolModes = {
|
|
424
|
+
Search: "search",
|
|
425
|
+
Browse: "browse"
|
|
426
|
+
};
|
|
447
427
|
var ToolChoiceTypes = {
|
|
448
428
|
Auto: "auto",
|
|
449
429
|
Required: "required",
|
|
450
430
|
None: "none"
|
|
451
431
|
};
|
|
432
|
+
var ResponseFormatTypes = {
|
|
433
|
+
Text: "text",
|
|
434
|
+
JsonObject: "json_object",
|
|
435
|
+
JsonSchema: "json_schema"
|
|
436
|
+
};
|
|
452
437
|
function mergeMetrics(base, override) {
|
|
453
438
|
if (!base && !override) return void 0;
|
|
454
439
|
return {
|
|
@@ -482,34 +467,14 @@ function stopReasonToString(value) {
|
|
|
482
467
|
if (typeof value === "string") return value;
|
|
483
468
|
return value.other?.trim() || void 0;
|
|
484
469
|
}
|
|
485
|
-
function normalizeProvider(value) {
|
|
486
|
-
if (value === void 0 || value === null) return void 0;
|
|
487
|
-
const str = String(value).trim();
|
|
488
|
-
if (!str) return void 0;
|
|
489
|
-
const lower = str.toLowerCase();
|
|
490
|
-
for (const p of Object.values(Providers)) {
|
|
491
|
-
if (lower === p) return p;
|
|
492
|
-
}
|
|
493
|
-
return { other: str };
|
|
494
|
-
}
|
|
495
|
-
function providerToString(value) {
|
|
496
|
-
if (!value) return void 0;
|
|
497
|
-
if (typeof value === "string") return value;
|
|
498
|
-
return value.other?.trim() || void 0;
|
|
499
|
-
}
|
|
500
470
|
function normalizeModelId(value) {
|
|
501
471
|
if (value === void 0 || value === null) return void 0;
|
|
502
472
|
const str = String(value).trim();
|
|
503
473
|
if (!str) return void 0;
|
|
504
|
-
|
|
505
|
-
for (const m of Object.values(Models)) {
|
|
506
|
-
if (lower === m) return m;
|
|
507
|
-
}
|
|
508
|
-
return { other: str };
|
|
474
|
+
return str;
|
|
509
475
|
}
|
|
510
476
|
function modelToString(value) {
|
|
511
|
-
|
|
512
|
-
return value.other?.trim() || "";
|
|
477
|
+
return String(value).trim();
|
|
513
478
|
}
|
|
514
479
|
|
|
515
480
|
// src/tools.ts
|
|
@@ -1167,7 +1132,6 @@ var ChatCompletionsClient = class {
|
|
|
1167
1132
|
if (!hasUserMessage(params.messages)) {
|
|
1168
1133
|
throw new ConfigError("at least one user message is required");
|
|
1169
1134
|
}
|
|
1170
|
-
validateRequestModel(params.model);
|
|
1171
1135
|
const authHeaders = await this.auth.authForChat(params.customerId);
|
|
1172
1136
|
const body = buildProxyBody(
|
|
1173
1137
|
params,
|
|
@@ -1181,7 +1145,6 @@ var ChatCompletionsClient = class {
|
|
|
1181
1145
|
const baseContext = {
|
|
1182
1146
|
method: "POST",
|
|
1183
1147
|
path: "/llm/proxy",
|
|
1184
|
-
provider: params.provider,
|
|
1185
1148
|
model: params.model,
|
|
1186
1149
|
requestId
|
|
1187
1150
|
};
|
|
@@ -1231,6 +1194,80 @@ var ChatCompletionsClient = class {
|
|
|
1231
1194
|
trace
|
|
1232
1195
|
);
|
|
1233
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Stream structured JSON responses using the NDJSON contract defined for
|
|
1199
|
+
* /llm/proxy. The request must include a structured responseFormat.
|
|
1200
|
+
*/
|
|
1201
|
+
async streamJSON(params, options = {}) {
|
|
1202
|
+
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
1203
|
+
const trace = mergeTrace(this.trace, options.trace);
|
|
1204
|
+
if (!params?.messages?.length) {
|
|
1205
|
+
throw new ConfigError("at least one message is required");
|
|
1206
|
+
}
|
|
1207
|
+
if (!hasUserMessage(params.messages)) {
|
|
1208
|
+
throw new ConfigError("at least one user message is required");
|
|
1209
|
+
}
|
|
1210
|
+
if (!params.responseFormat || params.responseFormat.type !== "json_object" && params.responseFormat.type !== "json_schema") {
|
|
1211
|
+
throw new ConfigError(
|
|
1212
|
+
"responseFormat with type=json_object or json_schema is required for structured streaming"
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
const authHeaders = await this.auth.authForChat(params.customerId);
|
|
1216
|
+
const body = buildProxyBody(
|
|
1217
|
+
params,
|
|
1218
|
+
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
1219
|
+
);
|
|
1220
|
+
const requestId = params.requestId || options.requestId;
|
|
1221
|
+
const headers = { ...options.headers || {} };
|
|
1222
|
+
if (requestId) {
|
|
1223
|
+
headers[REQUEST_ID_HEADER] = requestId;
|
|
1224
|
+
}
|
|
1225
|
+
const baseContext = {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
path: "/llm/proxy",
|
|
1228
|
+
model: params.model,
|
|
1229
|
+
requestId
|
|
1230
|
+
};
|
|
1231
|
+
const response = await this.http.request("/llm/proxy", {
|
|
1232
|
+
method: "POST",
|
|
1233
|
+
body,
|
|
1234
|
+
headers,
|
|
1235
|
+
apiKey: authHeaders.apiKey,
|
|
1236
|
+
accessToken: authHeaders.accessToken,
|
|
1237
|
+
accept: "application/x-ndjson",
|
|
1238
|
+
raw: true,
|
|
1239
|
+
signal: options.signal,
|
|
1240
|
+
timeoutMs: options.timeoutMs ?? 0,
|
|
1241
|
+
useDefaultTimeout: false,
|
|
1242
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
1243
|
+
retry: options.retry,
|
|
1244
|
+
metrics,
|
|
1245
|
+
trace,
|
|
1246
|
+
context: baseContext
|
|
1247
|
+
});
|
|
1248
|
+
const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
|
|
1249
|
+
if (!response.ok) {
|
|
1250
|
+
throw await parseErrorResponse(response);
|
|
1251
|
+
}
|
|
1252
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
1253
|
+
if (!/application\/(x-)?ndjson/i.test(contentType)) {
|
|
1254
|
+
throw new TransportError(
|
|
1255
|
+
`expected NDJSON structured stream, got Content-Type ${contentType || "missing"}`,
|
|
1256
|
+
{ kind: "request" }
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
const streamContext = {
|
|
1260
|
+
...baseContext,
|
|
1261
|
+
requestId: resolvedRequestId ?? baseContext.requestId
|
|
1262
|
+
};
|
|
1263
|
+
return new StructuredJSONStream(
|
|
1264
|
+
response,
|
|
1265
|
+
resolvedRequestId,
|
|
1266
|
+
streamContext,
|
|
1267
|
+
metrics,
|
|
1268
|
+
trace
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1234
1271
|
};
|
|
1235
1272
|
var ChatCompletionsStream = class {
|
|
1236
1273
|
constructor(response, requestId, context, metrics, trace) {
|
|
@@ -1332,6 +1369,164 @@ var ChatCompletionsStream = class {
|
|
|
1332
1369
|
});
|
|
1333
1370
|
}
|
|
1334
1371
|
};
|
|
1372
|
+
var StructuredJSONStream = class {
|
|
1373
|
+
constructor(response, requestId, context, metrics, trace) {
|
|
1374
|
+
this.closed = false;
|
|
1375
|
+
this.sawTerminal = false;
|
|
1376
|
+
if (!response.body) {
|
|
1377
|
+
throw new ConfigError("streaming response is missing a body");
|
|
1378
|
+
}
|
|
1379
|
+
this.response = response;
|
|
1380
|
+
this.requestId = requestId;
|
|
1381
|
+
this.context = context;
|
|
1382
|
+
this.metrics = metrics;
|
|
1383
|
+
this.trace = trace;
|
|
1384
|
+
}
|
|
1385
|
+
async cancel(reason) {
|
|
1386
|
+
this.closed = true;
|
|
1387
|
+
try {
|
|
1388
|
+
await this.response.body?.cancel(reason);
|
|
1389
|
+
} catch {
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
async *[Symbol.asyncIterator]() {
|
|
1393
|
+
if (this.closed) {
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const body = this.response.body;
|
|
1397
|
+
if (!body) {
|
|
1398
|
+
throw new ConfigError("streaming response is missing a body");
|
|
1399
|
+
}
|
|
1400
|
+
const reader = body.getReader();
|
|
1401
|
+
const decoder = new TextDecoder();
|
|
1402
|
+
let buffer = "";
|
|
1403
|
+
try {
|
|
1404
|
+
while (true) {
|
|
1405
|
+
if (this.closed) {
|
|
1406
|
+
await reader.cancel();
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const { value, done } = await reader.read();
|
|
1410
|
+
if (done) {
|
|
1411
|
+
const { records: records2 } = consumeNDJSONBuffer(buffer, true);
|
|
1412
|
+
for (const line of records2) {
|
|
1413
|
+
const evt = this.parseRecord(line);
|
|
1414
|
+
if (evt) {
|
|
1415
|
+
this.traceStructuredEvent(evt, line);
|
|
1416
|
+
yield evt;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (!this.sawTerminal) {
|
|
1420
|
+
throw new TransportError(
|
|
1421
|
+
"structured stream ended without completion or error",
|
|
1422
|
+
{ kind: "request" }
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1428
|
+
const { records, remainder } = consumeNDJSONBuffer(buffer);
|
|
1429
|
+
buffer = remainder;
|
|
1430
|
+
for (const line of records) {
|
|
1431
|
+
const evt = this.parseRecord(line);
|
|
1432
|
+
if (evt) {
|
|
1433
|
+
this.traceStructuredEvent(evt, line);
|
|
1434
|
+
yield evt;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
this.trace?.streamError?.({ context: this.context, error: err });
|
|
1440
|
+
throw err;
|
|
1441
|
+
} finally {
|
|
1442
|
+
this.closed = true;
|
|
1443
|
+
reader.releaseLock();
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async collect() {
|
|
1447
|
+
let last;
|
|
1448
|
+
for await (const evt of this) {
|
|
1449
|
+
last = evt;
|
|
1450
|
+
if (evt.type === "completion") {
|
|
1451
|
+
return evt.payload;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
throw new TransportError(
|
|
1455
|
+
"structured stream ended without completion or error",
|
|
1456
|
+
{ kind: "request" }
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
parseRecord(line) {
|
|
1460
|
+
let parsed;
|
|
1461
|
+
try {
|
|
1462
|
+
parsed = JSON.parse(line);
|
|
1463
|
+
} catch (err) {
|
|
1464
|
+
throw new TransportError("invalid JSON in structured stream", {
|
|
1465
|
+
kind: "request",
|
|
1466
|
+
cause: err
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1470
|
+
throw new TransportError("structured stream record is not an object", {
|
|
1471
|
+
kind: "request"
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
const obj = parsed;
|
|
1475
|
+
const rawType = String(obj.type || "").trim().toLowerCase();
|
|
1476
|
+
if (!rawType) return null;
|
|
1477
|
+
if (rawType === "start") {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
if (rawType === "error") {
|
|
1481
|
+
this.sawTerminal = true;
|
|
1482
|
+
const status = typeof obj.status === "number" && obj.status > 0 ? obj.status : 500;
|
|
1483
|
+
const message = typeof obj.message === "string" && obj.message.trim() ? obj.message : "structured stream error";
|
|
1484
|
+
const code = typeof obj.code === "string" && obj.code.trim() ? obj.code : void 0;
|
|
1485
|
+
throw new APIError(message, {
|
|
1486
|
+
status,
|
|
1487
|
+
code,
|
|
1488
|
+
requestId: this.requestId
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
if (rawType !== "update" && rawType !== "completion") {
|
|
1492
|
+
return null;
|
|
1493
|
+
}
|
|
1494
|
+
if (obj.payload === void 0 || obj.payload === null) {
|
|
1495
|
+
throw new TransportError(
|
|
1496
|
+
"structured stream record missing payload",
|
|
1497
|
+
{ kind: "request" }
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
if (rawType === "completion") {
|
|
1501
|
+
this.sawTerminal = true;
|
|
1502
|
+
}
|
|
1503
|
+
const event = {
|
|
1504
|
+
type: rawType,
|
|
1505
|
+
// biome-ignore lint/suspicious/noExplicitAny: payload is untyped json
|
|
1506
|
+
payload: obj.payload,
|
|
1507
|
+
requestId: this.requestId
|
|
1508
|
+
};
|
|
1509
|
+
return event;
|
|
1510
|
+
}
|
|
1511
|
+
traceStructuredEvent(evt, raw) {
|
|
1512
|
+
if (!this.trace?.streamEvent) return;
|
|
1513
|
+
const event = {
|
|
1514
|
+
type: "custom",
|
|
1515
|
+
event: "structured",
|
|
1516
|
+
data: { type: evt.type, payload: evt.payload },
|
|
1517
|
+
textDelta: void 0,
|
|
1518
|
+
toolCallDelta: void 0,
|
|
1519
|
+
toolCalls: void 0,
|
|
1520
|
+
responseId: void 0,
|
|
1521
|
+
model: void 0,
|
|
1522
|
+
stopReason: void 0,
|
|
1523
|
+
usage: void 0,
|
|
1524
|
+
requestId: this.requestId,
|
|
1525
|
+
raw
|
|
1526
|
+
};
|
|
1527
|
+
this.trace.streamEvent({ context: this.context, event });
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1335
1530
|
function consumeSSEBuffer(buffer, flush = false) {
|
|
1336
1531
|
const events = [];
|
|
1337
1532
|
let eventName = "";
|
|
@@ -1374,6 +1569,19 @@ function consumeSSEBuffer(buffer, flush = false) {
|
|
|
1374
1569
|
}
|
|
1375
1570
|
return { events, remainder };
|
|
1376
1571
|
}
|
|
1572
|
+
function consumeNDJSONBuffer(buffer, flush = false) {
|
|
1573
|
+
const lines = buffer.split(/\r?\n/);
|
|
1574
|
+
const records = [];
|
|
1575
|
+
const lastIndex = lines.length - 1;
|
|
1576
|
+
const limit = flush ? lines.length : Math.max(0, lastIndex);
|
|
1577
|
+
for (let i = 0; i < limit; i++) {
|
|
1578
|
+
const line = lines[i]?.trim();
|
|
1579
|
+
if (!line) continue;
|
|
1580
|
+
records.push(line);
|
|
1581
|
+
}
|
|
1582
|
+
const remainder = flush ? "" : lines[lastIndex] ?? "";
|
|
1583
|
+
return { records, remainder };
|
|
1584
|
+
}
|
|
1377
1585
|
function mapChatEvent(raw, requestId) {
|
|
1378
1586
|
let parsed = raw.data;
|
|
1379
1587
|
if (raw.data) {
|
|
@@ -1502,7 +1710,6 @@ function normalizeChatResponse(payload, requestId) {
|
|
|
1502
1710
|
const p = payload;
|
|
1503
1711
|
const response = {
|
|
1504
1712
|
id: p?.id,
|
|
1505
|
-
provider: normalizeProvider(p?.provider),
|
|
1506
1713
|
content: Array.isArray(p?.content) ? p.content : p?.content ? [String(p.content)] : [],
|
|
1507
1714
|
stopReason: normalizeStopReason(p?.stop_reason),
|
|
1508
1715
|
model: normalizeModelId(p?.model),
|
|
@@ -1533,19 +1740,6 @@ function normalizeUsage(payload) {
|
|
|
1533
1740
|
const totalTokens = Number(payload.total_tokens ?? 0);
|
|
1534
1741
|
return createUsage(inputTokens, outputTokens, totalTokens || void 0);
|
|
1535
1742
|
}
|
|
1536
|
-
function validateRequestModel(model) {
|
|
1537
|
-
if (model === void 0 || model === null) return;
|
|
1538
|
-
const value = modelToString(model).trim();
|
|
1539
|
-
if (!value) {
|
|
1540
|
-
throw new ConfigError("model id must be a non-empty string when provided");
|
|
1541
|
-
}
|
|
1542
|
-
const knownModels = Object.values(Models);
|
|
1543
|
-
if (!knownModels.includes(value)) {
|
|
1544
|
-
throw new ConfigError(
|
|
1545
|
-
`unsupported model id "${value}". Use one of the SDK Models.* constants or omit model to use the tier's default model.`
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
1743
|
function buildProxyBody(params, metadata) {
|
|
1550
1744
|
const modelValue = params.model ? modelToString(params.model).trim() : "";
|
|
1551
1745
|
const body = {
|
|
@@ -1555,7 +1749,6 @@ function buildProxyBody(params, metadata) {
|
|
|
1555
1749
|
body.model = modelValue;
|
|
1556
1750
|
}
|
|
1557
1751
|
if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
|
|
1558
|
-
if (params.provider) body.provider = providerToString(params.provider);
|
|
1559
1752
|
if (typeof params.temperature === "number")
|
|
1560
1753
|
body.temperature = params.temperature;
|
|
1561
1754
|
if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
|
|
@@ -1563,6 +1756,7 @@ function buildProxyBody(params, metadata) {
|
|
|
1563
1756
|
if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
|
|
1564
1757
|
if (params.tools?.length) body.tools = normalizeTools(params.tools);
|
|
1565
1758
|
if (params.toolChoice) body.tool_choice = normalizeToolChoice(params.toolChoice);
|
|
1759
|
+
if (params.responseFormat) body.response_format = params.responseFormat;
|
|
1566
1760
|
return body;
|
|
1567
1761
|
}
|
|
1568
1762
|
function normalizeMessages(messages) {
|
|
@@ -1742,6 +1936,32 @@ var CustomersClient = class {
|
|
|
1742
1936
|
});
|
|
1743
1937
|
return response.customer;
|
|
1744
1938
|
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Claim a customer by email, setting their external_id.
|
|
1941
|
+
* Used when a customer subscribes via Stripe Checkout (email only) and later
|
|
1942
|
+
* authenticates to the app, needing to link their identity.
|
|
1943
|
+
*
|
|
1944
|
+
* @throws {APIError} with status 404 if customer not found by email
|
|
1945
|
+
* @throws {APIError} with status 409 if customer already claimed or external_id in use
|
|
1946
|
+
*/
|
|
1947
|
+
async claim(request) {
|
|
1948
|
+
this.ensureSecretKey();
|
|
1949
|
+
if (!request.email?.trim()) {
|
|
1950
|
+
throw new ConfigError("email is required");
|
|
1951
|
+
}
|
|
1952
|
+
if (!isValidEmail(request.email)) {
|
|
1953
|
+
throw new ConfigError("invalid email format");
|
|
1954
|
+
}
|
|
1955
|
+
if (!request.external_id?.trim()) {
|
|
1956
|
+
throw new ConfigError("external_id is required");
|
|
1957
|
+
}
|
|
1958
|
+
const response = await this.http.json("/customers/claim", {
|
|
1959
|
+
method: "POST",
|
|
1960
|
+
body: request,
|
|
1961
|
+
apiKey: this.apiKey
|
|
1962
|
+
});
|
|
1963
|
+
return response.customer;
|
|
1964
|
+
}
|
|
1745
1965
|
/**
|
|
1746
1966
|
* Delete a customer by ID.
|
|
1747
1967
|
*/
|
|
@@ -1806,6 +2026,13 @@ var TiersClient = class {
|
|
|
1806
2026
|
);
|
|
1807
2027
|
}
|
|
1808
2028
|
}
|
|
2029
|
+
ensureSecretKey() {
|
|
2030
|
+
if (!this.apiKey || !this.apiKey.startsWith("mr_sk_")) {
|
|
2031
|
+
throw new ConfigError(
|
|
2032
|
+
"Secret key (mr_sk_*) required for checkout operations"
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
1809
2036
|
/**
|
|
1810
2037
|
* List all tiers in the project.
|
|
1811
2038
|
*/
|
|
@@ -1831,6 +2058,42 @@ var TiersClient = class {
|
|
|
1831
2058
|
});
|
|
1832
2059
|
return response.tier;
|
|
1833
2060
|
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Create a Stripe checkout session for a tier (Stripe-first flow).
|
|
2063
|
+
*
|
|
2064
|
+
* This enables users to subscribe before authenticating. After checkout
|
|
2065
|
+
* completes, a customer record is created with the provided email. The
|
|
2066
|
+
* customer can later be linked to an identity via POST /customers/claim.
|
|
2067
|
+
*
|
|
2068
|
+
* Requires a secret key (mr_sk_*).
|
|
2069
|
+
*
|
|
2070
|
+
* @param tierId - The tier ID to create a checkout session for
|
|
2071
|
+
* @param request - Checkout session request with email and redirect URLs
|
|
2072
|
+
* @returns Checkout session with Stripe URL
|
|
2073
|
+
*/
|
|
2074
|
+
async checkout(tierId, request) {
|
|
2075
|
+
this.ensureSecretKey();
|
|
2076
|
+
if (!tierId?.trim()) {
|
|
2077
|
+
throw new ConfigError("tierId is required");
|
|
2078
|
+
}
|
|
2079
|
+
if (!request.email?.trim()) {
|
|
2080
|
+
throw new ConfigError("email is required");
|
|
2081
|
+
}
|
|
2082
|
+
if (!request.success_url?.trim()) {
|
|
2083
|
+
throw new ConfigError("success_url is required");
|
|
2084
|
+
}
|
|
2085
|
+
if (!request.cancel_url?.trim()) {
|
|
2086
|
+
throw new ConfigError("cancel_url is required");
|
|
2087
|
+
}
|
|
2088
|
+
return await this.http.json(
|
|
2089
|
+
`/tiers/${tierId}/checkout`,
|
|
2090
|
+
{
|
|
2091
|
+
method: "POST",
|
|
2092
|
+
apiKey: this.apiKey,
|
|
2093
|
+
body: request
|
|
2094
|
+
}
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
1834
2097
|
};
|
|
1835
2098
|
|
|
1836
2099
|
// src/http.ts
|
|
@@ -2219,10 +2482,10 @@ function resolveBaseUrl(override) {
|
|
|
2219
2482
|
ErrorCodes,
|
|
2220
2483
|
ModelRelay,
|
|
2221
2484
|
ModelRelayError,
|
|
2222
|
-
|
|
2223
|
-
Providers,
|
|
2485
|
+
ResponseFormatTypes,
|
|
2224
2486
|
SDK_VERSION,
|
|
2225
2487
|
StopReasons,
|
|
2488
|
+
StructuredJSONStream,
|
|
2226
2489
|
TiersClient,
|
|
2227
2490
|
ToolArgsError,
|
|
2228
2491
|
ToolCallAccumulator,
|
|
@@ -2230,6 +2493,7 @@ function resolveBaseUrl(override) {
|
|
|
2230
2493
|
ToolRegistry,
|
|
2231
2494
|
ToolTypes,
|
|
2232
2495
|
TransportError,
|
|
2496
|
+
WebToolModes,
|
|
2233
2497
|
assistantMessageWithToolCalls,
|
|
2234
2498
|
createAccessTokenAuth,
|
|
2235
2499
|
createApiKeyAuth,
|
|
@@ -2254,12 +2518,10 @@ function resolveBaseUrl(override) {
|
|
|
2254
2518
|
mergeTrace,
|
|
2255
2519
|
modelToString,
|
|
2256
2520
|
normalizeModelId,
|
|
2257
|
-
normalizeProvider,
|
|
2258
2521
|
normalizeStopReason,
|
|
2259
2522
|
parseErrorResponse,
|
|
2260
2523
|
parseToolArgs,
|
|
2261
2524
|
parseToolArgsRaw,
|
|
2262
|
-
providerToString,
|
|
2263
2525
|
respondToToolCall,
|
|
2264
2526
|
stopReasonToString,
|
|
2265
2527
|
toolChoiceAuto,
|