@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 CHANGED
@@ -1,5 +1,37 @@
1
1
  # @resultdev/sdk
2
2
 
3
+ ## 0.3.0
4
+
5
+ Reliability release: every fix here comes from live verification against a
6
+ freshly provisioned backend.
7
+
8
+ - Functions: `invoke()` now goes through the main host's `/functions/{slug}`
9
+ route by default. The legacy subhosting fast-path was retired upstream and
10
+ broke browser calls entirely (its preflight 404s surfaced as
11
+ `TypeError: Failed to fetch`). The main route works from browsers with
12
+ correct CORS. In-process dispatch inside deployments is unchanged, and an
13
+ explicit `functionsUrl` is still honored (now with fallback to the main
14
+ route on network failure, not just 404).
15
+ - Realtime: incoming `meta.channel` had a transport prefix
16
+ (`realtime:chat:1` for a subscription to `chat:1`), so the natural
17
+ `msg.meta.channel === channel` filter silently dropped every message. The
18
+ SDK now strips the prefix before delivery; presence join/leave tracking is
19
+ fixed by the same change.
20
+ - Realtime docs: payload fields arrive spread onto the message (not under a
21
+ `payload` key), and the sender receives its own broadcast; dedupe with
22
+ `meta.messageId`.
23
+ - AI: streaming responses now surface web-search/file citations
24
+ (`chunk.choices[0].delta.annotations`, trailing chunk) and token usage
25
+ (`chunk.usage`). Both were silently dropped before.
26
+ - AI: deterministic bad requests fail fast with specific errors instead of
27
+ an opaque upstream 4xx after a round trip: unsupported audio formats
28
+ (browser MediaRecorder webm/opus included) and audio input on `openai/*`
29
+ models. Upstream provider errors on image/audio requests now name
30
+ `google/gemini-2.5-flash` as the verified multimodal model.
31
+ - Docs: session model documented on `auth` (httpOnly cookie + in-memory
32
+ token; localStorage is empty by design), verified model names in every AI
33
+ example, return shapes clarified across modules.
34
+
3
35
  ## 0.2.0
4
36
 
5
37
  The SDK is now built fully first-party instead of wrapping a bundled
@@ -1,4 +1,4 @@
1
- import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-CbsaMttt.js';
1
+ import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-DSVtGaIA.cjs';
2
2
  import * as _supabase_postgrest_js from '@supabase/postgrest-js';
3
3
  import { PostgrestClient } from '@supabase/postgrest-js';
4
4
 
@@ -248,9 +248,13 @@ interface ChatCompletionChunk {
248
248
  delta: {
249
249
  content?: string;
250
250
  tool_calls?: ToolCall[];
251
+ /** Web-search/file citations; arrives in a trailing chunk. */
252
+ annotations?: Annotation[];
251
253
  };
252
254
  finish_reason: "tool_calls" | null;
253
255
  }[];
256
+ /** Token usage; arrives in a trailing chunk with empty choices. */
257
+ usage?: ChatCompletionUsage;
254
258
  }
