@bty/feed_app-runtime-sdk 0.0.2

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 ADDED
@@ -0,0 +1,131 @@
1
+ # @bty/feed_app-runtime-sdk
2
+
3
+ Runtime SDK for Feed-App pages.
4
+
5
+ It provides a small set of browser-safe APIs for reading host user context and
6
+ calling the Feed-App AI gateway. The package has no root entry; import from the
7
+ capability sub-entries directly.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pnpm add @bty/feed_app-runtime-sdk
13
+ ```
14
+
15
+ ## User
16
+
17
+ ```ts
18
+ import {
19
+ getAuthTokenAsync,
20
+ getUserInfoAsync,
21
+ } from '@bty/feed_app-runtime-sdk/user'
22
+
23
+ const token = await getAuthTokenAsync()
24
+ const user = await getUserInfoAsync()
25
+ ```
26
+
27
+ `getAuthTokenAsync()` resolves to an auth token when the current host
28
+ environment can provide one. `getUserInfoAsync()` resolves to user information
29
+ or `null`.
30
+
31
+ Both APIs are safe to import during prerender or SSR. In a non-browser
32
+ environment they resolve to empty values instead of touching `window`.
33
+
34
+ ## AI
35
+
36
+ ```ts
37
+ import { configureRuntime, openai, models } from '@bty/feed_app-runtime-sdk/ai'
38
+
39
+ configureRuntime({
40
+ apiBaseUrl: 'https://example.com',
41
+ projectId: 'your-project-id',
42
+ })
43
+
44
+ const availableModels = await models.list()
45
+
46
+ const stream = await openai.chat.completions.create({
47
+ model: availableModels[0]?.id ?? 'gpt-5.4',
48
+ messages: [{ role: 'user', content: 'Hello' }],
49
+ stream: true,
50
+ })
51
+
52
+ for await (const chunk of stream) {
53
+ const text = chunk.choices[0]?.delta?.content
54
+ if (text) {
55
+ console.log(text)
56
+ }
57
+ }
58
+ ```
59
+
60
+ The AI entry supports:
61
+
62
+ - OpenAI-compatible chat completions
63
+ - Anthropic-compatible messages
64
+ - Image generation
65
+ - Text-to-speech
66
+ - Video generation
67
+ - Model listing
68
+ - `AbortSignal` cancellation
69
+ - Structured AI errors
70
+
71
+ ```ts
72
+ import {
73
+ AuthRequiredError,
74
+ RateLimitError,
75
+ openai,
76
+ } from '@bty/feed_app-runtime-sdk/ai'
77
+
78
+ try {
79
+ await openai.chat.completions.create({
80
+ model: 'gpt-5.4',
81
+ messages: [{ role: 'user', content: 'Hello' }],
82
+ })
83
+ } catch (error) {
84
+ if (error instanceof AuthRequiredError) {
85
+ // Ask the user to sign in.
86
+ } else if (error instanceof RateLimitError) {
87
+ // Show a rate limit message.
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## React
93
+
94
+ ```tsx
95
+ import {
96
+ useAiChat,
97
+ useAuthToken,
98
+ useUserInfo,
99
+ } from '@bty/feed_app-runtime-sdk/react'
100
+
101
+ export function App() {
102
+ const { token, loading: tokenLoading } = useAuthToken()
103
+ const { user } = useUserInfo()
104
+ const chat = useAiChat({ model: 'gpt-5.4' })
105
+
106
+ return (
107
+ <main>
108
+ <p>{tokenLoading ? 'Loading...' : user?.nickName}</p>
109
+ <button onClick={() => chat.send('Hello')}>Send</button>
110
+ <button onClick={chat.stop} disabled={!chat.loading}>Stop</button>
111
+ {chat.messages.map((message, index) => (
112
+ <p key={index}>{String(message.content)}</p>
113
+ ))}
114
+ </main>
115
+ )
116
+ }
117
+ ```
118
+
119
+ React is an optional peer dependency. Projects that do not import the `/react`
120
+ entry do not need to install React.
121
+
122
+ ## Published Files
123
+
124
+ The npm package publishes only built output and this README:
125
+
126
+ - `dist/**/*.js`
127
+ - `dist/**/*.d.ts`
128
+ - `README.md`
129
+ - `package.json`
130
+
131
+ Source files and sourcemaps are not included in the npm tarball.
@@ -0,0 +1,78 @@
1
+ import { C as ChatCompletionCreateParams, a as ChatCompletionChunk, b as ChatCompletion, I as ImageGenerateParams, c as ImageGenerateResponse, A as AudioSpeechCreateParams, V as VideoGenerateParams, d as VideoGenerateResponse, M as MessagesCreateParams, e as AnthropicStreamEvent, f as AnthropicMessageResponse, g as ModelInfo } from '../errors-C0zJPCCU.js';
2
+ export { h as AbortedError, i as AiError, j as AiErrorCode, k as AnthropicContentBlock, l as AnthropicMessage, m as AnthropicTool, n as AuthRequiredError, B as BadInputError, o as ChatContentPart, p as ChatMessage, q as ChatTool, r as ChatToolCall, s as ModelsListResponse, N as NetworkError, Q as QuotaExceededError, R as RateLimitError, S as ServerError } from '../errors-C0zJPCCU.js';
3
+
4
+ interface ChatCompletionsCreate {
5
+ (params: ChatCompletionCreateParams & {
6
+ stream: true;
7
+ }): Promise<AsyncIterable<ChatCompletionChunk>>;
8
+ (params: ChatCompletionCreateParams & {
9
+ stream?: false;
10
+ }): Promise<ChatCompletion>;
11
+ }
12
+ declare const openai: {
13
+ chat: {
14
+ completions: {
15
+ create: ChatCompletionsCreate;
16
+ };
17
+ };
18
+ images: {
19
+ /** Generate one or more images from a prompt. Non-streaming. */
20
+ generate(params: ImageGenerateParams): Promise<ImageGenerateResponse>;
21
+ };
22
+ audio: {
23
+ speech: {
24
+ /**
25
+ * Generate speech audio. Returns a Blob — caller decides whether to
26
+ * `URL.createObjectURL` for playback or convert to ArrayBuffer.
27
+ */
28
+ create(params: AudioSpeechCreateParams): Promise<Blob>;
29
+ };
30
+ };
31
+ video: {
32
+ generations: {
33
+ /**
34
+ * Generate one or more videos from a prompt. Non-streaming (gateway may
35
+ * return URLs to long-running jobs depending on model — payload is
36
+ * passed through).
37
+ */
38
+ create(params: VideoGenerateParams): Promise<VideoGenerateResponse>;
39
+ };
40
+ };
41
+ };
42
+
43
+ interface MessagesCreate {
44
+ (params: MessagesCreateParams & {
45
+ stream: true;
46
+ }): Promise<AsyncIterable<AnthropicStreamEvent>>;
47
+ (params: MessagesCreateParams & {
48
+ stream?: false;
49
+ }): Promise<AnthropicMessageResponse>;
50
+ }
51
+ declare const anthropic: {
52
+ messages: {
53
+ create: MessagesCreate;
54
+ };
55
+ };
56
+
57
+ interface ListOptions {
58
+ signal?: AbortSignal;
59
+ /** Re-fetch even when cache is warm. */
60
+ force?: boolean;
61
+ }
62
+ declare const models: {
63
+ list: (options?: ListOptions) => Promise<ModelInfo[]>;
64
+ };
65
+
66
+ interface RuntimeConfig {
67
+ /** Base URL for all reactus-backend calls. Trailing slash stripped. */
68
+ apiBaseUrl: string;
69
+ /** Project ID — sent as `x-bty-app` header. Empty string means "unknown". */
70
+ projectId: string;
71
+ }
72
+ /**
73
+ * Override runtime configuration. Call once at app boot, before any AI calls.
74
+ * Partial — only the fields you pass get overridden.
75
+ */
76
+ declare const configureRuntime: (cfg: Partial<RuntimeConfig>) => void;
77
+
78
+ export { AnthropicMessageResponse, AnthropicStreamEvent, AudioSpeechCreateParams, ChatCompletion, ChatCompletionChunk, ChatCompletionCreateParams, ImageGenerateParams, ImageGenerateResponse, MessagesCreateParams, ModelInfo, type RuntimeConfig, VideoGenerateParams, VideoGenerateResponse, anthropic, configureRuntime, models, openai };
@@ -0,0 +1,3 @@
1
+ var X={version:"0.0.2"};var y="/api/feed-app/runtime/ai",Y=`${y}/models`,H=`${y}/chat/completions`,D=`${y}/messages`,W=`${y}/images/generations`,z=`${y}/audio/speech`,Z=`${y}/video/generations`,ee="Authorization",te="x-bty-app",re="x-bty-extend",ne=X.version;var Be=/\r\n|\r|\n/,Fe=(()=>{let e=new TextDecoder;return (t,r)=>e.decode(t,{stream:r})})();async function*se(e){let t=e.getReader(),r="",n="",s=[],o=()=>{if(s.length===0&&!n)return null;let c={event:n||"message",data:s.join(`
2
+ `)};return n="",s=[],c};try{for(;;){let{done:c,value:p}=await t.read();if(c){let i=o();i&&(yield i);return}for(r+=Fe(p,!0);;){let i=Be.exec(r);if(!i)break;let l=r.slice(0,i.index);if(r=r.slice(i.index+i[0].length),l===""){let f=o();f&&(yield f);continue}if(l.startsWith(":"))continue;let h=l.indexOf(":"),I=h===-1?l:l.slice(0,h),d=h===-1?"":l.slice(h+1);d.startsWith(" ")&&(d=d.slice(1)),I==="event"?n=d:I==="data"&&s.push(d);}}}finally{t.releaseLock();}}async function*oe(e){for await(let t of se(e)){if(t.data==="[DONE]")return;t.data&&(yield JSON.parse(t.data));}}async function*ie(e){for await(let t of se(e))t.data&&(yield JSON.parse(t.data));}var qe="https://reactus-api.betteryeah.com",Ge=()=>{try{if(typeof __BTY_RUNTIME_API_BASE_URL__=="string"&&__BTY_RUNTIME_API_BASE_URL__.length>0)return __BTY_RUNTIME_API_BASE_URL__}catch{}},B={apiBaseUrl:(Ge()??qe).replace(/\/+$/,""),projectId:""},je=e=>{B={...B,...e,...e.apiBaseUrl?{apiBaseUrl:e.apiBaseUrl.replace(/\/+$/,"")}:{}};},P=()=>B;var m=class extends Error{status;code;body;constructor(t,r,n,s=null){super(t),this.name="AiError",this.code=r,this.status=n,this.body=s;}},g=class extends m{constructor(t="Auth required",r=null){super(t,"auth_required",401,r),this.name="AuthRequiredError";}},O=class extends m{constructor(t="Rate limit exceeded",r=null){super(t,"rate_limit",429,r),this.name="RateLimitError";}},M=class extends m{constructor(t="Quota exceeded",r=null){super(t,"quota_exceeded",402,r),this.name="QuotaExceededError";}},N=class extends m{constructor(t,r=400,n=null){super(t,"bad_input",r,n),this.name="BadInputError";}},k=class extends m{constructor(t,r=500,n=null){super(t,"server",r,n),this.name="ServerError";}},A=class extends m{constructor(t="Network error",r){super(t,"network",-1,r),this.name="NetworkError";}},S=class extends m{constructor(t="Request aborted"){super(t,"aborted",-1,null),this.name="AbortedError";}},ae=e=>typeof e=="object"&&e!==null,Ve=(e,t)=>{if(typeof e=="string")return e||t;if(!ae(e))return t;let r=e.message;if(typeof r=="string"&&r.length>0)return r;let n=e.error;if(typeof n=="string"&&n.length>0)return n;if(ae(n)&&typeof n.message=="string"&&n.message.length>0)return n.message;let s=e.detail;return typeof s=="string"&&s.length>0?s:t},ce=(e,t)=>{let r=Ve(t,`HTTP ${e}`);return e===401||e===403?new g(r,t):e===402?new M(r,t):e===429?new O(r,t):e>=400&&e<500?new N(r,e,t):e>=500?new k(r,e,t):new m(r,"unknown",e,t)};var u=()=>typeof window<"u"&&typeof document<"u";var Je="hostRuntime",F=class{listeners=new Map;on(t,r){let n=this.listeners.get(t),s=n??new Set;return n||this.listeners.set(t,s),s.add(r),()=>{s.delete(r),s.size===0&&this.listeners.delete(t);}}emit(t,r){let n=this.listeners.get(t);if(n)for(let s of n)s(r);}},a=new F,pe=0,q=e=>(pe+=1,`${e}.${pe}`),ue=false,le=()=>{if(!u()||ue)return;ue=true,Reflect.set(window,Je,{receiveMessage(t){if(!t)return;let r=t.endpoint;r&&a.emit(r,t.data??t);}});};var C=e=>typeof e=="object"&&e!==null,R=(e,t)=>{if(e)for(let r of t){let n=e[r];if(typeof n=="string"&&n.length>0)return n}},E=e=>{if(!C(e))return null;let t=C(e.credentials)?e.credentials:void 0,r=C(e.user)?e.user:void 0,n=R(e,["uid","userId","id"])??R(t,["uid","userId","id"])??R(r,["uid","userId","id"]),s=R(e,["token","authToken"])??R(t,["token","authToken"]);return !n||!s?null:{uid:n,token:s}},b=e=>{if(!C(e))return null;let t=C(e.data)?e.data:e,r=R(t,["uid","userId","id"]);return r?{...t,uid:r}:null};var de="HostApp",G="hostListener";var me="processUserCredentials",fe="processUserInfo",Ee="user.getCredentials";var _="user.credentials",j="user.info",ge="user-credentials-request",he="user-credentials-response";var ye="user-info-response",Ae={iOS:[1,6,7],Android:[1,1,8]},Re=3e3,_e=500,Te=50,Se=1500;var $e=e=>typeof e!="object"||e===null?false:typeof Reflect.get(e,"type")=="string",Ce=false,we=()=>{!u()||Ce||(Ce=true,le(),Reflect.set(window,me,e=>{E(e)&&a.emit(_,e);}),Reflect.set(window,fe,e=>{b(e)&&a.emit(j,e);}),window.addEventListener("message",e=>{let t=e.data;if($e(t))switch(t.type){case he:a.emit("iframe.credentials",t);break;case ye:a.emit("iframe.userinfo",t);break}}));};var Qe=new RegExp(`${de}\\/(\\d+\\.\\d+\\.\\d+)\\/(\\d+)\\/(iOS|Android)`),Ke=e=>{let t=e.split(".").map(r=>Number.parseInt(r,10));return [t[0]??0,t[1]??0,t[2]??0]},Xe=(e,t)=>{for(let r=0;r<3;r++){if(e[r]>t[r])return true;if(e[r]<t[r])return false}return true},Ye=e=>{let t=e.match(Qe);if(!t)return null;let[,r,n,s]=t;if(!r||!n||s!=="iOS"&&s!=="Android")return null;let o=Xe(Ke(r),Ae[s]);return {type:"native_app",platform:s,appVersion:r,buildNumber:n,meetsMinVersion:o}},xe=()=>{if(!u())return {type:"web",meetsMinVersion:false};let e=Ye(navigator.userAgent??"");return e||(window.parent!==window?{type:"iframe",meetsMinVersion:false}:{type:"web",meetsMinVersion:false})};var We=(e,t,r)=>new Promise(n=>{let s=false,o=i=>{s||(s=true,c(),clearTimeout(p),n(i));},c=a.on(t,i=>{let l=r(i);l&&o(l);}),p=setTimeout(()=>o(null),_e);try{window.parent.postMessage({type:e,timestamp:Date.now()},"*");}catch{o(null);}}),Ie=()=>We(ge,"iframe.credentials",E);var Pe=e=>typeof e!="object"||e===null?false:typeof Reflect.get(e,"postMessage")=="function",V=e=>{let t=Reflect.get(window,"webkit");if(typeof t=="object"&&t!==null){let n=Reflect.get(t,"messageHandlers");if(typeof n=="object"&&n!==null){let s=Reflect.get(n,e);if(Pe(s))return s}}let r=Reflect.get(window,e);return Pe(r)?r:null},Oe=(e,t={})=>{let{pollIntervalMs:r=50,timeoutMs:n=1500}=t;return new Promise(s=>{let o=Date.now(),c=()=>{let p=V(e);if(p){s(p);return}if(Date.now()-o>=n){s(null);return}setTimeout(c,r);};c();})};var ze=async(e,t,r,n)=>{let s=e.platform==="Android"?await Oe(G,{pollIntervalMs:Te,timeoutMs:Se}):V(G);if(!s)return null;let o=q(r);return new Promise(c=>{let p=false,i=d=>{p||(p=true,l(),h(),clearTimeout(I),c(d));},l=a.on(o,d=>{let f=n(d);f&&i(f);}),h=a.on(r,d=>{let f=n(d);f&&i(f);}),I=setTimeout(()=>i(null),Re);try{s.postMessage({command:t,parameters:JSON.stringify({timestamp:Date.now(),endpoint:o})});}catch{i(null);}})},Me=e=>ze(e,Ee,_,E);we();var J=null,w=null,Ze=e=>{switch(e.type){case "native_app":return e.meetsMinVersion?Me(e):Promise.resolve(null);case "iframe":return Ie();case "web":return Promise.resolve(null)}};var Ne=e=>{J=e;},ke=()=>w||(w=Ze(xe()).then(e=>(e&&Ne(e),e)).finally(()=>{w=null;}),w);u()&&a.on(_,e=>{let t=E(e);t&&Ne(t);});var $=async()=>u()?J?J.token:(await ke())?.token??"":"",be=async()=>u()?(await ke())?.token??"":"";var et=e=>JSON.stringify({"sdk-version":ne,...e}),ve=async(e={})=>{let{projectId:t}=P(),r=await $(),n=new Headers;if(r&&n.set(ee,`Bearer ${r}`),t&&n.set(te,t),n.set(re,et(e.extend)),e.contentType&&n.set("Content-Type",e.contentType),e.accept&&n.set("Accept",e.accept),e.extra)for(let[s,o]of Object.entries(e.extra))n.set(s,o);return n};var tt="feed-app-runtime-sdk:auth-required",Q=e=>{u()&&window.dispatchEvent(new CustomEvent(tt,{detail:e}));};var rt=e=>e instanceof DOMException&&e.name==="AbortError",nt=async e=>{let t=e.headers.get("content-type")??"";try{return t.includes("application/json")?await e.json():await e.text()}catch{return null}},Ue=async(e,t)=>{let{apiBaseUrl:r}=P(),n=`${r}${e}`,s=await ve(t),o;try{o=await fetch(n,{method:t.method??"POST",headers:s,body:t.body,signal:t.signal});}catch(p){throw rt(p)?new S:new A("Failed to reach AI backend",p)}if(o.ok)return o;let c=await nt(o);throw ce(o.status,c)},v=async(e,t={})=>{try{return await Ue(e,t)}catch(r){if(r instanceof g&&!t.skipAuthRetry&&!t.signal?.aborted){if(!await be())throw Q({reason:"refresh_failed",error:r}),r;try{return await Ue(e,{...t,skipAuthRetry:!0})}catch(s){throw s instanceof g&&Q({reason:"retry_rejected",error:s}),s}}throw r}},T=async(e,t,r={})=>await(await v(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)})).json(),Le=async(e,t={})=>await(await v(e,{...t,method:"GET"})).json(),U=async(e,t,r={})=>{let n=await v(e,{...r,method:"POST",contentType:"application/json",accept:"text/event-stream",body:JSON.stringify(t)});if(!n.body)throw new A("Streaming response has no body");return n.body},He=async(e,t,r={})=>await(await v(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)})).blob();var L=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},st=(async e=>{let{transport:t,payload:r}=L(e);if(e.stream){let n=await U(H,r,t);return oe(n)}return await T(H,r,t)}),ot={create:st},it={async generate(e){let{transport:t,payload:r}=L(e);return T(W,r,t)}},at={speech:{async create(e){let{transport:t,payload:r}=L(e);return He(z,r,t)}}},ct={generations:{async create(e){let{transport:t,payload:r}=L(e);return T(Z,r,t)}}},pt={chat:{completions:ot},images:it,audio:at,video:ct};var ut=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},lt=(async e=>{let{transport:t,payload:r}=ut(e);if(e.stream){let n=await U(D,r,t);return ie(n)}return await T(D,r,t)}),dt={messages:{create:lt}};var K=null,x=null,mt=async e=>(await Le(Y,{signal:e})).data??[],ft=async(e={})=>!e.force&&K?K:(!e.force&&x||(x=mt(e.signal).then(t=>(K=t,t)).finally(()=>{x=null;})),x),Et={list:ft};
3
+ export{S as AbortedError,m as AiError,g as AuthRequiredError,N as BadInputError,A as NetworkError,M as QuotaExceededError,O as RateLimitError,k as ServerError,dt as anthropic,je as configureRuntime,Et as models,pt as openai};
@@ -0,0 +1,295 @@
1
+ interface ApiRequestOptions {
2
+ /** AbortSignal — propagated through fetch into the upstream stream. */
3
+ signal?: AbortSignal;
4
+ /** Extra HTTP headers merged on top of SDK-injected ones. */
5
+ headers?: Record<string, string>;
6
+ }
7
+ interface ChatMessage {
8
+ role: "system" | "user" | "assistant" | "tool";
9
+ content: string | ChatContentPart[];
10
+ name?: string;
11
+ tool_call_id?: string;
12
+ tool_calls?: ChatToolCall[];
13
+ [key: string]: unknown;
14
+ }
15
+ interface ChatContentPart {
16
+ type: "text" | "image_url" | "input_audio";
17
+ text?: string;
18
+ image_url?: {
19
+ url: string;
20
+ detail?: "low" | "high" | "auto";
21
+ };
22
+ input_audio?: {
23
+ data: string;
24
+ format: "wav" | "mp3";
25
+ };
26
+ [key: string]: unknown;
27
+ }
28
+ interface ChatToolCall {
29
+ id: string;
30
+ type: "function";
31
+ function: {
32
+ name: string;
33
+ arguments: string;
34
+ };
35
+ }
36
+ interface ChatTool {
37
+ type: "function";
38
+ function: {
39
+ name: string;
40
+ description?: string;
41
+ parameters?: Record<string, unknown>;
42
+ };
43
+ }
44
+ interface ChatCompletionCreateParams extends ApiRequestOptions {
45
+ model: string;
46
+ messages: ChatMessage[];
47
+ stream?: boolean;
48
+ temperature?: number;
49
+ top_p?: number;
50
+ max_tokens?: number;
51
+ tools?: ChatTool[];
52
+ tool_choice?: "auto" | "none" | "required" | {
53
+ type: string;
54
+ function: {
55
+ name: string;
56
+ };
57
+ };
58
+ response_format?: {
59
+ type: "text" | "json_object" | "json_schema";
60
+ [key: string]: unknown;
61
+ };
62
+ [key: string]: unknown;
63
+ }
64
+ interface ChatCompletion {
65
+ id: string;
66
+ object: "chat.completion";
67
+ created: number;
68
+ model: string;
69
+ choices: {
70
+ index: number;
71
+ message: {
72
+ role: "assistant";
73
+ content: string | null;
74
+ tool_calls?: ChatToolCall[];
75
+ };
76
+ finish_reason: string | null;
77
+ }[];
78
+ usage?: {
79
+ prompt_tokens: number;
80
+ completion_tokens: number;
81
+ total_tokens: number;
82
+ };
83
+ [key: string]: unknown;
84
+ }
85
+ interface ChatCompletionChunk {
86
+ id: string;
87
+ object: "chat.completion.chunk";
88
+ created: number;
89
+ model: string;
90
+ choices: {
91
+ index: number;
92
+ delta: {
93
+ role?: "assistant";
94
+ content?: string;
95
+ tool_calls?: Partial<ChatToolCall>[];
96
+ };
97
+ finish_reason: string | null;
98
+ }[];
99
+ [key: string]: unknown;
100
+ }
101
+ interface ImageGenerateParams extends ApiRequestOptions {
102
+ prompt: string;
103
+ model?: string;
104
+ n?: number;
105
+ size?: string;
106
+ quality?: "standard" | "hd";
107
+ style?: "vivid" | "natural";
108
+ response_format?: "url" | "b64_json";
109
+ [key: string]: unknown;
110
+ }
111
+ interface ImageGenerateResponse {
112
+ created: number;
113
+ data: {
114
+ url?: string;
115
+ b64_json?: string;
116
+ revised_prompt?: string;
117
+ }[];
118
+ }
119
+ interface AudioSpeechCreateParams extends ApiRequestOptions {
120
+ model: string;
121
+ input: string;
122
+ voice: string;
123
+ response_format?: "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm";
124
+ speed?: number;
125
+ [key: string]: unknown;
126
+ }
127
+ interface VideoGenerateParams extends ApiRequestOptions {
128
+ prompt: string;
129
+ model?: string;
130
+ duration?: number;
131
+ size?: string;
132
+ [key: string]: unknown;
133
+ }
134
+ interface VideoGenerateResponse {
135
+ created: number;
136
+ data: {
137
+ url?: string;
138
+ b64_json?: string;
139
+ }[];
140
+ [key: string]: unknown;
141
+ }
142
+ interface AnthropicMessage {
143
+ role: "user" | "assistant";
144
+ content: string | AnthropicContentBlock[];
145
+ }
146
+ type AnthropicContentBlock = {
147
+ type: "text";
148
+ text: string;
149
+ } | {
150
+ type: "image";
151
+ source: {
152
+ type: "base64";
153
+ media_type: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
154
+ data: string;
155
+ };
156
+ } | {
157
+ type: "tool_use";
158
+ id: string;
159
+ name: string;
160
+ input: Record<string, unknown>;
161
+ } | {
162
+ type: "tool_result";
163
+ tool_use_id: string;
164
+ content: string | AnthropicContentBlock[];
165
+ };
166
+ interface AnthropicTool {
167
+ name: string;
168
+ description?: string;
169
+ input_schema: Record<string, unknown>;
170
+ }
171
+ interface MessagesCreateParams extends ApiRequestOptions {
172
+ model: string;
173
+ messages: AnthropicMessage[];
174
+ max_tokens: number;
175
+ system?: string | AnthropicContentBlock[];
176
+ stream?: boolean;
177
+ temperature?: number;
178
+ top_p?: number;
179
+ top_k?: number;
180
+ tools?: AnthropicTool[];
181
+ tool_choice?: {
182
+ type: "auto" | "any" | "tool";
183
+ name?: string;
184
+ };
185
+ stop_sequences?: string[];
186
+ [key: string]: unknown;
187
+ }
188
+ interface AnthropicMessageResponse {
189
+ id: string;
190
+ type: "message";
191
+ role: "assistant";
192
+ model: string;
193
+ content: AnthropicContentBlock[];
194
+ stop_reason: string | null;
195
+ stop_sequence: string | null;
196
+ usage: {
197
+ input_tokens: number;
198
+ output_tokens: number;
199
+ };
200
+ [key: string]: unknown;
201
+ }
202
+ type AnthropicStreamEvent = {
203
+ type: "message_start";
204
+ message: AnthropicMessageResponse;
205
+ } | {
206
+ type: "content_block_start";
207
+ index: number;
208
+ content_block: AnthropicContentBlock;
209
+ } | {
210
+ type: "content_block_delta";
211
+ index: number;
212
+ delta: {
213
+ type: "text_delta";
214
+ text: string;
215
+ } | {
216
+ type: "input_json_delta";
217
+ partial_json: string;
218
+ };
219
+ } | {
220
+ type: "content_block_stop";
221
+ index: number;
222
+ } | {
223
+ type: "message_delta";
224
+ delta: {
225
+ stop_reason: string | null;
226
+ stop_sequence: string | null;
227
+ };
228
+ usage: {
229
+ output_tokens: number;
230
+ };
231
+ } | {
232
+ type: "message_stop";
233
+ } | {
234
+ type: "ping";
235
+ } | {
236
+ type: "error";
237
+ error: {
238
+ type: string;
239
+ message: string;
240
+ };
241
+ };
242
+ interface ModelInfo {
243
+ id: string;
244
+ /** Which protocol this model is served via. */
245
+ protocol: "openai" | "anthropic";
246
+ /** Capability flags — drives CodingAgent's choice. */
247
+ capabilities: {
248
+ chat?: boolean;
249
+ images?: boolean;
250
+ audio?: boolean;
251
+ video?: boolean;
252
+ vision?: boolean;
253
+ tools?: boolean;
254
+ };
255
+ /** Free-form descriptive label, safe to show in UI. */
256
+ display_name?: string;
257
+ /** Approximate max context tokens — for UX hints. */
258
+ context_window?: number;
259
+ [key: string]: unknown;
260
+ }
261
+ interface ModelsListResponse {
262
+ object: "list";
263
+ data: ModelInfo[];
264
+ }
265
+
266
+ type AiErrorCode = "auth_required" | "rate_limit" | "quota_exceeded" | "bad_input" | "server" | "network" | "aborted" | "unknown";
267
+ declare class AiError extends Error {
268
+ readonly status: number;
269
+ readonly code: AiErrorCode;
270
+ readonly body: unknown;
271
+ constructor(message: string, code: AiErrorCode, status: number, body?: unknown);
272
+ }
273
+ declare class AuthRequiredError extends AiError {
274
+ constructor(message?: string, body?: unknown);
275
+ }
276
+ declare class RateLimitError extends AiError {
277
+ constructor(message?: string, body?: unknown);
278
+ }
279
+ declare class QuotaExceededError extends AiError {
280
+ constructor(message?: string, body?: unknown);
281
+ }
282
+ declare class BadInputError extends AiError {
283
+ constructor(message: string, status?: number, body?: unknown);
284
+ }
285
+ declare class ServerError extends AiError {
286
+ constructor(message: string, status?: number, body?: unknown);
287
+ }
288
+ declare class NetworkError extends AiError {
289
+ constructor(message?: string, cause?: unknown);
290
+ }
291
+ declare class AbortedError extends AiError {
292
+ constructor(message?: string);
293
+ }
294
+
295
+ export { type AudioSpeechCreateParams as A, BadInputError as B, type ChatCompletionCreateParams as C, type ImageGenerateParams as I, type MessagesCreateParams as M, NetworkError as N, QuotaExceededError as Q, RateLimitError as R, ServerError as S, type VideoGenerateParams as V, type ChatCompletionChunk as a, type ChatCompletion as b, type ImageGenerateResponse as c, type VideoGenerateResponse as d, type AnthropicStreamEvent as e, type AnthropicMessageResponse as f, type ModelInfo as g, AbortedError as h, AiError as i, type AiErrorCode as j, type AnthropicContentBlock as k, type AnthropicMessage as l, type AnthropicTool as m, AuthRequiredError as n, type ChatContentPart as o, type ChatMessage as p, type ChatTool as q, type ChatToolCall as r, type ModelsListResponse as s };
@@ -0,0 +1,72 @@
1
+ import { U as UserInfo } from '../types-CLPiEZOp.js';
2
+ import { p as ChatMessage, i as AiError } from '../errors-C0zJPCCU.js';
3
+
4
+ interface UseAuthTokenResult {
5
+ token: string;
6
+ /** 首次 fetch 或主动 refetch 未完成期间为 true。 */
7
+ loading: boolean;
8
+ /** 主动重新拉取;命中 SDK 凭证缓存时不会重复访问宿主。 */
9
+ refetch: () => void;
10
+ }
11
+ /**
12
+ * 鉴权 token。挂载时拉一次,业务需要刷新时调用 refetch。
13
+ *
14
+ * 注意:token 是字符串,不是 Credentials。如果你需要 uid,配合
15
+ * `useUserInfo()` 用——uid 在那里。
16
+ */
17
+ declare const useAuthToken: () => UseAuthTokenResult;
18
+
19
+ interface UseUserInfoResult {
20
+ user: UserInfo | null;
21
+ loading: boolean;
22
+ refetch: () => void;
23
+ }
24
+ /**
25
+ * 用户信息。同 useAuthToken:挂载拉一次,业务需要刷新时调用 refetch。
26
+ */
27
+ declare const useUserInfo: () => UseUserInfoResult;
28
+
29
+ type ChatProtocol = "openai" | "anthropic";
30
+ interface UseAiChatOptions {
31
+ /** 模型 ID(不维护别名,直接传 models.list() 返回的 id)。 */
32
+ model: string;
33
+ /** 默认 openai;anthropic 时按 Anthropic 协议路径走。 */
34
+ protocol?: ChatProtocol;
35
+ /** anthropic 协议下必填,openai 协议下可选。 */
36
+ maxTokens?: number;
37
+ /** 业务可选:每个 token 来时回调(用于打字机效果 / token 计数)。 */
38
+ onToken?: (delta: string) => void;
39
+ }
40
+ interface UseAiChatResult {
41
+ /** 完整消息历史。user 发问后立即 append,assistant 流式 token 边到边补。 */
42
+ messages: ChatMessage[];
43
+ /** 当前 assistant 正在拼接的文本(已含在 messages[-1] 里,单独暴露便于渲染 cursor)。 */
44
+ pending: string;
45
+ /** 是否有正在进行的请求。 */
46
+ loading: boolean;
47
+ /** 最近一次错误(AiError 子类 / Error / null)。 */
48
+ error: AiError | Error | null;
49
+ /** 发送一条 user 消息,触发 assistant 流式应答。 */
50
+ send: (content: string) => Promise<void>;
51
+ /** 终止当前 in-flight 请求。 */
52
+ stop: () => void;
53
+ /** 重置历史 + 错误。 */
54
+ reset: () => void;
55
+ }
56
+ /**
57
+ * 流式 chat 的最小 stateful hook。仅服务"和模型来回对话"的常见用例;
58
+ * 高级功能(工具调用、多轮 system prompt、image 输入)用 openai/anthropic
59
+ * 命名空间直接写。
60
+ *
61
+ * 协议差异:
62
+ * - openai:response 是 `chunk.choices[0].delta.content` 拼接
63
+ * - anthropic:response 是 `content_block_delta` 里 `text_delta.text` 拼接
64
+ *
65
+ * 错误处理:
66
+ * - AuthRequiredError → SDK 已强制刷新 token 并重试一次;这里只暴露最终结果,
67
+ * 同时会发出 auth-required 事件给容器处理
68
+ * - AbortedError → 用户主动 stop,error 仍会被设置,业务方按需忽略
69
+ */
70
+ declare const useAiChat: (options: UseAiChatOptions) => UseAiChatResult;
71
+
72
+ export { type ChatProtocol, type UseAiChatOptions, type UseAiChatResult, type UseAuthTokenResult, type UseUserInfoResult, useAiChat, useAuthToken, useUserInfo };
@@ -0,0 +1,3 @@
1
+ import {useState,useCallback,useEffect,useRef}from'react';var g=()=>typeof window<"u"&&typeof document<"u";var it="hostRuntime",q=class{listeners=new Map;on(t,n){let r=this.listeners.get(t),s=r??new Set;return r||this.listeners.set(t,s),s.add(n),()=>{s.delete(n),s.size===0&&this.listeners.delete(t);}}emit(t,n){let r=this.listeners.get(t);if(r)for(let s of r)s(n);}},m=new q,oe=0,G=e=>(oe+=1,`${e}.${oe}`),ie=false,ae=()=>{if(!g()||ie)return;ie=true,Reflect.set(window,it,{receiveMessage(t){if(!t)return;let n=t.endpoint;n&&m.emit(n,t.data??t);}});};var k=e=>typeof e=="object"&&e!==null,R=(e,t)=>{if(e)for(let n of t){let r=e[n];if(typeof r=="string"&&r.length>0)return r}},y=e=>{if(!k(e))return null;let t=k(e.credentials)?e.credentials:void 0,n=k(e.user)?e.user:void 0,r=R(e,["uid","userId","id"])??R(t,["uid","userId","id"])??R(n,["uid","userId","id"]),s=R(e,["token","authToken"])??R(t,["token","authToken"]);return !r||!s?null:{uid:r,token:s}},_=e=>{if(!k(e))return null;let t=k(e.data)?e.data:e,n=R(t,["uid","userId","id"]);return n?{...t,uid:n}:null};var ce="HostApp",j="hostListener";var ue="processUserCredentials",pe="processUserInfo",le="user.getCredentials",de="user.getUserInfo",T="user.credentials",O="user.info",me="user-credentials-request",fe="user-credentials-response",ge="user-info-request",Ee="user-info-response",he={iOS:[1,6,7],Android:[1,1,8]},ye=3e3,Ae=500,Re=50,_e=1500;var at=e=>typeof e!="object"||e===null?false:typeof Reflect.get(e,"type")=="string",Te=false,Se=()=>{!g()||Te||(Te=true,ae(),Reflect.set(window,ue,e=>{y(e)&&m.emit(T,e);}),Reflect.set(window,pe,e=>{_(e)&&m.emit(O,e);}),window.addEventListener("message",e=>{let t=e.data;if(at(t))switch(t.type){case fe:m.emit("iframe.credentials",t);break;case Ee:m.emit("iframe.userinfo",t);break}}));};var ct=new RegExp(`${ce}\\/(\\d+\\.\\d+\\.\\d+)\\/(\\d+)\\/(iOS|Android)`),ut=e=>{let t=e.split(".").map(n=>Number.parseInt(n,10));return [t[0]??0,t[1]??0,t[2]??0]},pt=(e,t)=>{for(let n=0;n<3;n++){if(e[n]>t[n])return true;if(e[n]<t[n])return false}return true},lt=e=>{let t=e.match(ct);if(!t)return null;let[,n,r,s]=t;if(!n||!r||s!=="iOS"&&s!=="Android")return null;let o=pt(ut(n),he[s]);return {type:"native_app",platform:s,appVersion:n,buildNumber:r,meetsMinVersion:o}},V=()=>{if(!g())return {type:"web",meetsMinVersion:false};let e=lt(navigator.userAgent??"");return e||(window.parent!==window?{type:"iframe",meetsMinVersion:false}:{type:"web",meetsMinVersion:false})};var Ce=(e,t,n)=>new Promise(r=>{let s=false,o=i=>{s||(s=true,a(),clearTimeout(p),r(i));},a=m.on(t,i=>{let f=n(i);f&&o(f);}),p=setTimeout(()=>o(null),Ae);try{window.parent.postMessage({type:e,timestamp:Date.now()},"*");}catch{o(null);}}),xe=()=>Ce(me,"iframe.credentials",y),we=()=>Ce(ge,"iframe.userinfo",_);var Ie=e=>typeof e!="object"||e===null?false:typeof Reflect.get(e,"postMessage")=="function",J=e=>{let t=Reflect.get(window,"webkit");if(typeof t=="object"&&t!==null){let r=Reflect.get(t,"messageHandlers");if(typeof r=="object"&&r!==null){let s=Reflect.get(r,e);if(Ie(s))return s}}let n=Reflect.get(window,e);return Ie(n)?n:null},Pe=(e,t={})=>{let{pollIntervalMs:n=50,timeoutMs:r=1500}=t;return new Promise(s=>{let o=Date.now(),a=()=>{let p=J(e);if(p){s(p);return}if(Date.now()-o>=r){s(null);return}setTimeout(a,n);};a();})};var ke=async(e,t,n,r)=>{let s=e.platform==="Android"?await Pe(j,{pollIntervalMs:Re,timeoutMs:_e}):J(j);if(!s)return null;let o=G(n);return new Promise(a=>{let p=false,i=c=>{p||(p=true,f(),h(),clearTimeout(A),a(c));},f=m.on(o,c=>{let l=r(c);l&&i(l);}),h=m.on(n,c=>{let l=r(c);l&&i(l);}),A=setTimeout(()=>i(null),ye);try{s.postMessage({command:t,parameters:JSON.stringify({timestamp:Date.now(),endpoint:o})});}catch{i(null);}})},be=e=>ke(e,le,T,y),ve=e=>ke(e,de,O,_);Se();var $=null,b=null,dt=e=>{switch(e.type){case "native_app":return e.meetsMinVersion?be(e):Promise.resolve(null);case "iframe":return xe();case "web":return Promise.resolve(null)}},mt=e=>{switch(e.type){case "native_app":return e.meetsMinVersion?ve(e):Promise.resolve(null);case "iframe":return we();case "web":return Promise.resolve(null)}},Oe=e=>{$=e;},Ue=()=>b||(b=dt(V()).then(e=>(e&&Oe(e),e)).finally(()=>{b=null;}),b);g()&&m.on(T,e=>{let t=y(e);t&&Oe(t);});var S=async()=>g()?$?$.token:(await Ue())?.token??"":"",Ne=async()=>g()?(await Ue())?.token??"":"",U=async()=>g()?mt(V()):null;var Et=()=>{let[e,t]=useState(""),[n,r]=useState(true),s=useCallback(()=>{r(true),S().then(o=>{t(o),r(false);});},[]);return useEffect(()=>{let o=true;return S().then(a=>{o&&(t(a),r(false));}),()=>{o=false;}},[]),{token:e,loading:n,refetch:s}};var At=()=>{let[e,t]=useState(null),[n,r]=useState(true),s=useCallback(()=>{r(true),U().then(o=>{t(o),r(false);});},[]);return useEffect(()=>{let o=true;return U().then(a=>{o&&(t(a),r(false));}),()=>{o=false;}},[]),{user:e,loading:n,refetch:s}};var Le={version:"0.0.2"};var C="/api/feed-app/runtime/ai",Q=`${C}/chat/completions`,K=`${C}/messages`,De=`${C}/images/generations`,Fe=`${C}/audio/speech`,Be=`${C}/video/generations`,qe="Authorization",je="x-bty-extend",Ve=Le.version;var _t=/\r\n|\r|\n/,Tt=(()=>{let e=new TextDecoder;return (t,n)=>e.decode(t,{stream:n})})();async function*Je(e){let t=e.getReader(),n="",r="",s=[],o=()=>{if(s.length===0&&!r)return null;let a={event:r||"message",data:s.join(`
2
+ `)};return r="",s=[],a};try{for(;;){let{done:a,value:p}=await t.read();if(a){let i=o();i&&(yield i);return}for(n+=Tt(p,!0);;){let i=_t.exec(n);if(!i)break;let f=n.slice(0,i.index);if(n=n.slice(i.index+i[0].length),f===""){let l=o();l&&(yield l);continue}if(f.startsWith(":"))continue;let h=f.indexOf(":"),A=h===-1?f:f.slice(0,h),c=h===-1?"":f.slice(h+1);c.startsWith(" ")&&(c=c.slice(1)),A==="event"?r=c:A==="data"&&s.push(c);}}}finally{t.releaseLock();}}async function*$e(e){for await(let t of Je(e)){if(t.data==="[DONE]")return;t.data&&(yield JSON.parse(t.data));}}async function*Qe(e){for await(let t of Je(e))t.data&&(yield JSON.parse(t.data));}var St="https://reactus-api.betteryeah.com",Ct=()=>{try{if(typeof __BTY_RUNTIME_API_BASE_URL__=="string"&&__BTY_RUNTIME_API_BASE_URL__.length>0)return __BTY_RUNTIME_API_BASE_URL__}catch{}},xt={apiBaseUrl:(Ct()??St).replace(/\/+$/,""),projectId:""};var N=()=>xt;var E=class extends Error{status;code;body;constructor(t,n,r,s=null){super(t),this.name="AiError",this.code=n,this.status=r,this.body=s;}},x=class extends E{constructor(t="Auth required",n=null){super(t,"auth_required",401,n),this.name="AuthRequiredError";}},X=class extends E{constructor(t="Rate limit exceeded",n=null){super(t,"rate_limit",429,n),this.name="RateLimitError";}},Y=class extends E{constructor(t="Quota exceeded",n=null){super(t,"quota_exceeded",402,n),this.name="QuotaExceededError";}},W=class extends E{constructor(t,n=400,r=null){super(t,"bad_input",n,r),this.name="BadInputError";}},z=class extends E{constructor(t,n=500,r=null){super(t,"server",n,r),this.name="ServerError";}},v=class extends E{constructor(t="Network error",n){super(t,"network",-1,n),this.name="NetworkError";}},w=class extends E{constructor(t="Request aborted"){super(t,"aborted",-1,null),this.name="AbortedError";}},Ke=e=>typeof e=="object"&&e!==null,wt=(e,t)=>{if(typeof e=="string")return e||t;if(!Ke(e))return t;let n=e.message;if(typeof n=="string"&&n.length>0)return n;let r=e.error;if(typeof r=="string"&&r.length>0)return r;if(Ke(r)&&typeof r.message=="string"&&r.message.length>0)return r.message;let s=e.detail;return typeof s=="string"&&s.length>0?s:t},Xe=(e,t)=>{let n=wt(t,`HTTP ${e}`);return e===401||e===403?new x(n,t):e===402?new Y(n,t):e===429?new X(n,t):e>=400&&e<500?new W(n,e,t):e>=500?new z(n,e,t):new E(n,"unknown",e,t)};var It=e=>JSON.stringify({"sdk-version":Ve,...e}),Ye=async(e={})=>{let n=await S(),r=new Headers;if(n&&r.set(qe,`Bearer ${n}`),r.set(je,It(e.extend)),e.contentType&&r.set("Content-Type",e.contentType),e.accept&&r.set("Accept",e.accept),e.extra)for(let[s,o]of Object.entries(e.extra))r.set(s,o);return r};var Pt="feed-app-runtime-sdk:auth-required",Z=e=>{g()&&window.dispatchEvent(new CustomEvent(Pt,{detail:e}));};var kt=e=>e instanceof DOMException&&e.name==="AbortError",bt=async e=>{let t=e.headers.get("content-type")??"";try{return t.includes("application/json")?await e.json():await e.text()}catch{return null}},We=async(e,t)=>{let{apiBaseUrl:n}=N(),r=`${n}${e}`,s=await Ye(t),o;try{o=await fetch(r,{method:t.method??"POST",headers:s,body:t.body,signal:t.signal});}catch(p){throw kt(p)?new w:new v("Failed to reach AI backend",p)}if(o.ok)return o;let a=await bt(o);throw Xe(o.status,a)},ee=async(e,t={})=>{try{return await We(e,t)}catch(n){if(n instanceof x&&!t.skipAuthRetry&&!t.signal?.aborted){if(!await Ne())throw Z({reason:"refresh_failed",error:n}),n;try{return await We(e,{...t,skipAuthRetry:!0})}catch(s){throw s instanceof x&&Z({reason:"retry_rejected",error:s}),s}}throw n}},I=async(e,t,n={})=>await(await ee(e,{...n,method:"POST",contentType:"application/json",body:JSON.stringify(t)})).json();var M=async(e,t,n={})=>{let r=await ee(e,{...n,method:"POST",contentType:"application/json",accept:"text/event-stream",body:JSON.stringify(t)});if(!r.body)throw new v("Streaming response has no body");return r.body},ze=async(e,t,n={})=>await(await ee(e,{...n,method:"POST",contentType:"application/json",body:JSON.stringify(t)})).blob();var vt=e=>{let{signal:t,headers:n,...r}=e;return {transport:{signal:t,extra:n},payload:r}},Ot=(async e=>{let{transport:t,payload:n}=vt(e);if(e.stream){let r=await M(K,n,t);return Qe(r)}return await I(K,n,t)}),Ze={messages:{create:Ot}};var H=e=>{let{signal:t,headers:n,...r}=e;return {transport:{signal:t,extra:n},payload:r}},Ut=(async e=>{let{transport:t,payload:n}=H(e);if(e.stream){let r=await M(Q,n,t);return $e(r)}return await I(Q,n,t)}),Nt={create:Ut},Mt={async generate(e){let{transport:t,payload:n}=H(e);return I(De,n,t)}},Ht={speech:{async create(e){let{transport:t,payload:n}=H(e);return ze(Fe,n,t)}}},Lt={generations:{async create(e){let{transport:t,payload:n}=H(e);return I(Be,n,t)}}},et={chat:{completions:Nt},images:Mt,audio:Ht,video:Lt};var Ft=e=>{let{model:t,protocol:n="openai",maxTokens:r=1024,onToken:s}=e,[o,a]=useState([]),[p,i]=useState(""),[f,h]=useState(false),[A,c]=useState(null),l=useRef(null),ne=useRef(s);ne.current=s,useEffect(()=>()=>l.current?.abort(),[]);let nt=useCallback(()=>l.current?.abort(),[]),rt=useCallback(()=>{l.current?.abort(),a([]),i(""),c(null),h(false);},[]),st=useCallback(async re=>{if(!re)return;c(null),i(""),h(true);let D={role:"user",content:re},ot={role:"assistant",content:""};a(u=>[...u,D,ot]);let F=new AbortController;l.current?.abort(),l.current=F;let B="",se=u=>{u&&(B+=u,i(B),a(P=>{let d=P.slice();return d[d.length-1]={role:"assistant",content:B},d}),ne.current?.(u));};try{if(n==="openai"){let u=await et.chat.completions.create({model:t,messages:[...o,D],stream:!0,signal:F.signal});for await(let P of u){let d=P.choices?.[0]?.delta?.content;typeof d=="string"&&se(d);}}else {let u=[...o,D].map(d=>({role:d.role==="assistant"?"assistant":"user",content:typeof d.content=="string"?d.content:""})),P=await Ze.messages.create({model:t,messages:u,max_tokens:r,stream:!0,signal:F.signal});for await(let d of P)d.type==="content_block_delta"&&d.delta.type==="text_delta"&&se(d.delta.text);}}catch(u){u instanceof w||u instanceof E||u instanceof Error?c(u):c(new Error(String(u)));}finally{h(false),i("");}},[r,o,t,n]);return {messages:o,pending:p,loading:f,error:A,send:st,stop:nt,reset:rt}};
3
+ export{Ft as useAiChat,Et as useAuthToken,At as useUserInfo};
@@ -0,0 +1,12 @@
1
+ interface Credentials {
2
+ uid: string;
3
+ token: string;
4
+ }
5
+ interface UserInfo {
6
+ uid: string;
7
+ nickName?: string;
8
+ avatar?: string;
9
+ [key: string]: unknown;
10
+ }
11
+
12
+ export type { Credentials as C, UserInfo as U };
@@ -0,0 +1,7 @@
1
+ import { U as UserInfo } from '../types-CLPiEZOp.js';
2
+ export { C as Credentials } from '../types-CLPiEZOp.js';
3
+
4
+ declare const getAuthTokenAsync: () => Promise<string>;
5
+ declare const getUserInfoAsync: () => Promise<UserInfo | null>;
6
+
7
+ export { UserInfo, getAuthTokenAsync, getUserInfoAsync };
@@ -0,0 +1 @@
1
+ var u=()=>typeof window<"u"&&typeof document<"u";var re="hostRuntime",g=class{listeners=new Map;on(t,n){let r=this.listeners.get(t),s=r??new Set;return r||this.listeners.set(t,s),s.add(n),()=>{s.delete(n),s.size===0&&this.listeners.delete(t);}}emit(t,n){let r=this.listeners.get(t);if(r)for(let s of r)s(n);}},o=new g,U=0,N=e=>(U+=1,`${e}.${U}`),v=false,y=()=>{if(!u()||v)return;v=true,Reflect.set(window,re,{receiveMessage(t){if(!t)return;let n=t.endpoint;n&&o.emit(n,t.data??t);}});};var _=e=>typeof e=="object"&&e!==null,f=(e,t)=>{if(e)for(let n of t){let r=e[n];if(typeof r=="string"&&r.length>0)return r}},a=e=>{if(!_(e))return null;let t=_(e.credentials)?e.credentials:void 0,n=_(e.user)?e.user:void 0,r=f(e,["uid","userId","id"])??f(t,["uid","userId","id"])??f(n,["uid","userId","id"]),s=f(e,["token","authToken"])??f(t,["token","authToken"]);return !r||!s?null:{uid:r,token:s}},m=e=>{if(!_(e))return null;let t=_(e.data)?e.data:e,n=f(t,["uid","userId","id"]);return n?{...t,uid:n}:null};var O="HostApp",A="hostListener";var P="processUserCredentials",k="processUserInfo",x="user.getCredentials",L="user.getUserInfo",d="user.credentials",R="user.info",h="user-credentials-request",F="user-credentials-response",b="user-info-request",H="user-info-response",D={iOS:[1,6,7],Android:[1,1,8]},q=3e3,B=500,V=50,j=1500;var se=e=>typeof e!="object"||e===null?false:typeof Reflect.get(e,"type")=="string",G=false,Q=()=>{!u()||G||(G=true,y(),Reflect.set(window,P,e=>{a(e)&&o.emit(d,e);}),Reflect.set(window,k,e=>{m(e)&&o.emit(R,e);}),window.addEventListener("message",e=>{let t=e.data;if(se(t))switch(t.type){case F:o.emit("iframe.credentials",t);break;case H:o.emit("iframe.userinfo",t);break}}));};var oe=new RegExp(`${O}\\/(\\d+\\.\\d+\\.\\d+)\\/(\\d+)\\/(iOS|Android)`),ie=e=>{let t=e.split(".").map(n=>Number.parseInt(n,10));return [t[0]??0,t[1]??0,t[2]??0]},ue=(e,t)=>{for(let n=0;n<3;n++){if(e[n]>t[n])return true;if(e[n]<t[n])return false}return true},le=e=>{let t=e.match(oe);if(!t)return null;let[,n,r,s]=t;if(!n||!r||s!=="iOS"&&s!=="Android")return null;let i=ue(ie(n),D[s]);return {type:"native_app",platform:s,appVersion:n,buildNumber:r,meetsMinVersion:i}},w=()=>{if(!u())return {type:"web",meetsMinVersion:false};let e=le(navigator.userAgent??"");return e||(window.parent!==window?{type:"iframe",meetsMinVersion:false}:{type:"web",meetsMinVersion:false})};var $=(e,t,n)=>new Promise(r=>{let s=false,i=l=>{s||(s=true,p(),clearTimeout(c),r(l));},p=o.on(t,l=>{let T=n(l);T&&i(T);}),c=setTimeout(()=>i(null),B);try{window.parent.postMessage({type:e,timestamp:Date.now()},"*");}catch{i(null);}}),K=()=>$(h,"iframe.credentials",a),z=()=>$(b,"iframe.userinfo",m);var J=e=>typeof e!="object"||e===null?false:typeof Reflect.get(e,"postMessage")=="function",M=e=>{let t=Reflect.get(window,"webkit");if(typeof t=="object"&&t!==null){let r=Reflect.get(t,"messageHandlers");if(typeof r=="object"&&r!==null){let s=Reflect.get(r,e);if(J(s))return s}}let n=Reflect.get(window,e);return J(n)?n:null},X=(e,t={})=>{let{pollIntervalMs:n=50,timeoutMs:r=1500}=t;return new Promise(s=>{let i=Date.now(),p=()=>{let c=M(e);if(c){s(c);return}if(Date.now()-i>=r){s(null);return}setTimeout(p,n);};p();})};var W=async(e,t,n,r)=>{let s=e.platform==="Android"?await X(A,{pollIntervalMs:V,timeoutMs:j}):M(A);if(!s)return null;let i=N(n);return new Promise(p=>{let c=false,l=E=>{c||(c=true,T(),te(),clearTimeout(ne),p(E));},T=o.on(i,E=>{let I=r(E);I&&l(I);}),te=o.on(n,E=>{let I=r(E);I&&l(I);}),ne=setTimeout(()=>l(null),q);try{s.postMessage({command:t,parameters:JSON.stringify({timestamp:Date.now(),endpoint:i})});}catch{l(null);}})},Y=e=>W(e,x,d,a),Z=e=>W(e,L,R,m);Q();var C=null,S=null,ae=e=>{switch(e.type){case "native_app":return e.meetsMinVersion?Y(e):Promise.resolve(null);case "iframe":return K();case "web":return Promise.resolve(null)}},ce=e=>{switch(e.type){case "native_app":return e.meetsMinVersion?Z(e):Promise.resolve(null);case "iframe":return z();case "web":return Promise.resolve(null)}},ee=e=>{C=e;},pe=()=>S||(S=ae(w()).then(e=>(e&&ee(e),e)).finally(()=>{S=null;}),S);u()&&o.on(d,e=>{let t=a(e);t&&ee(t);});var fe=async()=>u()?C?C.token:(await pe())?.token??"":"";var me=async()=>u()?ce(w()):null;export{fe as getAuthTokenAsync,me as getUserInfoAsync};
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@bty/feed_app-runtime-sdk",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "browser": true,
6
+ "description": "Runtime SDK for feed-app template: auth / AI capabilities, multi-environment bridge (native App / iframe / web).",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "exports": {
12
+ "./user": "./src/user/index.ts",
13
+ "./ai": "./src/ai/index.ts",
14
+ "./react": "./src/react/index.ts"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org/",
19
+ "exports": {
20
+ "./user": {
21
+ "types": "./dist/user/index.d.ts",
22
+ "import": "./dist/user/index.js"
23
+ },
24
+ "./ai": {
25
+ "types": "./dist/ai/index.d.ts",
26
+ "import": "./dist/ai/index.js"
27
+ },
28
+ "./react": {
29
+ "types": "./dist/react/index.d.ts",
30
+ "import": "./dist/react/index.js"
31
+ }
32
+ }
33
+ },
34
+ "peerDependencies": {
35
+ "react": ">=17.0.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "react": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "prepublishOnly": "pnpm build",
48
+ "release": "pnpm publish"
49
+ },
50
+ "devDependencies": {
51
+ "@reactus/tsconfig": "workspace:*",
52
+ "@types/react": "catalog:",
53
+ "happy-dom": "^15.11.7",
54
+ "react": "catalog:",
55
+ "tsup": "^8.3.5",
56
+ "typescript": "catalog:",
57
+ "vite": "catalog:",
58
+ "vitest": "^2.1.8"
59
+ }
60
+ }