@resultdev/sdk 0.2.0 → 0.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/CHANGELOG.md +43 -0
- package/dist/{client-DeE086La.d.cts → client--Xjaaw1l.d.ts} +62 -32
- package/dist/{client-Z0zUHGE3.d.ts → client-BrOD2EO5.d.cts} +62 -32
- package/dist/index.cjs +170 -20
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +170 -20
- package/dist/{middleware-TGaDOhE9.d.ts → middleware-Uh09_lFn.d.ts} +1 -1
- package/dist/{middleware-BBY_6hyx.d.cts → middleware-VCWoai4o.d.cts} +1 -1
- package/dist/ssr/middleware.d.cts +2 -2
- package/dist/ssr/middleware.d.ts +2 -2
- package/dist/ssr.cjs +170 -20
- package/dist/ssr.d.cts +4 -4
- package/dist/ssr.d.ts +4 -4
- package/dist/ssr.js +170 -20
- package/dist/{types-CbsaMttt.d.cts → types-BmU3deRO.d.cts} +12 -3
- package/dist/{types-CbsaMttt.d.ts → types-BmU3deRO.d.ts} +12 -3
- package/package.json +1 -1
package/dist/ssr.cjs
CHANGED
|
@@ -690,7 +690,15 @@ var HttpClient = class {
|
|
|
690
690
|
*/
|
|
691
691
|
constructor(config, tokenManager, logger) {
|
|
692
692
|
this.config = config;
|
|
693
|
-
|
|
693
|
+
if (!config.baseUrl) {
|
|
694
|
+
throw new ResultError(
|
|
695
|
+
"createClient needs a baseUrl, but it was undefined at runtime.",
|
|
696
|
+
0,
|
|
697
|
+
"MISSING_BASE_URL",
|
|
698
|
+
"Pass the NEXT_PUBLIC_BACKEND_URL value from .env.local. In browser code, write process.env.NEXT_PUBLIC_BACKEND_URL as a literal expression (never through an imported process module) so the bundler inlines it, and set it as a deployments build variable before deploying."
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
this.baseUrl = config.baseUrl;
|
|
694
702
|
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
695
703
|
this.anonKey = config.anonKey;
|
|
696
704
|
this.defaultHeaders = {
|
|
@@ -1208,6 +1216,70 @@ var HttpClient = class {
|
|
|
1208
1216
|
};
|
|
1209
1217
|
|
|
1210
1218
|
// src/modules/ai.ts
|
|
1219
|
+
var SUPPORTED_AUDIO_FORMATS = [
|
|
1220
|
+
"wav",
|
|
1221
|
+
"mp3",
|
|
1222
|
+
"aiff",
|
|
1223
|
+
"aac",
|
|
1224
|
+
"ogg",
|
|
1225
|
+
"flac",
|
|
1226
|
+
"m4a"
|
|
1227
|
+
];
|
|
1228
|
+
var MULTIMODAL_MODEL = "google/gemini-2.5-flash";
|
|
1229
|
+
function summarizeContent(messages) {
|
|
1230
|
+
const summary = {
|
|
1231
|
+
hasImage: false,
|
|
1232
|
+
hasAudio: false,
|
|
1233
|
+
badAudioFormats: []
|
|
1234
|
+
};
|
|
1235
|
+
for (const message of messages) {
|
|
1236
|
+
if (message.images?.length) {
|
|
1237
|
+
summary.hasImage = true;
|
|
1238
|
+
}
|
|
1239
|
+
if (!Array.isArray(message.content)) {
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
for (const part of message.content) {
|
|
1243
|
+
if (part?.type === "image_url") {
|
|
1244
|
+
summary.hasImage = true;
|
|
1245
|
+
} else if (part?.type === "input_audio") {
|
|
1246
|
+
summary.hasAudio = true;
|
|
1247
|
+
const format2 = part.input_audio?.format;
|
|
1248
|
+
if (format2 && !SUPPORTED_AUDIO_FORMATS.includes(format2)) {
|
|
1249
|
+
summary.badAudioFormats.push(format2);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return summary;
|
|
1255
|
+
}
|
|
1256
|
+
function assertSupportedContent(model, summary) {
|
|
1257
|
+
if (summary.badAudioFormats.length) {
|
|
1258
|
+
throw new ResultError(
|
|
1259
|
+
`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.`,
|
|
1260
|
+
400,
|
|
1261
|
+
"AI_INVALID_INPUT"
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
if (summary.hasAudio && model.startsWith("openai/")) {
|
|
1265
|
+
throw new ResultError(
|
|
1266
|
+
`Model "${model}" does not accept audio input on this gateway. Use ${MULTIMODAL_MODEL} (verified for audio).`,
|
|
1267
|
+
400,
|
|
1268
|
+
"AI_INVALID_INPUT"
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
function enrichUpstreamError(error, summary) {
|
|
1273
|
+
if (!(error instanceof ResultError) || error.error !== "AI_UPSTREAM_UNAVAILABLE" || !summary.hasImage && !summary.hasAudio) {
|
|
1274
|
+
return error;
|
|
1275
|
+
}
|
|
1276
|
+
const kind = summary.hasImage && summary.hasAudio ? "image and audio" : summary.hasImage ? "image" : "audio";
|
|
1277
|
+
return new ResultError(
|
|
1278
|
+
`${error.message} This request included ${kind} input; if the error persists, use ${MULTIMODAL_MODEL} (verified for vision and audio).`,
|
|
1279
|
+
error.statusCode,
|
|
1280
|
+
error.error
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1211
1283
|
var AI = class {
|
|
1212
1284
|
chat;
|
|
1213
1285
|
images;
|
|
@@ -1230,6 +1302,8 @@ var ChatCompletions = class {
|
|
|
1230
1302
|
}
|
|
1231
1303
|
http;
|
|
1232
1304
|
async create(params) {
|
|
1305
|
+
const contentSummary = summarizeContent(params.messages ?? []);
|
|
1306
|
+
assertSupportedContent(params.model, contentSummary);
|
|
1233
1307
|
const backendParams = {
|
|
1234
1308
|
model: params.model,
|
|
1235
1309
|
messages: params.messages,
|
|
@@ -1258,15 +1332,28 @@ var ChatCompletions = class {
|
|
|
1258
1332
|
}
|
|
1259
1333
|
);
|
|
1260
1334
|
if (!response2.ok) {
|
|
1261
|
-
|
|
1262
|
-
|
|
1335
|
+
let body = null;
|
|
1336
|
+
try {
|
|
1337
|
+
body = await response2.json();
|
|
1338
|
+
} catch {
|
|
1339
|
+
}
|
|
1340
|
+
throw enrichUpstreamError(
|
|
1341
|
+
new ResultError(
|
|
1342
|
+
body?.message || body?.error || "Stream request failed",
|
|
1343
|
+
body?.statusCode ?? response2.status,
|
|
1344
|
+
body?.error || "AI_STREAM_ERROR"
|
|
1345
|
+
),
|
|
1346
|
+
contentSummary
|
|
1347
|
+
);
|
|
1263
1348
|
}
|
|
1264
1349
|
return this.parseSSEStream(response2, params.model);
|
|
1265
1350
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
backendParams
|
|
1269
|
-
)
|
|
1351
|
+
let response;
|
|
1352
|
+
try {
|
|
1353
|
+
response = await this.http.post("/api/ai/chat/completion", backendParams);
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
throw enrichUpstreamError(error, contentSummary);
|
|
1356
|
+
}
|
|
1270
1357
|
const content = response.text || "";
|
|
1271
1358
|
return {
|
|
1272
1359
|
id: `chatcmpl-${Date.now()}`,
|
|
@@ -1361,6 +1448,37 @@ var ChatCompletions = class {
|
|
|
1361
1448
|
]
|
|
1362
1449
|
};
|
|
1363
1450
|
}
|
|
1451
|
+
if (data.annotations?.length) {
|
|
1452
|
+
yield {
|
|
1453
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1454
|
+
object: "chat.completion.chunk",
|
|
1455
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1456
|
+
model,
|
|
1457
|
+
choices: [
|
|
1458
|
+
{
|
|
1459
|
+
index: 0,
|
|
1460
|
+
delta: {
|
|
1461
|
+
annotations: data.annotations
|
|
1462
|
+
},
|
|
1463
|
+
finish_reason: null
|
|
1464
|
+
}
|
|
1465
|
+
]
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
if (data.tokenUsage) {
|
|
1469
|
+
yield {
|
|
1470
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1471
|
+
object: "chat.completion.chunk",
|
|
1472
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1473
|
+
model,
|
|
1474
|
+
choices: [],
|
|
1475
|
+
usage: {
|
|
1476
|
+
prompt_tokens: data.tokenUsage.promptTokens || 0,
|
|
1477
|
+
completion_tokens: data.tokenUsage.completionTokens || 0,
|
|
1478
|
+
total_tokens: data.tokenUsage.totalTokens || 0
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1364
1482
|
if (data.done) {
|
|
1365
1483
|
reader.releaseLock();
|
|
1366
1484
|
return;
|
|
@@ -2597,13 +2715,13 @@ var Functions = class _Functions {
|
|
|
2597
2715
|
functionsUrl;
|
|
2598
2716
|
constructor(http, functionsUrl) {
|
|
2599
2717
|
this.http = http;
|
|
2600
|
-
this.functionsUrl = functionsUrl
|
|
2718
|
+
this.functionsUrl = functionsUrl;
|
|
2601
2719
|
}
|
|
2602
2720
|
/**
|
|
2603
|
-
* Derive the subhosting URL from the base URL
|
|
2604
|
-
* Rewrites the backend base URL to its functions subhosting URL
|
|
2721
|
+
* Derive the legacy subhosting URL from the base URL
|
|
2605
2722
|
* ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
|
|
2606
|
-
* Only
|
|
2723
|
+
* Only used to recognize when a configured functionsUrl still points at
|
|
2724
|
+
* the local deployment, so in-process dispatch can short-circuit it.
|
|
2607
2725
|
*/
|
|
2608
2726
|
static deriveSubhostingUrl(baseUrl) {
|
|
2609
2727
|
try {
|
|
@@ -2636,11 +2754,13 @@ var Functions = class _Functions {
|
|
|
2636
2754
|
* Invoke an Edge Function.
|
|
2637
2755
|
*
|
|
2638
2756
|
* Dispatch order:
|
|
2639
|
-
* 1. If the platform's in-process dispatch hook is present
|
|
2640
|
-
*
|
|
2641
|
-
*
|
|
2642
|
-
* 2.
|
|
2643
|
-
*
|
|
2757
|
+
* 1. If the platform's in-process dispatch hook is present and no foreign
|
|
2758
|
+
* functionsUrl is configured, call it directly (function-to-function
|
|
2759
|
+
* calls inside the same deployment).
|
|
2760
|
+
* 2. If a custom functionsUrl is configured, try it; fall back to the
|
|
2761
|
+
* proxy path on 404 or network failure.
|
|
2762
|
+
* 3. Otherwise, invoke through the main host's /functions/{slug} proxy.
|
|
2763
|
+
* This route works from browsers too (correct CORS).
|
|
2644
2764
|
*
|
|
2645
2765
|
* @param slug The function slug to invoke
|
|
2646
2766
|
* @param options Request options
|
|
@@ -2649,7 +2769,7 @@ var Functions = class _Functions {
|
|
|
2649
2769
|
const { method = "POST", body, headers = {} } = options;
|
|
2650
2770
|
const dispatch = globalThis.__insforge_dispatch__;
|
|
2651
2771
|
const localFunctionsUrl = _Functions.deriveSubhostingUrl(this.http.baseUrl);
|
|
2652
|
-
if (typeof dispatch === "function" && !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl) {
|
|
2772
|
+
if (typeof dispatch === "function" && (this.functionsUrl === void 0 || !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl)) {
|
|
2653
2773
|
try {
|
|
2654
2774
|
const req = this.buildInProcessRequest(slug, method, body, headers);
|
|
2655
2775
|
const res = await dispatch(req);
|
|
@@ -2684,7 +2804,8 @@ var Functions = class _Functions {
|
|
|
2684
2804
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2685
2805
|
throw error;
|
|
2686
2806
|
}
|
|
2687
|
-
|
|
2807
|
+
const unreachable = error instanceof ResultError && (error.statusCode === 404 || error.statusCode === 0);
|
|
2808
|
+
if (unreachable) {
|
|
2688
2809
|
} else {
|
|
2689
2810
|
return {
|
|
2690
2811
|
data: null,
|
|
@@ -2720,6 +2841,17 @@ var Functions = class _Functions {
|
|
|
2720
2841
|
// src/modules/realtime.ts
|
|
2721
2842
|
var CONNECT_TIMEOUT = 1e4;
|
|
2722
2843
|
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2844
|
+
var CHANNEL_PREFIX = "realtime:";
|
|
2845
|
+
function normalizeChannelMeta(message) {
|
|
2846
|
+
const channel = message?.meta?.channel;
|
|
2847
|
+
if (typeof channel !== "string" || !channel.startsWith(CHANNEL_PREFIX)) {
|
|
2848
|
+
return message;
|
|
2849
|
+
}
|
|
2850
|
+
return {
|
|
2851
|
+
...message,
|
|
2852
|
+
meta: { ...message.meta, channel: channel.slice(CHANNEL_PREFIX.length) }
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2723
2855
|
var Realtime = class {
|
|
2724
2856
|
constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
|
|
2725
2857
|
this.baseUrl = baseUrl;
|
|
@@ -2841,8 +2973,9 @@ var Realtime = class {
|
|
|
2841
2973
|
if (event === "realtime:error") {
|
|
2842
2974
|
return;
|
|
2843
2975
|
}
|
|
2844
|
-
|
|
2845
|
-
this.
|
|
2976
|
+
const normalized = normalizeChannelMeta(message);
|
|
2977
|
+
this.applyPresenceEvent(event, normalized);
|
|
2978
|
+
this.notifyListeners(event, normalized);
|
|
2846
2979
|
};
|
|
2847
2980
|
this.connectionAttempt = { id: attemptId, socket, cancel: fail };
|
|
2848
2981
|
socket.on("connect", onConnect);
|
|
@@ -3090,6 +3223,17 @@ var Realtime = class {
|
|
|
3090
3223
|
this.socket.emit("realtime:unsubscribe", { channel });
|
|
3091
3224
|
}
|
|
3092
3225
|
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Publish an event to a channel.
|
|
3228
|
+
*
|
|
3229
|
+
* Delivery notes (verified against a live backend):
|
|
3230
|
+
* - Payload fields arrive spread onto the received message itself, next to
|
|
3231
|
+
* `meta` - not nested under a `payload` key. Publishing `{ text: "hi" }`
|
|
3232
|
+
* means receivers read `msg.text`.
|
|
3233
|
+
* - The sender receives its own message too. Deduplicate with
|
|
3234
|
+
* `msg.meta.messageId` (or `msg.meta.senderId`) if you also update local
|
|
3235
|
+
* state optimistically.
|
|
3236
|
+
*/
|
|
3093
3237
|
async publish(channel, event, payload) {
|
|
3094
3238
|
if (!this.socket?.connected) {
|
|
3095
3239
|
throw new Error(
|
|
@@ -3098,6 +3242,12 @@ var Realtime = class {
|
|
|
3098
3242
|
}
|
|
3099
3243
|
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
3100
3244
|
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Listen for an event by name, across all subscribed channels.
|
|
3247
|
+
* Filter by channel inside the callback: `msg.meta.channel` always matches
|
|
3248
|
+
* the exact name passed to subscribe() (the SDK strips the server's
|
|
3249
|
+
* transport prefix before delivery).
|
|
3250
|
+
*/
|
|
3101
3251
|
on(event, callback) {
|
|
3102
3252
|
const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
3103
3253
|
listeners.add(callback);
|
package/dist/ssr.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as ResultClient } from './client-
|
|
2
|
-
import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-
|
|
3
|
-
import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-
|
|
4
|
-
export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-
|
|
1
|
+
import { R as ResultClient } from './client-BrOD2EO5.cjs';
|
|
2
|
+
import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BmU3deRO.cjs';
|
|
3
|
+
import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-VCWoai4o.cjs';
|
|
4
|
+
export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-VCWoai4o.cjs';
|
|
5
5
|
import '@supabase/postgrest-js';
|
|
6
6
|
|
|
7
7
|
interface CreateAuthActionsOptions extends Omit<ResultConfig, "accessToken" | "edgeFunctionToken" | "isServerMode" | "auth">, AuthCookieSettings {
|
package/dist/ssr.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as ResultClient } from './client
|
|
2
|
-
import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-
|
|
3
|
-
import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-
|
|
4
|
-
export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-
|
|
1
|
+
import { R as ResultClient } from './client--Xjaaw1l.js';
|
|
2
|
+
import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BmU3deRO.js';
|
|
3
|
+
import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-Uh09_lFn.js';
|
|
4
|
+
export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-Uh09_lFn.js';
|
|
5
5
|
import '@supabase/postgrest-js';
|
|
6
6
|
|
|
7
7
|
interface CreateAuthActionsOptions extends Omit<ResultConfig, "accessToken" | "edgeFunctionToken" | "isServerMode" | "auth">, AuthCookieSettings {
|
package/dist/ssr.js
CHANGED
|
@@ -641,7 +641,15 @@ var HttpClient = class {
|
|
|
641
641
|
*/
|
|
642
642
|
constructor(config, tokenManager, logger) {
|
|
643
643
|
this.config = config;
|
|
644
|
-
|
|
644
|
+
if (!config.baseUrl) {
|
|
645
|
+
throw new ResultError(
|
|
646
|
+
"createClient needs a baseUrl, but it was undefined at runtime.",
|
|
647
|
+
0,
|
|
648
|
+
"MISSING_BASE_URL",
|
|
649
|
+
"Pass the NEXT_PUBLIC_BACKEND_URL value from .env.local. In browser code, write process.env.NEXT_PUBLIC_BACKEND_URL as a literal expression (never through an imported process module) so the bundler inlines it, and set it as a deployments build variable before deploying."
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
this.baseUrl = config.baseUrl;
|
|
645
653
|
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
646
654
|
this.anonKey = config.anonKey;
|
|
647
655
|
this.defaultHeaders = {
|
|
@@ -1159,6 +1167,70 @@ var HttpClient = class {
|
|
|
1159
1167
|
};
|
|
1160
1168
|
|
|
1161
1169
|
// src/modules/ai.ts
|
|
1170
|
+
var SUPPORTED_AUDIO_FORMATS = [
|
|
1171
|
+
"wav",
|
|
1172
|
+
"mp3",
|
|
1173
|
+
"aiff",
|
|
1174
|
+
"aac",
|
|
1175
|
+
"ogg",
|
|
1176
|
+
"flac",
|
|
1177
|
+
"m4a"
|
|
1178
|
+
];
|
|
1179
|
+
var MULTIMODAL_MODEL = "google/gemini-2.5-flash";
|
|
1180
|
+
function summarizeContent(messages) {
|
|
1181
|
+
const summary = {
|
|
1182
|
+
hasImage: false,
|
|
1183
|
+
hasAudio: false,
|
|
1184
|
+
badAudioFormats: []
|
|
1185
|
+
};
|
|
1186
|
+
for (const message of messages) {
|
|
1187
|
+
if (message.images?.length) {
|
|
1188
|
+
summary.hasImage = true;
|
|
1189
|
+
}
|
|
1190
|
+
if (!Array.isArray(message.content)) {
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
for (const part of message.content) {
|
|
1194
|
+
if (part?.type === "image_url") {
|
|
1195
|
+
summary.hasImage = true;
|
|
1196
|
+
} else if (part?.type === "input_audio") {
|
|
1197
|
+
summary.hasAudio = true;
|
|
1198
|
+
const format2 = part.input_audio?.format;
|
|
1199
|
+
if (format2 && !SUPPORTED_AUDIO_FORMATS.includes(format2)) {
|
|
1200
|
+
summary.badAudioFormats.push(format2);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
return summary;
|
|
1206
|
+
}
|
|
1207
|
+
function assertSupportedContent(model, summary) {
|
|
1208
|
+
if (summary.badAudioFormats.length) {
|
|
1209
|
+
throw new ResultError(
|
|
1210
|
+
`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.`,
|
|
1211
|
+
400,
|
|
1212
|
+
"AI_INVALID_INPUT"
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
if (summary.hasAudio && model.startsWith("openai/")) {
|
|
1216
|
+
throw new ResultError(
|
|
1217
|
+
`Model "${model}" does not accept audio input on this gateway. Use ${MULTIMODAL_MODEL} (verified for audio).`,
|
|
1218
|
+
400,
|
|
1219
|
+
"AI_INVALID_INPUT"
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function enrichUpstreamError(error, summary) {
|
|
1224
|
+
if (!(error instanceof ResultError) || error.error !== "AI_UPSTREAM_UNAVAILABLE" || !summary.hasImage && !summary.hasAudio) {
|
|
1225
|
+
return error;
|
|
1226
|
+
}
|
|
1227
|
+
const kind = summary.hasImage && summary.hasAudio ? "image and audio" : summary.hasImage ? "image" : "audio";
|
|
1228
|
+
return new ResultError(
|
|
1229
|
+
`${error.message} This request included ${kind} input; if the error persists, use ${MULTIMODAL_MODEL} (verified for vision and audio).`,
|
|
1230
|
+
error.statusCode,
|
|
1231
|
+
error.error
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1162
1234
|
var AI = class {
|
|
1163
1235
|
chat;
|
|
1164
1236
|
images;
|
|
@@ -1181,6 +1253,8 @@ var ChatCompletions = class {
|
|
|
1181
1253
|
}
|
|
1182
1254
|
http;
|
|
1183
1255
|
async create(params) {
|
|
1256
|
+
const contentSummary = summarizeContent(params.messages ?? []);
|
|
1257
|
+
assertSupportedContent(params.model, contentSummary);
|
|
1184
1258
|
const backendParams = {
|
|
1185
1259
|
model: params.model,
|
|
1186
1260
|
messages: params.messages,
|
|
@@ -1209,15 +1283,28 @@ var ChatCompletions = class {
|
|
|
1209
1283
|
}
|
|
1210
1284
|
);
|
|
1211
1285
|
if (!response2.ok) {
|
|
1212
|
-
|
|
1213
|
-
|
|
1286
|
+
let body = null;
|
|
1287
|
+
try {
|
|
1288
|
+
body = await response2.json();
|
|
1289
|
+
} catch {
|
|
1290
|
+
}
|
|
1291
|
+
throw enrichUpstreamError(
|
|
1292
|
+
new ResultError(
|
|
1293
|
+
body?.message || body?.error || "Stream request failed",
|
|
1294
|
+
body?.statusCode ?? response2.status,
|
|
1295
|
+
body?.error || "AI_STREAM_ERROR"
|
|
1296
|
+
),
|
|
1297
|
+
contentSummary
|
|
1298
|
+
);
|
|
1214
1299
|
}
|
|
1215
1300
|
return this.parseSSEStream(response2, params.model);
|
|
1216
1301
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
backendParams
|
|
1220
|
-
)
|
|
1302
|
+
let response;
|
|
1303
|
+
try {
|
|
1304
|
+
response = await this.http.post("/api/ai/chat/completion", backendParams);
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
throw enrichUpstreamError(error, contentSummary);
|
|
1307
|
+
}
|
|
1221
1308
|
const content = response.text || "";
|
|
1222
1309
|
return {
|
|
1223
1310
|
id: `chatcmpl-${Date.now()}`,
|
|
@@ -1312,6 +1399,37 @@ var ChatCompletions = class {
|
|
|
1312
1399
|
]
|
|
1313
1400
|
};
|
|
1314
1401
|
}
|
|
1402
|
+
if (data.annotations?.length) {
|
|
1403
|
+
yield {
|
|
1404
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1405
|
+
object: "chat.completion.chunk",
|
|
1406
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1407
|
+
model,
|
|
1408
|
+
choices: [
|
|
1409
|
+
{
|
|
1410
|
+
index: 0,
|
|
1411
|
+
delta: {
|
|
1412
|
+
annotations: data.annotations
|
|
1413
|
+
},
|
|
1414
|
+
finish_reason: null
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
if (data.tokenUsage) {
|
|
1420
|
+
yield {
|
|
1421
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1422
|
+
object: "chat.completion.chunk",
|
|
1423
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1424
|
+
model,
|
|
1425
|
+
choices: [],
|
|
1426
|
+
usage: {
|
|
1427
|
+
prompt_tokens: data.tokenUsage.promptTokens || 0,
|
|
1428
|
+
completion_tokens: data.tokenUsage.completionTokens || 0,
|
|
1429
|
+
total_tokens: data.tokenUsage.totalTokens || 0
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1315
1433
|
if (data.done) {
|
|
1316
1434
|
reader.releaseLock();
|
|
1317
1435
|
return;
|
|
@@ -2548,13 +2666,13 @@ var Functions = class _Functions {
|
|
|
2548
2666
|
functionsUrl;
|
|
2549
2667
|
constructor(http, functionsUrl) {
|
|
2550
2668
|
this.http = http;
|
|
2551
|
-
this.functionsUrl = functionsUrl
|
|
2669
|
+
this.functionsUrl = functionsUrl;
|
|
2552
2670
|
}
|
|
2553
2671
|
/**
|
|
2554
|
-
* Derive the subhosting URL from the base URL
|
|
2555
|
-
* Rewrites the backend base URL to its functions subhosting URL
|
|
2672
|
+
* Derive the legacy subhosting URL from the base URL
|
|
2556
2673
|
* ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
|
|
2557
|
-
* Only
|
|
2674
|
+
* Only used to recognize when a configured functionsUrl still points at
|
|
2675
|
+
* the local deployment, so in-process dispatch can short-circuit it.
|
|
2558
2676
|
*/
|
|
2559
2677
|
static deriveSubhostingUrl(baseUrl) {
|
|
2560
2678
|
try {
|
|
@@ -2587,11 +2705,13 @@ var Functions = class _Functions {
|
|
|
2587
2705
|
* Invoke an Edge Function.
|
|
2588
2706
|
*
|
|
2589
2707
|
* Dispatch order:
|
|
2590
|
-
* 1. If the platform's in-process dispatch hook is present
|
|
2591
|
-
*
|
|
2592
|
-
*
|
|
2593
|
-
* 2.
|
|
2594
|
-
*
|
|
2708
|
+
* 1. If the platform's in-process dispatch hook is present and no foreign
|
|
2709
|
+
* functionsUrl is configured, call it directly (function-to-function
|
|
2710
|
+
* calls inside the same deployment).
|
|
2711
|
+
* 2. If a custom functionsUrl is configured, try it; fall back to the
|
|
2712
|
+
* proxy path on 404 or network failure.
|
|
2713
|
+
* 3. Otherwise, invoke through the main host's /functions/{slug} proxy.
|
|
2714
|
+
* This route works from browsers too (correct CORS).
|
|
2595
2715
|
*
|
|
2596
2716
|
* @param slug The function slug to invoke
|
|
2597
2717
|
* @param options Request options
|
|
@@ -2600,7 +2720,7 @@ var Functions = class _Functions {
|
|
|
2600
2720
|
const { method = "POST", body, headers = {} } = options;
|
|
2601
2721
|
const dispatch = globalThis.__insforge_dispatch__;
|
|
2602
2722
|
const localFunctionsUrl = _Functions.deriveSubhostingUrl(this.http.baseUrl);
|
|
2603
|
-
if (typeof dispatch === "function" && !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl) {
|
|
2723
|
+
if (typeof dispatch === "function" && (this.functionsUrl === void 0 || !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl)) {
|
|
2604
2724
|
try {
|
|
2605
2725
|
const req = this.buildInProcessRequest(slug, method, body, headers);
|
|
2606
2726
|
const res = await dispatch(req);
|
|
@@ -2635,7 +2755,8 @@ var Functions = class _Functions {
|
|
|
2635
2755
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2636
2756
|
throw error;
|
|
2637
2757
|
}
|
|
2638
|
-
|
|
2758
|
+
const unreachable = error instanceof ResultError && (error.statusCode === 404 || error.statusCode === 0);
|
|
2759
|
+
if (unreachable) {
|
|
2639
2760
|
} else {
|
|
2640
2761
|
return {
|
|
2641
2762
|
data: null,
|
|
@@ -2671,6 +2792,17 @@ var Functions = class _Functions {
|
|
|
2671
2792
|
// src/modules/realtime.ts
|
|
2672
2793
|
var CONNECT_TIMEOUT = 1e4;
|
|
2673
2794
|
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2795
|
+
var CHANNEL_PREFIX = "realtime:";
|
|
2796
|
+
function normalizeChannelMeta(message) {
|
|
2797
|
+
const channel = message?.meta?.channel;
|
|
2798
|
+
if (typeof channel !== "string" || !channel.startsWith(CHANNEL_PREFIX)) {
|
|
2799
|
+
return message;
|
|
2800
|
+
}
|
|
2801
|
+
return {
|
|
2802
|
+
...message,
|
|
2803
|
+
meta: { ...message.meta, channel: channel.slice(CHANNEL_PREFIX.length) }
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2674
2806
|
var Realtime = class {
|
|
2675
2807
|
constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
|
|
2676
2808
|
this.baseUrl = baseUrl;
|
|
@@ -2792,8 +2924,9 @@ var Realtime = class {
|
|
|
2792
2924
|
if (event === "realtime:error") {
|
|
2793
2925
|
return;
|
|
2794
2926
|
}
|
|
2795
|
-
|
|
2796
|
-
this.
|
|
2927
|
+
const normalized = normalizeChannelMeta(message);
|
|
2928
|
+
this.applyPresenceEvent(event, normalized);
|
|
2929
|
+
this.notifyListeners(event, normalized);
|
|
2797
2930
|
};
|
|
2798
2931
|
this.connectionAttempt = { id: attemptId, socket, cancel: fail };
|
|
2799
2932
|
socket.on("connect", onConnect);
|
|
@@ -3041,6 +3174,17 @@ var Realtime = class {
|
|
|
3041
3174
|
this.socket.emit("realtime:unsubscribe", { channel });
|
|
3042
3175
|
}
|
|
3043
3176
|
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Publish an event to a channel.
|
|
3179
|
+
*
|
|
3180
|
+
* Delivery notes (verified against a live backend):
|
|
3181
|
+
* - Payload fields arrive spread onto the received message itself, next to
|
|
3182
|
+
* `meta` - not nested under a `payload` key. Publishing `{ text: "hi" }`
|
|
3183
|
+
* means receivers read `msg.text`.
|
|
3184
|
+
* - The sender receives its own message too. Deduplicate with
|
|
3185
|
+
* `msg.meta.messageId` (or `msg.meta.senderId`) if you also update local
|
|
3186
|
+
* state optimistically.
|
|
3187
|
+
*/
|
|
3044
3188
|
async publish(channel, event, payload) {
|
|
3045
3189
|
if (!this.socket?.connected) {
|
|
3046
3190
|
throw new Error(
|
|
@@ -3049,6 +3193,12 @@ var Realtime = class {
|
|
|
3049
3193
|
}
|
|
3050
3194
|
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
3051
3195
|
}
|
|
3196
|
+
/**
|
|
3197
|
+
* Listen for an event by name, across all subscribed channels.
|
|
3198
|
+
* Filter by channel inside the callback: `msg.meta.channel` always matches
|
|
3199
|
+
* the exact name passed to subscribe() (the SDK strips the server's
|
|
3200
|
+
* transport prefix before delivery).
|
|
3201
|
+
*/
|
|
3052
3202
|
on(event, callback) {
|
|
3053
3203
|
const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
3054
3204
|
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;
|
|
@@ -518,8 +526,9 @@ interface SocketMessage {
|
|
|
518
526
|
type ResultErrorCode = ErrorCode | (string & {});
|
|
519
527
|
interface ResultConfig {
|
|
520
528
|
/**
|
|
521
|
-
* The base URL of the Result backend API
|
|
522
|
-
*
|
|
529
|
+
* The base URL of the Result backend API (the NEXT_PUBLIC_BACKEND_URL
|
|
530
|
+
* value from .env.local). Required at runtime: constructing a client
|
|
531
|
+
* without it throws MISSING_BASE_URL.
|
|
523
532
|
*/
|
|
524
533
|
baseUrl?: string;
|
|
525
534
|
/**
|
|
@@ -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;
|
|
@@ -518,8 +526,9 @@ interface SocketMessage {
|
|
|
518
526
|
type ResultErrorCode = ErrorCode | (string & {});
|
|
519
527
|
interface ResultConfig {
|
|
520
528
|
/**
|
|
521
|
-
* The base URL of the Result backend API
|
|
522
|
-
*
|
|
529
|
+
* The base URL of the Result backend API (the NEXT_PUBLIC_BACKEND_URL
|
|
530
|
+
* value from .env.local). Required at runtime: constructing a client
|
|
531
|
+
* without it throws MISSING_BASE_URL.
|
|
523
532
|
*/
|
|
524
533
|
baseUrl?: string;
|
|
525
534
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@resultdev/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|