255
259
  interface EmbeddingsResult {
256
260
  object: "list";
@@ -293,14 +297,16 @@ declare class ChatCompletions {
293
297
  * ```typescript
294
298
  * // Non-streaming
295
299
  * const completion = await client.ai.chat.completions.create({
296
- * model: 'gpt-4',
300
+ * model: 'openai/gpt-4o-mini',
297
301
  * messages: [{ role: 'user', content: 'Hello!' }]
298
302
  * });
299
303
  * console.log(completion.choices[0].message.content);
300
304
  *
301
- * // With images (OpenAI-compatible format)
305
+ * // With images. google/gemini-2.5-flash is the verified multimodal
306
+ * // model (openai/gpt-4o and gpt-4o-mini also accept images, but audio
307
+ * // requires gemini). URLs and base64 data URIs both work.
302
308
  * const response = await client.ai.chat.completions.create({
303
- * model: 'gpt-4-vision',
309
+ * model: 'google/gemini-2.5-flash',
304
310
  * messages: [{
305
311
  * role: 'user',
306
312
  * content: [
@@ -310,37 +316,32 @@ declare class ChatCompletions {
310
316
  * }]
311
317
  * });
312
318
  *
313
- * // With PDF files
314
- * const pdfResponse = await client.ai.chat.completions.create({
315
- * model: 'anthropic/claude-3.5-sonnet',
319
+ * // With audio. Base64 data in wav/mp3/aiff/aac/ogg/flac/m4a only -
320
+ * // browser MediaRecorder's webm/opus is rejected; record PCM via the
321
+ * // Web Audio API and encode a 16 kHz mono WAV instead.
322
+ * const audioResponse = await client.ai.chat.completions.create({
323
+ * model: 'google/gemini-2.5-flash',
316
324
  * messages: [{
317
325
  * role: 'user',
318
326
  * content: [
319
- * { type: 'text', text: 'Summarize this document' },
320
- * { type: 'file', file: { filename: 'doc.pdf', file_data: 'https://example.com/doc.pdf' } }
327
+ * { type: 'text', text: 'Transcribe this recording' },
328
+ * { type: 'input_audio', input_audio: { data: base64Wav, format: 'wav' } }
321
329
  * ]
322
- * }],
323
- * fileParser: { enabled: true, pdf: { engine: 'mistral-ocr' } }
330
+ * }]
324
331
  * });
325
332
  *
326
- * // With web search
333
+ * // With web search. Non-streaming: citations arrive on
334
+ * // response.choices[0].message.annotations. Streaming: they arrive in a
335
+ * // trailing chunk as chunk.choices[0].delta.annotations.
327
336
  * const searchResponse = await client.ai.chat.completions.create({
328
- * model: 'openai/gpt-4',
337
+ * model: 'openai/gpt-4o-mini',
329
338
  * messages: [{ role: 'user', content: 'What are the latest news about AI?' }],
330
339
  * webSearch: { enabled: true, maxResults: 5 }
331
340
  * });
332
- * // Access citations from response.choices[0].message.annotations
333
- *
334
- * // With thinking/reasoning mode (Anthropic models)
335
- * const thinkingResponse = await client.ai.chat.completions.create({
336
- * model: 'anthropic/claude-3.5-sonnet',
337
- * messages: [{ role: 'user', content: 'Solve this complex math problem...' }],
338
- * thinking: true
339
- * });
340
341
  *
341
342
  * // Streaming - returns async iterable
342
343
  * const stream = await client.ai.chat.completions.create({
343
- * model: 'gpt-4',
344
+ * model: 'openai/gpt-4o-mini',
344
345
  * messages: [{ role: 'user', content: 'Tell me a story' }],
345
346
  * stream: true
346
347
  * });
@@ -452,6 +453,16 @@ type OAuthSignInOptions = {
452
453
  type OAuthSignInLegacyOptions = OAuthSignInOptions & {
453
454
  provider: OAuthProvidersSchema | string;
454
455
  };
456
+ /**
457
+ * Authentication client.
458
+ *
459
+ * Session model: the refresh token lives in an httpOnly cookie set by the
460
+ * backend (plus a CSRF cookie); the access token is held in memory and
461
+ * refreshed from that cookie. Nothing auth-related is written to
462
+ * localStorage, so an empty localStorage does NOT mean the user is signed
463
+ * out. Check state with getCurrentUser() or onAuthStateChange(), never by
464
+ * reading browser storage.
465
+ */
455
466
  declare class Auth {
456
467
  private http;
457
468
  private tokenManager;
@@ -748,10 +759,10 @@ declare class Functions {
748
759
  private functionsUrl;
749
760
  constructor(http: HttpClient, functionsUrl?: string);
750
761
  /**
751
- * Derive the subhosting URL from the base URL.
752
- * Rewrites the backend base URL to its functions subhosting URL
762
+ * Derive the legacy subhosting URL from the base URL
753
763
  * ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
754
- * Only applies to platform-hosted backends.
764
+ * Only used to recognize when a configured functionsUrl still points at
765
+ * the local deployment, so in-process dispatch can short-circuit it.
755
766
  */
756
767
  private static deriveSubhostingUrl;
757
768
  /**
@@ -763,11 +774,13 @@ declare class Functions {
763
774
  * Invoke an Edge Function.
764
775
  *
765
776
  * Dispatch order:
766
- * 1. If the platform's in-process dispatch hook is present, call it directly.
767
- * This avoids Deno Subhosting's 508 Loop Detected when one bundled
768
- * function invokes another inside the same deployment.
769
- * 2. Otherwise, try the configured subhosting URL.
770
- * 3. On 404 from subhosting, fall back to the proxy path.
777
+ * 1. If the platform's in-process dispatch hook is present and no foreign
778
+ * functionsUrl is configured, call it directly (function-to-function
779
+ * calls inside the same deployment).
780
+ * 2. If a custom functionsUrl is configured, try it; fall back to the
781
+ * proxy path on 404 or network failure.
782
+ * 3. Otherwise, invoke through the main host's /functions/{slug} proxy.
783
+ * This route works from browsers too (correct CORS).
771
784
  *
772
785
  * @param slug The function slug to invoke
773
786
  * @param options Request options
@@ -811,7 +824,24 @@ declare class Realtime {
811
824
  get socketId(): string | undefined;
812
825
  subscribe(channel: string): Promise<SubscribeResponse>;
813
826
  unsubscribe(channel: string): void;
827
+ /**
828
+ * Publish an event to a channel.
829
+ *
830
+ * Delivery notes (verified against a live backend):
831
+ * - Payload fields arrive spread onto the received message itself, next to
832
+ * `meta` - not nested under a `payload` key. Publishing `{ text: "hi" }`
833
+ * means receivers read `msg.text`.
834
+ * - The sender receives its own message too. Deduplicate with
835
+ * `msg.meta.messageId` (or `msg.meta.senderId`) if you also update local
836
+ * state optimistically.
837
+ */
814
838
  publish<T = unknown>(channel: string, event: string, payload: T): Promise<void>;
839
+ /**
840
+ * Listen for an event by name, across all subscribed channels.
841
+ * Filter by channel inside the callback: `msg.meta.channel` always matches
842
+ * the exact name passed to subscribe() (the SDK strips the server's
843
+ * transport prefix before delivery).
844
+ */
815
845
  on<T = SocketMessage>(event: string, callback: EventCallback<T>): void;
816
846
  off<T = SocketMessage>(event: string, callback: EventCallback<T>): void;
817
847
  once<T = SocketMessage>(event: string, callback: EventCallback<T>): void;
@@ -1,4 +1,4 @@
1
- import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-CbsaMttt.cjs';
1
+ import { e as AuthSession, U as UserSchema, a as ResultConfig, G as AuthRefreshResponse, C as ChatCompletionRequest, B as ToolCall, H as Annotation, I as ImageGenerationRequest, j as EmbeddingsRequest, J as EmbeddingObject, h as CreateUserRequest, K as CreateUserResponse, o as ResultError, g as CreateSessionRequest, L as CreateSessionResponse, l as OAuthProvidersSchema, M as RefreshSessionResponse, N as GetProfileResponse, Q as SendVerificationEmailRequest, V as VerifyEmailRequest, W as VerifyEmailResponse, X as SendResetPasswordEmailRequest, Y as ExchangeResetPasswordTokenRequest, Z as ExchangeResetPasswordTokenResponse, _ as ResetPasswordResponse, $ as GetPublicAuthConfigResponse, S as SendRawEmailRequest, q as SendEmailResponse, v as SubscribeResponse, r as SocketMessage, P as PresenceMember, u as StorageFileSchema, a0 as ListObjectsResponseSchema, i as DeleteObjectsResponse, A as Analytics, w as Support } from './types-DSVtGaIA.js';
2
2
  import * as _supabase_postgrest_js from '@supabase/postgrest-js';
3
3
  import { PostgrestClient } from '@supabase/postgrest-js';
4
4
 
@@ -248,9 +248,13 @@ interface ChatCompletionChunk {
248
248
  delta: {
249
249
  content?: string;
250
250
  tool_calls?: ToolCall[];
251
+ /** Web-search/file citations; arrives in a trailing chunk. */
252
+ annotations?: Annotation[];
251
253
  };
252
254
  finish_reason: "tool_calls" | null;
253
255
  }[];
256
+ /** Token usage; arrives in a trailing chunk with empty choices. */
257
+ usage?: ChatCompletionUsage;
254
258
  }
255
259
  interface EmbeddingsResult {
256
260
  object: "list";
@@ -293,14 +297,16 @@ declare class ChatCompletions {
293
297
  * ```typescript
294
298
  * // Non-streaming
295
299
  * const completion = await client.ai.chat.completions.create({
296
- * model: 'gpt-4',
300
+ * model: 'openai/gpt-4o-mini',
297
301
  * messages: [{ role: 'user', content: 'Hello!' }]
298
302
  * });
299
303
  * console.log(completion.choices[0].message.content);
300
304
  *
301
- * // With images (OpenAI-compatible format)
305
+ * // With images. google/gemini-2.5-flash is the verified multimodal
306
+ * // model (openai/gpt-4o and gpt-4o-mini also accept images, but audio
307
+ * // requires gemini). URLs and base64 data URIs both work.
302
308
  * const response = await client.ai.chat.completions.create({
303
- * model: 'gpt-4-vision',
309
+ * model: 'google/gemini-2.5-flash',
304
310
  * messages: [{
305
311
  * role: 'user',
306
312
  * content: [
@@ -310,37 +316,32 @@ declare class ChatCompletions {
310
316
  * }]
311
317
  * });
312
318
  *
313
- * // With PDF files
314
- * const pdfResponse = await client.ai.chat.completions.create({
315
- * model: 'anthropic/claude-3.5-sonnet',
319
+ * // With audio. Base64 data in wav/mp3/aiff/aac/ogg/flac/m4a only -
320
+ * // browser MediaRecorder's webm/opus is rejected; record PCM via the
321
+ * // Web Audio API and encode a 16 kHz mono WAV instead.
322
+ * const audioResponse = await client.ai.chat.completions.create({
323
+ * model: 'google/gemini-2.5-flash',
316
324
  * messages: [{
317
325
  * role: 'user',
318
326
  * content: [
319
- * { type: 'text', text: 'Summarize this document' },
320
- * { type: 'file', file: { filename: 'doc.pdf', file_data: 'https://example.com/doc.pdf' } }
327
+ * { type: 'text', text: 'Transcribe this recording' },
328
+ * { type: 'input_audio', input_audio: { data: base64Wav, format: 'wav' } }
321
329
  * ]
322
- * }],
323
- * fileParser: { enabled: true, pdf: { engine: 'mistral-ocr' } }
330
+ * }]
324
331
  * });
325
332
  *
326
- * // With web search
333
+ * // With web search. Non-streaming: citations arrive on
334
+ * // response.choices[0].message.annotations. Streaming: they arrive in a
335
+ * // trailing chunk as chunk.choices[0].delta.annotations.
327
336
  * const searchResponse = await client.ai.chat.completions.create({
328
- * model: 'openai/gpt-4',
337
+ * model: 'openai/gpt-4o-mini',
329
338
  * messages: [{ role: 'user', content: 'What are the latest news about AI?' }],
330
339
  * webSearch: { enabled: true, maxResults: 5 }
331
340
  * });
332
- * // Access citations from response.choices[0].message.annotations
333
- *
334
- * // With thinking/reasoning mode (Anthropic models)
335
- * const thinkingResponse = await client.ai.chat.completions.create({
336
- * model: 'anthropic/claude-3.5-sonnet',
337
- * messages: [{ role: 'user', content: 'Solve this complex math problem...' }],
338
- * thinking: true
339
- * });
340
341
  *
341
342
  * // Streaming - returns async iterable
342
343
  * const stream = await client.ai.chat.completions.create({
343
- * model: 'gpt-4',
344
+ * model: 'openai/gpt-4o-mini',
344
345
  * messages: [{ role: 'user', content: 'Tell me a story' }],
345
346
  * stream: true
346
347
  * });
@@ -452,6 +453,16 @@ type OAuthSignInOptions = {
452
453
  type OAuthSignInLegacyOptions = OAuthSignInOptions & {
453
454
  provider: OAuthProvidersSchema | string;
454
455
  };
456
+ /**
457
+ * Authentication client.
458
+ *
459
+ * Session model: the refresh token lives in an httpOnly cookie set by the
460
+ * backend (plus a CSRF cookie); the access token is held in memory and
461
+ * refreshed from that cookie. Nothing auth-related is written to
462
+ * localStorage, so an empty localStorage does NOT mean the user is signed
463
+ * out. Check state with getCurrentUser() or onAuthStateChange(), never by
464
+ * reading browser storage.
465
+ */
455
466
  declare class Auth {
456
467
  private http;
457
468
  private tokenManager;
@@ -748,10 +759,10 @@ declare class Functions {
748
759
  private functionsUrl;
749
760
  constructor(http: HttpClient, functionsUrl?: string);
750
761
  /**
751
- * Derive the subhosting URL from the base URL.
752
- * Rewrites the backend base URL to its functions subhosting URL
762
+ * Derive the legacy subhosting URL from the base URL
753
763
  * ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
754
- * Only applies to platform-hosted backends.
764
+ * Only used to recognize when a configured functionsUrl still points at
765
+ * the local deployment, so in-process dispatch can short-circuit it.
755
766
  */
756
767
  private static deriveSubhostingUrl;
757
768
  /**
@@ -763,11 +774,13 @@ declare class Functions {
763
774
  * Invoke an Edge Function.
764
775
  *
765
776
  * Dispatch order:
766
- * 1. If the platform's in-process dispatch hook is present, call it directly.
767
- * This avoids Deno Subhosting's 508 Loop Detected when one bundled
768
- * function invokes another inside the same deployment.
769
- * 2. Otherwise, try the configured subhosting URL.
770
- * 3. On 404 from subhosting, fall back to the proxy path.
777
+ * 1. If the platform's in-process dispatch hook is present and no foreign
778
+ * functionsUrl is configured, call it directly (function-to-function
779
+ * calls inside the same deployment).
780
+ * 2. If a custom functionsUrl is configured, try it; fall back to the
781
+ * proxy path on 404 or network failure.
782
+ * 3. Otherwise, invoke through the main host's /functions/{slug} proxy.
783
+ * This route works from browsers too (correct CORS).
771
784
  *
772
785
  * @param slug The function slug to invoke
773
786
  * @param options Request options
@@ -811,7 +824,24 @@ declare class Realtime {
811
824
  get socketId(): string | undefined;
812
825
  subscribe(channel: string): Promise<SubscribeResponse>;
813
826
  unsubscribe(channel: string): void;
827
+ /**
828
+ * Publish an event to a channel.
829
+ *
830
+ * Delivery notes (verified against a live backend):
831
+ * - Payload fields arrive spread onto the received message itself, next to
832
+ * `meta` - not nested under a `payload` key. Publishing `{ text: "hi" }`
833
+ * means receivers read `msg.text`.
834
+ * - The sender receives its own message too. Deduplicate with
835
+ * `msg.meta.messageId` (or `msg.meta.senderId`) if you also update local
836
+ * state optimistically.
837
+ */
814
838
  publish<T = unknown>(channel: string, event: string, payload: T): Promise<void>;
839
+ /**
840
+ * Listen for an event by name, across all subscribed channels.
841
+ * Filter by channel inside the callback: `msg.meta.channel` always matches
842
+ * the exact name passed to subscribe() (the SDK strips the server's
843
+ * transport prefix before delivery).
844
+ */
815
845
  on<T = SocketMessage>(event: string, callback: EventCallback<T>): void;
816
846
  off<T = SocketMessage>(event: string, callback: EventCallback<T>): void;
817
847
  once<T = SocketMessage>(event: string, callback: EventCallback<T>): void;
package/dist/index.cjs CHANGED
@@ -1021,6 +1021,70 @@ var HttpClient = class {
1021
1021
  };
1022
1022
 
1023
1023
  // src/modules/ai.ts
1024
+ var SUPPORTED_AUDIO_FORMATS = [
1025
+ "wav",
1026
+ "mp3",
1027
+ "aiff",
1028
+ "aac",
1029
+ "ogg",
1030
+ "flac",
1031
+ "m4a"
1032
+ ];
1033
+ var MULTIMODAL_MODEL = "google/gemini-2.5-flash";
1034
+ function summarizeContent(messages) {
1035
+ const summary = {
1036
+ hasImage: false,
1037
+ hasAudio: false,
1038
+ badAudioFormats: []
1039
+ };
1040
+ for (const message of messages) {
1041
+ if (message.images?.length) {
1042
+ summary.hasImage = true;
1043
+ }
1044
+ if (!Array.isArray(message.content)) {
1045
+ continue;
1046
+ }
1047
+ for (const part of message.content) {
1048
+ if (part?.type === "image_url") {
1049
+ summary.hasImage = true;
1050
+ } else if (part?.type === "input_audio") {
1051
+ summary.hasAudio = true;
1052
+ const format2 = part.input_audio?.format;
1053
+ if (format2 && !SUPPORTED_AUDIO_FORMATS.includes(format2)) {
1054
+ summary.badAudioFormats.push(format2);
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ return summary;
1060
+ }
1061
+ function assertSupportedContent(model, summary) {
1062
+ if (summary.badAudioFormats.length) {
1063
+ throw new ResultError(
1064
+ `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.`,
1065
+ 400,
1066
+ "AI_INVALID_INPUT"
1067
+ );
1068
+ }
1069
+ if (summary.hasAudio && model.startsWith("openai/")) {
1070
+ throw new ResultError(
1071
+ `Model "${model}" does not accept audio input on this gateway. Use ${MULTIMODAL_MODEL} (verified for audio).`,
1072
+ 400,
1073
+ "AI_INVALID_INPUT"
1074
+ );
1075
+ }
1076
+ }
1077
+ function enrichUpstreamError(error, summary) {
1078
+ if (!(error instanceof ResultError) || error.error !== "AI_UPSTREAM_UNAVAILABLE" || !summary.hasImage && !summary.hasAudio) {
1079
+ return error;
1080
+ }
1081
+ const kind = summary.hasImage && summary.hasAudio ? "image and audio" : summary.hasImage ? "image" : "audio";
1082
+ return new ResultError(
1083
+ `${error.message} This request included ${kind} input; if the error persists, use ${MULTIMODAL_MODEL} (verified for vision and audio).`,
1084
+ error.statusCode,
1085
+ error.error
1086
+ );
1087
+ }
1024
1088
  var AI = class {
1025
1089
  chat;
1026
1090
  images;
@@ -1043,6 +1107,8 @@ var ChatCompletions = class {
1043
1107
  }
1044
1108
  http;
1045
1109
  async create(params) {
1110
+ const contentSummary = summarizeContent(params.messages ?? []);
1111
+ assertSupportedContent(params.model, contentSummary);
1046
1112
  const backendParams = {
1047
1113
  model: params.model,
1048
1114
  messages: params.messages,
@@ -1071,15 +1137,28 @@ var ChatCompletions = class {
1071
1137
  }
1072
1138
  );
1073
1139
  if (!response2.ok) {
1074
- const error = await response2.json();
1075
- throw new Error(error.error || "Stream request failed");
1140
+ let body = null;
1141
+ try {
1142
+ body = await response2.json();
1143
+ } catch {
1144
+ }
1145
+ throw enrichUpstreamError(
1146
+ new ResultError(
1147
+ body?.message || body?.error || "Stream request failed",
1148
+ body?.statusCode ?? response2.status,
1149
+ body?.error || "AI_STREAM_ERROR"
1150
+ ),
1151
+ contentSummary
1152
+ );
1076
1153
  }
1077
1154
  return this.parseSSEStream(response2, params.model);
1078
1155
  }
1079
- const response = await this.http.post(
1080
- "/api/ai/chat/completion",
1081
- backendParams
1082
- );
1156
+ let response;
1157
+ try {
1158
+ response = await this.http.post("/api/ai/chat/completion", backendParams);
1159
+ } catch (error) {
1160
+ throw enrichUpstreamError(error, contentSummary);
1161
+ }
1083
1162
  const content = response.text || "";
1084
1163
  return {
1085
1164
  id: `chatcmpl-${Date.now()}`,
@@ -1174,6 +1253,37 @@ var ChatCompletions = class {
1174
1253
  ]
1175
1254
  };
1176
1255
  }
1256
+ if (data.annotations?.length) {
1257
+ yield {
1258
+ id: `chatcmpl-${Date.now()}`,
1259
+ object: "chat.completion.chunk",
1260
+ created: Math.floor(Date.now() / 1e3),
1261
+ model,
1262
+ choices: [
1263
+ {
1264
+ index: 0,
1265
+ delta: {
1266
+ annotations: data.annotations
1267
+ },
1268
+ finish_reason: null
1269
+ }
1270
+ ]
1271
+ };
1272
+ }
1273
+ if (data.tokenUsage) {
1274
+ yield {
1275
+ id: `chatcmpl-${Date.now()}`,
1276
+ object: "chat.completion.chunk",
1277
+ created: Math.floor(Date.now() / 1e3),
1278
+ model,
1279
+ choices: [],
1280
+ usage: {
1281
+ prompt_tokens: data.tokenUsage.promptTokens || 0,
1282
+ completion_tokens: data.tokenUsage.completionTokens || 0,
1283
+ total_tokens: data.tokenUsage.totalTokens || 0
1284
+ }
1285
+ };
1286
+ }
1177
1287
  if (data.done) {
1178
1288
  reader.releaseLock();
1179
1289
  return;
@@ -2410,13 +2520,13 @@ var Functions = class _Functions {
2410
2520
  functionsUrl;
2411
2521
  constructor(http, functionsUrl) {
2412
2522
  this.http = http;
2413
- this.functionsUrl = functionsUrl || _Functions.deriveSubhostingUrl(http.baseUrl);
2523
+ this.functionsUrl = functionsUrl;
2414
2524
  }
2415
2525
  /**
2416
- * Derive the subhosting URL from the base URL.
2417
- * Rewrites the backend base URL to its functions subhosting URL
2526
+ * Derive the legacy subhosting URL from the base URL
2418
2527
  * ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
2419
- * Only applies to platform-hosted backends.
2528
+ * Only used to recognize when a configured functionsUrl still points at
2529
+ * the local deployment, so in-process dispatch can short-circuit it.
2420
2530
  */
2421
2531
  static deriveSubhostingUrl(baseUrl) {
2422
2532
  try {
@@ -2449,11 +2559,13 @@ var Functions = class _Functions {
2449
2559
  * Invoke an Edge Function.
2450
2560
  *
2451
2561
  * Dispatch order:
2452
- * 1. If the platform's in-process dispatch hook is present, call it directly.
2453
- * This avoids Deno Subhosting's 508 Loop Detected when one bundled
2454
- * function invokes another inside the same deployment.
2455
- * 2. Otherwise, try the configured subhosting URL.
2456
- * 3. On 404 from subhosting, fall back to the proxy path.
2562
+ * 1. If the platform's in-process dispatch hook is present and no foreign
2563
+ * functionsUrl is configured, call it directly (function-to-function
2564
+ * calls inside the same deployment).
2565
+ * 2. If a custom functionsUrl is configured, try it; fall back to the
2566
+ * proxy path on 404 or network failure.
2567
+ * 3. Otherwise, invoke through the main host's /functions/{slug} proxy.
2568
+ * This route works from browsers too (correct CORS).
2457
2569
  *
2458
2570
  * @param slug The function slug to invoke
2459
2571
  * @param options Request options
@@ -2462,7 +2574,7 @@ var Functions = class _Functions {
2462
2574
  const { method = "POST", body, headers = {} } = options;
2463
2575
  const dispatch = globalThis.__insforge_dispatch__;
2464
2576
  const localFunctionsUrl = _Functions.deriveSubhostingUrl(this.http.baseUrl);
2465
- if (typeof dispatch === "function" && !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl) {
2577
+ if (typeof dispatch === "function" && (this.functionsUrl === void 0 || !!localFunctionsUrl && this.functionsUrl === localFunctionsUrl)) {
2466
2578
  try {
2467
2579
  const req = this.buildInProcessRequest(slug, method, body, headers);
2468
2580
  const res = await dispatch(req);
@@ -2497,7 +2609,8 @@ var Functions = class _Functions {
2497
2609
  if (error instanceof Error && error.name === "AbortError") {
2498
2610
  throw error;
2499
2611
  }
2500
- if (error instanceof ResultError && error.statusCode === 404) {
2612
+ const unreachable = error instanceof ResultError && (error.statusCode === 404 || error.statusCode === 0);
2613
+ if (unreachable) {
2501
2614
  } else {
2502
2615
  return {
2503
2616
  data: null,
@@ -2533,6 +2646,17 @@ var Functions = class _Functions {
2533
2646
  // src/modules/realtime.ts
2534
2647
  var CONNECT_TIMEOUT = 1e4;
2535
2648
  var SUBSCRIBE_TIMEOUT = 1e4;
2649
+ var CHANNEL_PREFIX = "realtime:";
2650
+ function normalizeChannelMeta(message) {
2651
+ const channel = message?.meta?.channel;
2652
+ if (typeof channel !== "string" || !channel.startsWith(CHANNEL_PREFIX)) {
2653
+ return message;
2654
+ }
2655
+ return {
2656
+ ...message,
2657
+ meta: { ...message.meta, channel: channel.slice(CHANNEL_PREFIX.length) }
2658
+ };
2659
+ }
2536
2660
  var Realtime = class {
2537
2661
  constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
2538
2662
  this.baseUrl = baseUrl;
@@ -2654,8 +2778,9 @@ var Realtime = class {
2654
2778
  if (event === "realtime:error") {
2655
2779
  return;
2656
2780
  }
2657
- this.applyPresenceEvent(event, message);
2658
- this.notifyListeners(event, message);
2781
+ const normalized = normalizeChannelMeta(message);
2782
+ this.applyPresenceEvent(event, normalized);
2783
+ this.notifyListeners(event, normalized);
2659
2784
  };
2660
2785
  this.connectionAttempt = { id: attemptId, socket, cancel: fail };
2661
2786
  socket.on("connect", onConnect);
@@ -2903,6 +3028,17 @@ var Realtime = class {
2903
3028
  this.socket.emit("realtime:unsubscribe", { channel });
2904
3029
  }
2905
3030
  }
3031
+ /**
3032
+ * Publish an event to a channel.
3033
+ *
3034
+ * Delivery notes (verified against a live backend):
3035
+ * - Payload fields arrive spread onto the received message itself, next to
3036
+ * `meta` - not nested under a `payload` key. Publishing `{ text: "hi" }`
3037
+ * means receivers read `msg.text`.
3038
+ * - The sender receives its own message too. Deduplicate with
3039
+ * `msg.meta.messageId` (or `msg.meta.senderId`) if you also update local
3040
+ * state optimistically.
3041
+ */
2906
3042
  async publish(channel, event, payload) {
2907
3043
  if (!this.socket?.connected) {
2908
3044
  throw new Error(
@@ -2911,6 +3047,12 @@ var Realtime = class {
2911
3047
  }
2912
3048
  this.socket.emit("realtime:publish", { channel, event, payload });
2913
3049
  }
3050
+ /**
3051
+ * Listen for an event by name, across all subscribed channels.
3052
+ * Filter by channel inside the callback: `msg.meta.channel` always matches
3053
+ * the exact name passed to subscribe() (the SDK strips the server's
3054
+ * transport prefix before delivery).
3055
+ */
2914
3056
  on(event, callback) {
2915
3057
  const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
2916
3058
  listeners.add(callback);
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client-DeE086La.cjs';
2
- export { A as AI, a as AccessTokenChangeEvent, b as Auth, c as AuthChangeEvent, d as AuthStateChangeCallback, C as ChatCompletion, e as ChatCompletionChoice, f as ChatCompletionChunk, g as ChatCompletionUsage, h as ConnectionState, D as Database, E as Emails, i as EmbeddingsResult, j as EventCallback, F as FunctionInvokeOptions, k as Functions, H as HttpClient, I as ImageGenerationResult, L as Logger, l as Realtime, S as Storage, m as StorageBucket, n as StorageResponse } from './client-DeE086La.cjs';
3
- import { R as ResultAdminConfig, a as ResultConfig } from './types-CbsaMttt.cjs';
4
- export { A as Analytics, b as AnalyticsOptions, c as ApiError, d as AuthErrorResponse, e as AuthSession, C as ChatCompletionRequest, f as ChatMessageSchema, g as CreateSessionRequest, h as CreateUserRequest, D as DeleteObjectResult, i as DeleteObjectsResponse, E as ERROR_CODES, j as EmbeddingsRequest, k as ErrorCode, I as ImageGenerationRequest, O as OAUTH_PROVIDERS, l as OAuthProvidersSchema, P as PresenceMember, m as ProfileSchema, n as RealtimeErrorPayload, o as ResultError, p as ResultErrorCode, S as SendEmailOptions, q as SendEmailResponse, r as SocketMessage, s as StartThreadOptions, t as StartedThread, u as StorageFileSchema, v as SubscribeResponse, w as Support, x as SupportMessage, y as SupportOptions, z as SupportThread, T as Tool, B as ToolCall, F as ToolChoice, U as UserSchema } from './types-CbsaMttt.cjs';
1
+ import { R as ResultClient } from './client-CJv_awPt.cjs';
2
+ export { A as AI, a as AccessTokenChangeEvent, b as Auth, c as AuthChangeEvent, d as AuthStateChangeCallback, C as ChatCompletion, e as ChatCompletionChoice, f as ChatCompletionChunk, g as ChatCompletionUsage, h as ConnectionState, D as Database, E as Emails, i as EmbeddingsResult, j as EventCallback, F as FunctionInvokeOptions, k as Functions, H as HttpClient, I as ImageGenerationResult, L as Logger, l as Realtime, S as Storage, m as StorageBucket, n as StorageResponse } from './client-CJv_awPt.cjs';
3
+ import { R as ResultAdminConfig, a as ResultConfig } from './types-DSVtGaIA.cjs';
4
+ export { A as Analytics, b as AnalyticsOptions, c as ApiError, d as AuthErrorResponse, e as AuthSession, C as ChatCompletionRequest, f as ChatMessageSchema, g as CreateSessionRequest, h as CreateUserRequest, D as DeleteObjectResult, i as DeleteObjectsResponse, E as ERROR_CODES, j as EmbeddingsRequest, k as ErrorCode, I as ImageGenerationRequest, O as OAUTH_PROVIDERS, l as OAuthProvidersSchema, P as PresenceMember, m as ProfileSchema, n as RealtimeErrorPayload, o as ResultError, p as ResultErrorCode, S as SendEmailOptions, q as SendEmailResponse, r as SocketMessage, s as StartThreadOptions, t as StartedThread, u as StorageFileSchema, v as SubscribeResponse, w as Support, x as SupportMessage, y as SupportOptions, z as SupportThread, T as Tool, B as ToolCall, F as ToolChoice, U as UserSchema } from './types-DSVtGaIA.cjs';
5
5
  import '@supabase/postgrest-js';
6
6
 
7
7
  /**