@resultdev/sdk 0.2.0 → 0.3.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/CHANGELOG.md +32 -0
- package/dist/{client-Z0zUHGE3.d.ts → client-CJv_awPt.d.cts} +60 -30
- package/dist/{client-DeE086La.d.cts → client-DrsZ1qIm.d.ts} +60 -30
- package/dist/index.cjs +161 -19
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +161 -19
- package/dist/{middleware-TGaDOhE9.d.ts → middleware-BJl9JRg3.d.ts} +1 -1
- package/dist/{middleware-BBY_6hyx.d.cts → middleware-C1Z6aMvn.d.cts} +1 -1
- package/dist/ssr/middleware.d.cts +2 -2
- package/dist/ssr/middleware.d.ts +2 -2
- package/dist/ssr.cjs +161 -19
- package/dist/ssr.d.cts +4 -4
- package/dist/ssr.d.ts +4 -4
- package/dist/ssr.js +161 -19
- package/dist/{types-CbsaMttt.d.cts → types-DSVtGaIA.d.cts} +9 -1
- package/dist/{types-CbsaMttt.d.ts → types-DSVtGaIA.d.ts} +9 -1
- package/package.json +1 -1
package/dist/ssr.js
CHANGED
|
@@ -1159,6 +1159,70 @@ var HttpClient = class {
|
|
|
1159
1159
|
};
|
|
1160
1160
|
|
|
1161
1161
|
// src/modules/ai.ts
|
|
1162
|
+
var SUPPORTED_AUDIO_FORMATS = [
|
|
1163
|
+
"wav",
|
|
1164
|
+
"mp3",
|
|
1165
|
+
"aiff",
|
|
1166
|
+
"aac",
|
|
1167
|
+
"ogg",
|
|
1168
|
+
"flac",
|
|
1169
|
+
"m4a"
|
|
1170
|
+
];
|
|
1171
|
+
var MULTIMODAL_MODEL = "google/gemini-2.5-flash";
|
|
1172
|
+
function summarizeContent(messages) {
|
|
1173
|
+
const summary = {
|
|
1174
|
+
hasImage: false,
|
|
1175
|
+
hasAudio: false,
|
|
1176
|
+
badAudioFormats: []
|
|
1177
|
+
};
|
|
1178
|
+
for (const message of messages) {
|
|
1179
|
+
if (message.images?.length) {
|
|
1180
|
+
summary.hasImage = true;
|
|
1181
|
+
}
|
|
1182
|
+
if (!Array.isArray(message.content)) {
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
for (const part of message.content) {
|
|
1186
|
+
if (part?.type === "image_url") {
|
|
1187
|
+
summary.hasImage = true;
|
|
1188
|
+
} else if (part?.type === "input_audio") {
|
|
1189
|
+
summary.hasAudio = true;
|
|
1190
|
+
const format2 = part.input_audio?.format;
|
|
1191
|
+
if (format2 && !SUPPORTED_AUDIO_FORMATS.includes(format2)) {
|
|
1192
|
+
summary.badAudioFormats.push(format2);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return summary;
|
|
1198
|
+
}
|
|
1199
|
+
function assertSupportedContent(model, summary) {
|
|
1200
|
+
if (summary.badAudioFormats.length) {
|
|
1201
|
+
throw new ResultError(
|
|
1202
|
+
`Audio format "${summary.badAudioFormats[0]}" is not supported. Supported formats: ${SUPPORTED_AUDIO_FORMATS.join(", ")}. Browser MediaRecorder produces webm/opus, which the gateway rejects - capture PCM with the Web Audio API and encode a 16 kHz mono WAV instead.`,
|
|
1203
|
+
400,
|
|
1204
|
+
"AI_INVALID_INPUT"
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
if (summary.hasAudio && model.startsWith("openai/")) {
|
|
1208
|
+
throw new ResultError(
|
|
1209
|
+
`Model "${model}" does not accept audio input on this gateway. Use ${MULTIMODAL_MODEL} (verified for audio).`,
|
|
1210
|
+
400,
|
|
1211
|
+
"AI_INVALID_INPUT"
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function enrichUpstreamError(error, summary) {
|
|
1216
|
+
if (!(error instanceof ResultError) || error.error !== "AI_UPSTREAM_UNAVAILABLE" || !summary.hasImage && !summary.hasAudio) {
|
|
1217
|
+
return error;
|
|
1218
|
+
}
|
|
1219
|
+
const kind = summary.hasImage && summary.hasAudio ? "image and audio" : summary.hasImage ? "image" : "audio";
|
|
1220
|
+
return new ResultError(
|
|
1221
|
+
`${error.message} This request included ${kind} input; if the error persists, use ${MULTIMODAL_MODEL} (verified for vision and audio).`,
|
|
1222
|
+
error.statusCode,
|
|
1223
|
+
error.error
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1162
1226
|
var AI = class {
|
|
1163
1227
|
chat;
|
|
1164
1228
|
images;
|
|
@@ -1181,6 +1245,8 @@ var ChatCompletions = class {
|
|
|
1181
1245
|
}
|
|
1182
1246
|
http;
|
|
1183
1247
|
async create(params) {
|
|
1248
|
+
const contentSummary = summarizeContent(params.messages ?? []);
|
|
1249
|
+
assertSupportedContent(params.model, contentSummary);
|
|
1184
1250
|
const backendParams = {
|
|
1185
1251
|
model: params.model,
|
|
1186
1252
|
messages: params.messages,
|
|
@@ -1209,15 +1275,28 @@ var ChatCompletions = class {
|
|
|
1209
1275
|
}
|
|
1210
1276
|
);
|
|
1211
1277
|
if (!response2.ok) {
|
|
1212
|
-
|
|
1213
|
-
|
|
1278
|
+
let body = null;
|
|
1279
|
+
try {
|
|
1280
|
+
body = await response2.json();
|
|
1281
|
+
} catch {
|
|
1282
|
+
}
|
|
1283
|
+
throw enrichUpstreamError(
|
|
1284
|
+
new ResultError(
|
|
1285
|
+
body?.message || body?.error || "Stream request failed",
|
|
1286
|
+
body?.statusCode ?? response2.status,
|
|
1287
|
+
body?.error || "AI_STREAM_ERROR"
|
|
1288
|
+
),
|
|
1289
|
+
contentSummary
|
|
1290
|
+
);
|
|
1214
1291
|
}
|
|
1215
1292
|
return this.parseSSEStream(response2, params.model);
|
|
1216
1293
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
backendParams
|
|
1220
|
-
)
|
|
1294
|
+
let response;
|
|
1295
|
+
try {
|
|
1296
|
+
response = await this.http.post("/api/ai/chat/completion", backendParams);
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
throw enrichUpstreamError(error, contentSummary);
|
|
1299
|
+
}
|
|
1221
1300
|
const content = response.text || "";
|
|
1222
1301
|
return {
|
|
1223
1302
|
id: `chatcmpl-${Date.now()}`,
|
|
@@ -1312,6 +1391,37 @@ var ChatCompletions = class {
|
|
|
1312
1391
|
]
|
|
1313
1392
|
};
|
|
1314
1393
|
}
|
|
1394
|
+
if (data.annotations?.length) {
|
|
1395
|
+
yield {
|
|
1396
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1397
|
+
object: "chat.completion.chunk",
|
|
1398
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1399
|
+
model,
|
|
1400
|
+
choices: [
|
|
1401
|
+
{
|
|
1402
|
+
index: 0,
|
|
1403
|
+
delta: {
|
|
1404
|
+
annotations: data.annotations
|
|
1405
|
+
},
|
|
1406
|
+
finish_reason: null
|
|
1407
|
+
}
|
|
1408
|
+
]
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
if (data.tokenUsage) {
|
|
1412
|
+
yield {
|
|
1413
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1414
|
+
object: "chat.completion.chunk",
|
|
1415
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1416
|
+
model,
|
|
1417
|
+
choices: [],
|
|
1418
|
+
usage: {
|
|
1419
|
+
prompt_tokens: data.tokenUsage.promptTokens || 0,
|
|
1420
|
+
completion_tokens: data.tokenUsage.completionTokens || 0,
|
|
1421
|
+
total_tokens: data.tokenUsage.totalTokens || 0
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1315
1425
|
if (data.done) {
|
|
1316
1426
|
reader.releaseLock();
|
|
1317
1427
|
return;
|
|
@@ -2548,13 +2658,13 @@ var Functions = class _Functions {
|
|
|
2548
2658
|
functionsUrl;
|
|
2549
2659
|
constructor(http, functionsUrl) {
|
|
2550
2660
|
this.http = http;
|
|
2551
|
-
this.functionsUrl = functionsUrl
|
|
2661
|
+
this.functionsUrl = functionsUrl;
|
|
2552
2662
|
}
|
|
2553
2663
|
/**
|
|
2554
|
-
* Derive the subhosting URL from the base URL
|
|
2555
|
-
* Rewrites the backend base URL to its functions subhosting URL
|
|
2664
|
+
* Derive the legacy subhosting URL from the base URL
|
|
2556
2665
|
* ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
|
|
2557
|
-
* Only
|
|
2666
|
+
* Only used to recognize when a configured functionsUrl still points at
|
|
2667
|
+
* the local deployment, so in-process dispatch can short-circuit it.
|
|
2558
2668
|
*/
|
|
2559
2669
|
static deriveSubhostingUrl(baseUrl) {
|
|
2560
2670
|
try {
|
|
@@ -2587,11 +2697,13 @@ var Functions = class _Functions {
|
|
|
2587
2697
|
* Invoke an Edge Function.
|
|
2588
2698
|
*
|
|
2589
2699
|
* Dispatch order:
|
|
2590
|
-
* 1. If the platform's in-process dispatch hook is present
|
|
2591
|
-
*
|
|
2592
|
-
*
|
|
2593
|
-
* 2.
|
|
2594
|
-
*
|
|
2700
|
+
* 1. If the platform's in-process dispatch hook is present and no foreign
|
|
2701
|
+
* functionsUrl is configured, call it directly (function-to-function
|
|
2702
|
+
* calls inside the same deployment).
|
|
2703
|
+
* 2. If a custom functionsUrl is configured, try it; fall back to the
|
|
2704
|
+
* proxy path on 404 or network failure.
|
|
2705
|
+
* 3. Otherwise, invoke through the main host's /functions/{slug} proxy.
|
|
2706
|
+
* This route works from browsers too (correct CORS).
|
|
2595
2707
|
*
|
|
2596
2708
|
* @param slug The function slug to invoke
|
|
2597
2709
|
* @param options Request options
|
|
@@ -2600,7 +2712,7 @@ var Functions = class _Functions {
|
|
|
2600
2712
|
const { method = "POST", body, headers = {} } = options;
|
|
2601
2713
|
const dispatch = globalThis.__insforge_dispatch__;
|
|
2602
2714
|
const localFunctionsUrl = _Functions.deriveSubhostingUrl(this.http.baseUrl);
|
|
2603
|
-
if (typeof dispatch === "function" && !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl) {
|
|
2715
|
+
if (typeof dispatch === "function" && (this.functionsUrl === void 0 || !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl)) {
|
|
2604
2716
|
try {
|
|
2605
2717
|
const req = this.buildInProcessRequest(slug, method, body, headers);
|
|
2606
2718
|
const res = await dispatch(req);
|
|
@@ -2635,7 +2747,8 @@ var Functions = class _Functions {
|
|
|
2635
2747
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2636
2748
|
throw error;
|
|
2637
2749
|
}
|
|
2638
|
-
|
|
2750
|
+
const unreachable = error instanceof ResultError && (error.statusCode === 404 || error.statusCode === 0);
|
|
2751
|
+
if (unreachable) {
|
|
2639
2752
|
} else {
|
|
2640
2753
|
return {
|
|
2641
2754
|
data: null,
|
|
@@ -2671,6 +2784,17 @@ var Functions = class _Functions {
|
|
|
2671
2784
|
// src/modules/realtime.ts
|
|
2672
2785
|
var CONNECT_TIMEOUT = 1e4;
|
|
2673
2786
|
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2787
|
+
var CHANNEL_PREFIX = "realtime:";
|
|
2788
|
+
function normalizeChannelMeta(message) {
|
|
2789
|
+
const channel = message?.meta?.channel;
|
|
2790
|
+
if (typeof channel !== "string" || !channel.startsWith(CHANNEL_PREFIX)) {
|
|
2791
|
+
return message;
|
|
2792
|
+
}
|
|
2793
|
+
return {
|
|
2794
|
+
...message,
|
|
2795
|
+
meta: { ...message.meta, channel: channel.slice(CHANNEL_PREFIX.length) }
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2674
2798
|
var Realtime = class {
|
|
2675
2799
|
constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
|
|
2676
2800
|
this.baseUrl = baseUrl;
|
|
@@ -2792,8 +2916,9 @@ var Realtime = class {
|
|
|
2792
2916
|
if (event === "realtime:error") {
|
|
2793
2917
|
return;
|
|
2794
2918
|
}
|
|
2795
|
-
|
|
2796
|
-
this.
|
|
2919
|
+
const normalized = normalizeChannelMeta(message);
|
|
2920
|
+
this.applyPresenceEvent(event, normalized);
|
|
2921
|
+
this.notifyListeners(event, normalized);
|
|
2797
2922
|
};
|
|
2798
2923
|
this.connectionAttempt = { id: attemptId, socket, cancel: fail };
|
|
2799
2924
|
socket.on("connect", onConnect);
|
|
@@ -3041,6 +3166,17 @@ var Realtime = class {
|
|
|
3041
3166
|
this.socket.emit("realtime:unsubscribe", { channel });
|
|
3042
3167
|
}
|
|
3043
3168
|
}
|
|
3169
|
+
/**
|
|
3170
|
+
* Publish an event to a channel.
|
|
3171
|
+
*
|
|
3172
|
+
* Delivery notes (verified against a live backend):
|
|
3173
|
+
* - Payload fields arrive spread onto the received message itself, next to
|
|
3174
|
+
* `meta` - not nested under a `payload` key. Publishing `{ text: "hi" }`
|
|
3175
|
+
* means receivers read `msg.text`.
|
|
3176
|
+
* - The sender receives its own message too. Deduplicate with
|
|
3177
|
+
* `msg.meta.messageId` (or `msg.meta.senderId`) if you also update local
|
|
3178
|
+
* state optimistically.
|
|
3179
|
+
*/
|
|
3044
3180
|
async publish(channel, event, payload) {
|
|
3045
3181
|
if (!this.socket?.connected) {
|
|
3046
3182
|
throw new Error(
|
|
@@ -3049,6 +3185,12 @@ var Realtime = class {
|
|
|
3049
3185
|
}
|
|
3050
3186
|
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
3051
3187
|
}
|
|
3188
|
+
/**
|
|
3189
|
+
* Listen for an event by name, across all subscribed channels.
|
|
3190
|
+
* Filter by channel inside the callback: `msg.meta.channel` always matches
|
|
3191
|
+
* the exact name passed to subscribe() (the SDK strips the server's
|
|
3192
|
+
* transport prefix before delivery).
|
|
3193
|
+
*/
|
|
3052
3194
|
on(event, callback) {
|
|
3053
3195
|
const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
3054
3196
|
listeners.add(callback);
|
|
@@ -498,13 +498,21 @@ interface RealtimeErrorPayload {
|
|
|
498
498
|
message: string;
|
|
499
499
|
}
|
|
500
500
|
interface SocketMessageMeta {
|
|
501
|
-
/**
|
|
501
|
+
/**
|
|
502
|
+
* Present for room broadcasts. Matches the exact channel name passed to
|
|
503
|
+
* subscribe() - the SDK strips the server's transport prefix on delivery.
|
|
504
|
+
*/
|
|
502
505
|
channel?: string;
|
|
503
506
|
messageId: string;
|
|
504
507
|
senderType: SenderType;
|
|
505
508
|
senderId?: string;
|
|
506
509
|
timestamp: string;
|
|
507
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* A received realtime message. The published payload's fields arrive spread
|
|
513
|
+
* onto this object next to `meta` (publish `{ text: "hi" }`, read
|
|
514
|
+
* `msg.text`) - there is no `payload` wrapper key.
|
|
515
|
+
*/
|
|
508
516
|
interface SocketMessage {
|
|
509
517
|
meta: SocketMessageMeta;
|
|
510
518
|
[key: string]: unknown;
|
|
@@ -498,13 +498,21 @@ interface RealtimeErrorPayload {
|
|
|
498
498
|
message: string;
|
|
499
499
|
}
|
|
500
500
|
interface SocketMessageMeta {
|
|
501
|
-
/**
|
|
501
|
+
/**
|
|
502
|
+
* Present for room broadcasts. Matches the exact channel name passed to
|
|
503
|
+
* subscribe() - the SDK strips the server's transport prefix on delivery.
|
|
504
|
+
*/
|
|
502
505
|
channel?: string;
|
|
503
506
|
messageId: string;
|
|
504
507
|
senderType: SenderType;
|
|
505
508
|
senderId?: string;
|
|
506
509
|
timestamp: string;
|
|
507
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* A received realtime message. The published payload's fields arrive spread
|
|
513
|
+
* onto this object next to `meta` (publish `{ text: "hi" }`, read
|
|
514
|
+
* `msg.text`) - there is no `payload` wrapper key.
|
|
515
|
+
*/
|
|
508
516
|
interface SocketMessage {
|
|
509
517
|
meta: SocketMessageMeta;
|
|
510
518
|
[key: string]: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@resultdev/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Result Backend SDK - authentication, database, storage, functions, realtime, AI, email, web analytics and customer support for apps built on Result.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://docs.result.dev",
|