@aippy/runtime 0.4.2-dev.2 → 0.4.2-dev.3

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 CHANGED
@@ -127,9 +127,25 @@ const image = await generateImage({
127
127
  console.log(image.image?.base64);
128
128
  ```
129
129
 
130
- The AI module adds `getCredits` and `deductCredits` in the unreleased source; published `@aippy/runtime@0.4.1` does not include them. Use them only after installing a published release containing both exports (`>= 0.4.2`), and verify the installed module declarations before importing.
130
+ ### Credits
131
131
 
132
- The server checks the balance atomically and prevents duplicate charges for the same event and amount; changed charge details return HTTP 409. Insufficient balance uses the existing runtime error, the host `container.message` notification, and a native `wallet.showRecharge` command on the shared iOS/Android `aippyListener`. Call credits functions from UI handlers with error handling. Use explicit deductions for custom actions; model generation already charges credits. See the [AI module](./src/ai/README.md) and `aippy-ai-sdk` skill for usage.
132
+ `@aippy/runtime/credits` provides `getCredits` and `deductCredits` for balance
133
+ queries and user-approved custom credit charges, including non-AI actions.
134
+ The new subpath is unreleased in this change; published `@aippy/runtime@0.4.1`
135
+ does not include it. Install a published release whose package exports and
136
+ installed declarations provide `./credits` and both functions before importing.
137
+ A release that only provides the old AI exports is not sufficient.
138
+
139
+ ```typescript
140
+ import { getCredits, deductCredits } from '@aippy/runtime/credits';
141
+ ```
142
+
143
+ The functions and their options types are no longer exported from
144
+ `@aippy/runtime/ai`; update existing AI imports to the credits subpath. They
145
+ remain available from the root runtime entry. No AI configuration is needed.
146
+ Call them from UI handlers with error handling; model generation already charges
147
+ credits. See the [credits module](./src/credits/README.md) and the
148
+ `aippy-credits-sdk` skill for usage, retry constraints, and host behavior.
133
149
 
134
150
  ## Packages
135
151
 
@@ -138,6 +154,7 @@ The server checks the balance atomically and prevents duplicate charges for the
138
154
  - `@aippy/runtime/utils` - Platform detection, performance monitoring, PWA utilities
139
155
  - `@aippy/runtime/audio` - iOS-compatible Web Audio API wrapper
140
156
  - `@aippy/runtime/ai` - AI SDK adapter wrapping `ai` package, routes through backend proxy
157
+ - `@aippy/runtime/credits` - Current-user credit balance and explicit deductions
141
158
 
142
159
  ## Publishing
143
160
 
@@ -41,6 +41,5 @@ export type { AippyOpenAIConfig } from './openai';
41
41
  export { aippyChatConfig, type AippyUseChatOptions, type AippyUIConfig, type AippyChatConfig, UI_CHAT_ENDPOINT, } from './ui';
42
42
  export { DEFAULT_BASE_URL, DEFAULT_UI_BASE_URL, DEFAULT_CHAT_MODEL, DEFAULT_CHAT_SYSTEM, resolveOpenAIConfig, resolveUIConfig, } from './shared';
43
43
  export { normalizeError } from './errors';
44
- export { getCredits, deductCredits, type CreditsRequestOptions, type DeductCreditsOptions, } from './credits';
45
44
  export { generateCharacter3D, generateMotion, type GenerateCharacter3DInput, type GenerateMotionInput, type ViggleAsset, } from './viggle/generate';
46
45
  export { type AIConfig, type AIConfigValue, type AIConfigItem, type NumberAIConfigItem, type BooleanAIConfigItem, type TextAIConfigItem, type EnumAIConfigItem, type AIConfigError, type AIConfigValues, validateAIConfig, parseAIConfig, loadAIConfig, getAIConfigValue, sendAIConfigToContainer, createAIConfig, } from './config';
package/dist/ai/index.js CHANGED
@@ -1,25 +1,24 @@
1
- import { D as r, d as i, e as n, c as A, U as C, a as t, b as g, m as I, h as f, i as _, j as p, k as E, g as T, l as U, n as d, p as D, r as l, f as L, s as m, v } from "../helper-CCuf-R6I.js";
1
+ import { D as r, d as i, e as n, c as A, U as C, a as g, b as t, j as f, g as I, h as p, i as _, l as E, p as T, r as U, f as D, s as l, v as L } from "../helper-BQmXeuDs.js";
2
2
  import "react";
3
3
  import "../bridge-CtB9L5kL.js";
4
+ import { n as m } from "../fetch-lzas52mc.js";
4
5
  export {
5
6
  r as DEFAULT_BASE_URL,
6
7
  i as DEFAULT_CHAT_MODEL,
7
8
  n as DEFAULT_CHAT_SYSTEM,
8
9
  A as DEFAULT_UI_BASE_URL,
9
10
  C as UI_CHAT_ENDPOINT,
10
- t as aippyAIProvider,
11
- g as aippyChatConfig,
12
- I as createAIConfig,
13
- f as deductCredits,
14
- _ as generateCharacter3D,
11
+ g as aippyAIProvider,
12
+ t as aippyChatConfig,
13
+ f as createAIConfig,
14
+ I as generateCharacter3D,
15
15
  p as generateMotion,
16
- E as getAIConfigValue,
17
- T as getCredits,
18
- U as loadAIConfig,
19
- d as normalizeError,
20
- D as parseAIConfig,
21
- l as resolveOpenAIConfig,
22
- L as resolveUIConfig,
23
- m as sendAIConfigToContainer,
24
- v as validateAIConfig
16
+ _ as getAIConfigValue,
17
+ E as loadAIConfig,
18
+ m as normalizeError,
19
+ T as parseAIConfig,
20
+ U as resolveOpenAIConfig,
21
+ D as resolveUIConfig,
22
+ l as sendAIConfigToContainer,
23
+ L as validateAIConfig
25
24
  };
@@ -1,6 +1,6 @@
1
1
  import "react";
2
2
  import { g as a } from "./bridge-CtB9L5kL.js";
3
- const r = "0.4.2-dev.2", i = {
3
+ const r = "0.4.2-dev.3", i = {
4
4
  version: r
5
5
  }, o = i.version, c = "@aippy/runtime";
6
6
  function w() {
@@ -1,7 +1,7 @@
1
1
  import { D as r, g as s, m as o } from "../config-B0A7gHQM.js";
2
2
  import { _ as t, r as i } from "../bridge-CtB9L5kL.js";
3
3
  import { A as R, a as E, E as c, b as m, c as A } from "../errors-B3bDbQbD.js";
4
- import { S as f, V as g, c as l, a as y, g as _, s as d } from "../container-message-ChLaG4Ke.js";
4
+ import { S as f, V as g, c as l, a as y, g as _, s as d } from "../container-message-D1_iPvJX.js";
5
5
  import { A as D, C as O, R as F, a as I, p as S } from "../runtime-CmoG3v2m.js";
6
6
  export {
7
7
  D as AippyRuntime,
@@ -1,5 +1,6 @@
1
- import { AippyOpenAIConfig } from './shared';
2
- export interface CreditsRequestOptions extends AippyOpenAIConfig {
1
+ export interface CreditsRequestOptions {
2
+ /** Backend API base URL, including /aisdk/v1/; resolved from the page by default. */
3
+ baseUrl?: string;
3
4
  signal?: AbortSignal;
4
5
  }
5
6
  export interface DeductCreditsOptions extends CreditsRequestOptions {
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @aippy/runtime/credits — balance and explicit deductions for Aippy credits.
3
+ */
4
+ export { getCredits, deductCredits, type CreditsRequestOptions, type DeductCreditsOptions, } from './credits';
@@ -0,0 +1,5 @@
1
+ import { d as t, g as r } from "../credits-IWtJtN8O.js";
2
+ export {
3
+ t as deductCredits,
4
+ r as getCredits
5
+ };
@@ -0,0 +1,53 @@
1
+ import { AISDKError as o } from "ai";
2
+ import { c as m } from "./fetch-lzas52mc.js";
3
+ import { r as p } from "./bridge-CtB9L5kL.js";
4
+ import { E as c } from "./errors-B3bDbQbD.js";
5
+ import { j as v } from "./url-c26cuIpu.js";
6
+ async function w(r = {}) {
7
+ const e = await d("GET", "credits", r);
8
+ return (!e || typeof e != "object" || !("credits" in e) || !Number.isSafeInteger(e.credits)) && a(), e.credits;
9
+ }
10
+ async function E(r, e) {
11
+ (!Number.isSafeInteger(r) || r <= 0) && n("deductCredits amount must be a positive safe integer"), (typeof e?.eventId != "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
12
+ e.eventId
13
+ )) && n("deductCredits eventId must be a UUID");
14
+ const t = await d("POST", "credits/deduct", e, {
15
+ amount: r,
16
+ eventId: e.eventId.toLowerCase()
17
+ });
18
+ (!t || typeof t != "object" || !("success" in t) || t.success !== !0) && a();
19
+ }
20
+ async function d(r, e, t, i) {
21
+ const f = t.baseUrl ?? `${p()}/aisdk/v1/`, u = await m()(v(f, e), {
22
+ method: r,
23
+ cache: "no-store",
24
+ signal: t.signal,
25
+ headers: i === void 0 ? void 0 : { "Content-Type": "application/json" },
26
+ body: i === void 0 ? void 0 : JSON.stringify(i)
27
+ });
28
+ try {
29
+ return await u.json();
30
+ } catch (s) {
31
+ if (t.signal?.aborted)
32
+ throw t.signal.reason ?? s;
33
+ if (s instanceof Error && s.name === "AbortError")
34
+ throw s;
35
+ a();
36
+ }
37
+ }
38
+ function n(r) {
39
+ throw new o({
40
+ name: "AippyAIError_credits_input",
41
+ message: `${c.CODE} ${r}`
42
+ });
43
+ }
44
+ function a() {
45
+ throw new o({
46
+ name: "AippyAIError_credits_response",
47
+ message: `${c.RUNTIME} Invalid credits response`
48
+ });
49
+ }
50
+ export {
51
+ E as d,
52
+ w as g
53
+ };
@@ -0,0 +1,2 @@
1
+ export * from './credits/index'
2
+ export {}
@@ -0,0 +1,66 @@
1
+ import { s as i, a as u } from "./container-message-D1_iPvJX.js";
2
+ import { AISDKError as c } from "ai";
3
+ import { E as p } from "./errors-B3bDbQbD.js";
4
+ const f = 100003, m = "insufficient_quota";
5
+ function g(s, r) {
6
+ const t = s.status;
7
+ if (r && typeof r == "object" && "error" in r) {
8
+ const e = r, a = e.error.code, o = a != null ? `AippyAIError_${a}` : `AippyAIError_${t}`, n = r;
9
+ return "appMessage" in n && n.appMessage !== void 0 && i(n.appMessage), l(e.error) && E(), new c({
10
+ name: o,
11
+ message: `${p.RUNTIME} ${e.error.message}`,
12
+ cause: {
13
+ type: e.error.type ?? void 0,
14
+ param: e.error.param ?? void 0,
15
+ status: t
16
+ }
17
+ });
18
+ }
19
+ if (r && typeof r == "object" && r !== null) {
20
+ const e = r;
21
+ "appMessage" in e && e.appMessage !== void 0 && i(e.appMessage);
22
+ }
23
+ return new c({
24
+ name: `AippyAIError_${t}`,
25
+ message: `${p.RUNTIME} Request failed with status ${t}`,
26
+ cause: { status: t }
27
+ });
28
+ }
29
+ function l(s) {
30
+ return s.code === f || s.type === m;
31
+ }
32
+ function E() {
33
+ if (!(typeof window > "u"))
34
+ try {
35
+ const s = window.webkit?.messageHandlers?.aippyListener;
36
+ if (!s)
37
+ return;
38
+ s.postMessage({ command: "wallet.showRecharge" });
39
+ } catch (s) {
40
+ console.warn(
41
+ "⚠️ [Container Message] Failed to request native recharge:",
42
+ s
43
+ );
44
+ }
45
+ }
46
+ function d() {
47
+ return async (s, r) => {
48
+ const t = await u(r?.headers), e = await globalThis.fetch(s, { ...r, headers: t });
49
+ if (!e.ok) {
50
+ const a = await e.text().catch(() => null);
51
+ let o = null;
52
+ if (a)
53
+ try {
54
+ o = JSON.parse(a);
55
+ } catch {
56
+ o = { message: a };
57
+ }
58
+ throw g(e, o);
59
+ }
60
+ return e;
61
+ };
62
+ }
63
+ export {
64
+ d as c,
65
+ g as n
66
+ };
@@ -0,0 +1,299 @@
1
+ import { createOpenAICompatible as I } from "@ai-sdk/openai-compatible";
2
+ import { c as d } from "./fetch-lzas52mc.js";
3
+ import { j as C } from "./url-c26cuIpu.js";
4
+ import { DefaultChatTransport as O, AISDKError as h } from "ai";
5
+ import { E as y, b as p, a as f } from "./errors-B3bDbQbD.js";
6
+ import { r as m } from "./bridge-CtB9L5kL.js";
7
+ const v = `${m()}/aisdk/v1/`, K = `${m()}/aisdk/v1/ui/`, S = "gpt-5-nano", _ = "";
8
+ function E(t = {}) {
9
+ return {
10
+ baseUrl: t.baseUrl ?? `${m()}/aisdk/v1/`
11
+ };
12
+ }
13
+ function T(t = {}) {
14
+ return {
15
+ baseUrl: t.baseUrl ?? `${m()}/aisdk/v1/ui/`
16
+ };
17
+ }
18
+ const U = [
19
+ "gpt-image-1",
20
+ "gpt-image-1-mini",
21
+ "gpt-image-1.5"
22
+ ];
23
+ function D(t) {
24
+ try {
25
+ const e = JSON.parse(t);
26
+ if (e.model && U.some((n) => e.model.startsWith(n))) {
27
+ const { response_format: n, ...o } = e;
28
+ return JSON.stringify(o);
29
+ }
30
+ } catch {
31
+ }
32
+ return t;
33
+ }
34
+ function X(t = {}) {
35
+ const { baseUrl: e } = E(t), n = d();
36
+ return I({
37
+ name: "aippy",
38
+ baseURL: e,
39
+ fetch: async (s, r) => s.toString().includes("/images/generations") && r?.method?.toUpperCase() === "POST" && r?.body ? n(s, {
40
+ ...r,
41
+ body: D(r.body)
42
+ }) : n(s, r),
43
+ // Enable structured outputs support (json_schema response format)
44
+ // This is required for Output.object() and Output.array() to work properly
45
+ supportsStructuredOutputs: !0
46
+ });
47
+ }
48
+ const M = "/chat";
49
+ function Y(t = {}) {
50
+ const { baseUrl: e } = T(t), n = t.model ?? S, o = t.system ?? _, s = t.api ?? C(e, M), r = { model: n, system: o }, a = d();
51
+ return {
52
+ transport: new O({
53
+ api: s,
54
+ body: r,
55
+ // Ensure token is fetched fresh for every request
56
+ fetch: a
57
+ })
58
+ };
59
+ }
60
+ function l(t) {
61
+ return [...t].length;
62
+ }
63
+ const L = 3e3, N = 5e3, F = 300 * 1e3, R = 360 * 1e3;
64
+ async function Q(t) {
65
+ const e = t.prompt?.trim() ?? "", n = t.imageUrl?.trim() ?? "", o = t.image;
66
+ l(e) > 1e3 && u("generateCharacter3D prompt must be at most 1000 characters"), o != null && !(o instanceof Blob) && u("generateCharacter3D image must be a file"), o instanceof Blob && o.size === 0 && u("generateCharacter3D image is empty");
67
+ const s = t;
68
+ return A(
69
+ "viggle/characters",
70
+ o instanceof Blob ? $(o, e) : j(n, e),
71
+ s.pollIntervalMs ?? L,
72
+ s.timeoutMs ?? F,
73
+ t.signal
74
+ );
75
+ }
76
+ function $(t, e) {
77
+ const n = new FormData(), o = t instanceof File && t.name ? t.name : "image.png";
78
+ return n.append("image", t, o), e && n.append("prompt", e), { body: n };
79
+ }
80
+ function j(t, e) {
81
+ !e && !t && u(
82
+ "generateCharacter3D requires an image, an https imageUrl, or a prompt"
83
+ ), t && !t.startsWith("https://") && u("generateCharacter3D imageUrl must be an https URL");
84
+ const n = {};
85
+ return t && (n.imageUrl = t), e && (n.prompt = e), {
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify(n)
88
+ };
89
+ }
90
+ async function Z(t) {
91
+ const e = t.text?.trim() ?? "";
92
+ (l(e) < 1 || l(e) > 400) && u("generateMotion text must be 1 to 400 characters"), t.durationSeconds != null && (!Number.isInteger(t.durationSeconds) || t.durationSeconds < 3 || t.durationSeconds > 60) && u("generateMotion durationSeconds must be an integer from 3 to 60");
93
+ const n = { text: e };
94
+ t.durationSeconds != null && (n.durationSeconds = t.durationSeconds);
95
+ const o = t;
96
+ return A(
97
+ "viggle/motions",
98
+ {
99
+ headers: { "Content-Type": "application/json" },
100
+ body: JSON.stringify(n)
101
+ },
102
+ o.pollIntervalMs ?? N,
103
+ o.timeoutMs ?? R,
104
+ t.signal
105
+ );
106
+ }
107
+ async function A(t, e, n, o, s) {
108
+ const r = d(), a = Date.now();
109
+ let i = await b(r, t, {
110
+ method: "POST",
111
+ ...e,
112
+ signal: s
113
+ });
114
+ for (; i.status !== "ready"; )
115
+ i.status === "failed" && g(i.error || "Viggle generation failed"), Date.now() - a > o && g("Viggle generation timed out"), await P(n, s), i = await b(r, `${t}/${i.id}`, {
116
+ method: "GET",
117
+ signal: s
118
+ });
119
+ return i.url || g("Viggle generation finished without a file URL"), { url: i.url, thumbnailUrl: i.thumbnailUrl };
120
+ }
121
+ async function b(t, e, n) {
122
+ return await (await t(x(v, e), {
123
+ ...n,
124
+ cache: "no-store"
125
+ })).json();
126
+ }
127
+ function x(t, e) {
128
+ return `${t.replace(/\/$/, "")}/${e.replace(/^\//, "")}`;
129
+ }
130
+ function P(t, e) {
131
+ if (e?.aborted)
132
+ throw e.reason ?? new DOMException("The operation was aborted", "AbortError");
133
+ return new Promise((n, o) => {
134
+ const s = setTimeout(n, t);
135
+ e?.addEventListener(
136
+ "abort",
137
+ () => {
138
+ clearTimeout(s), o(
139
+ e.reason ?? new DOMException("The operation was aborted", "AbortError")
140
+ );
141
+ },
142
+ { once: !0 }
143
+ );
144
+ });
145
+ }
146
+ function u(t) {
147
+ throw new h({
148
+ name: "AippyAIError_viggle_input",
149
+ message: `${y.CODE} ${t}`
150
+ });
151
+ }
152
+ function g(t) {
153
+ throw new h({
154
+ name: "AippyAIError_viggle",
155
+ message: `${y.RUNTIME} ${t}`
156
+ });
157
+ }
158
+ function w(t) {
159
+ const e = [];
160
+ if (!t || typeof t != "object")
161
+ throw p("AIConfig must be an object", f.INVALID_CONFIG);
162
+ const n = t;
163
+ for (const [o, s] of Object.entries(n)) {
164
+ if (!s || typeof s != "object") {
165
+ e.push({ key: o, message: "Item must be an object" });
166
+ continue;
167
+ }
168
+ const r = s;
169
+ typeof r.index != "number" && e.push({ key: o, message: "index must be a number" }), typeof r.name != "string" && e.push({ key: o, message: "name must be a string" }), typeof r.description != "string" && e.push({ key: o, message: "description must be a string" });
170
+ const a = r.type;
171
+ if (!["number", "boolean", "text", "enum"].includes(a)) {
172
+ e.push({
173
+ key: o,
174
+ message: `type must be one of: number, boolean, text, enum (got: ${a})`
175
+ });
176
+ continue;
177
+ }
178
+ if (a === "number")
179
+ typeof r.value != "number" && e.push({ key: o, message: 'value must be a number for type "number"' }), r.min !== void 0 && typeof r.min != "number" && e.push({ key: o, message: "min must be a number" }), r.max !== void 0 && typeof r.max != "number" && e.push({ key: o, message: "max must be a number" }), r.step !== void 0 && typeof r.step != "number" && e.push({ key: o, message: "step must be a number" });
180
+ else if (a === "boolean")
181
+ typeof r.value != "boolean" && e.push({ key: o, message: 'value must be a boolean for type "boolean"' });
182
+ else if (a === "text")
183
+ typeof r.value != "string" && e.push({ key: o, message: 'value must be a string for type "text"' });
184
+ else if (a === "enum") {
185
+ if (!Array.isArray(r.options))
186
+ e.push({ key: o, message: 'options must be an array for type "enum"' });
187
+ else if (r.options.length === 0)
188
+ e.push({ key: o, message: 'options array cannot be empty for type "enum"' });
189
+ else
190
+ for (let c = 0; c < r.options.length; c++)
191
+ typeof r.options[c] != "string" && e.push({
192
+ key: o,
193
+ message: `options[${c}] must be a string`
194
+ });
195
+ typeof r.value != "string" ? e.push({ key: o, message: 'value must be a string for type "enum"' }) : Array.isArray(r.options) && !r.options.includes(r.value) && e.push({
196
+ key: o,
197
+ message: `value "${r.value}" is not in options: [${r.options.join(", ")}]`
198
+ });
199
+ }
200
+ r.group !== void 0 && typeof r.group != "string" && e.push({ key: o, message: "group must be a string" });
201
+ }
202
+ if (e.length > 0)
203
+ throw p(
204
+ `AIConfig validation failed with ${e.length} error(s)`,
205
+ f.INVALID_CONFIG,
206
+ { errors: e }
207
+ );
208
+ }
209
+ function ee(t) {
210
+ let e;
211
+ try {
212
+ e = JSON.parse(t);
213
+ } catch (n) {
214
+ throw p(
215
+ `Invalid JSON: ${n instanceof Error ? n.message : String(n)}`,
216
+ f.INVALID_CONFIG
217
+ );
218
+ }
219
+ return w(e), e;
220
+ }
221
+ function B(t) {
222
+ return w(t), t;
223
+ }
224
+ function J(t, e) {
225
+ const n = t[e];
226
+ if (!n)
227
+ throw p(
228
+ `AIConfig key "${e}" not found`,
229
+ f.INVALID_CONFIG,
230
+ { key: e }
231
+ );
232
+ return n.value;
233
+ }
234
+ function V(t) {
235
+ if (!(typeof window > "u"))
236
+ try {
237
+ const e = window.webkit?.messageHandlers?.aippyListener;
238
+ if (e)
239
+ try {
240
+ const n = {
241
+ command: "ai.initialize",
242
+ parameters: JSON.stringify(t)
243
+ };
244
+ e.postMessage(n);
245
+ } catch (n) {
246
+ console.warn("❌ [Aippy AI Config] Failed to send config to iOS app:", n);
247
+ }
248
+ if (window.parent && window.parent !== window)
249
+ try {
250
+ const n = {
251
+ type: "ai.initialize",
252
+ config: JSON.stringify(t)
253
+ };
254
+ window.parent.postMessage(n, "*");
255
+ } catch (n) {
256
+ console.warn("❌ [Aippy AI Config] Failed to send config to parent window:", n);
257
+ }
258
+ } catch (e) {
259
+ console.warn("⚠️ [Aippy AI Config] Failed to send config:", e);
260
+ }
261
+ }
262
+ function te(t) {
263
+ const e = B(t);
264
+ return V(e), new Proxy({}, {
265
+ get(n, o) {
266
+ if (typeof o == "string")
267
+ try {
268
+ return J(e, o);
269
+ } catch {
270
+ return;
271
+ }
272
+ },
273
+ has(n, o) {
274
+ return typeof o != "string" ? !1 : o in e;
275
+ },
276
+ ownKeys(n) {
277
+ return Object.keys(e);
278
+ }
279
+ });
280
+ }
281
+ export {
282
+ v as D,
283
+ M as U,
284
+ X as a,
285
+ Y as b,
286
+ K as c,
287
+ S as d,
288
+ _ as e,
289
+ T as f,
290
+ Q as g,
291
+ Z as h,
292
+ J as i,
293
+ te as j,
294
+ B as l,
295
+ ee as p,
296
+ E as r,
297
+ V as s,
298
+ w as v
299
+ };
@@ -1,18 +1,20 @@
1
1
  import { D as r, g as s, m as o } from "../config-B0A7gHQM.js";
2
2
  import { _ as i, g as n, r as p } from "../bridge-CtB9L5kL.js";
3
- import { A as m, a as c, E as f, b as C, c as g } from "../errors-B3bDbQbD.js";
4
- import { S as d, V as l, c as I, a as E, g as S, s as x } from "../container-message-ChLaG4Ke.js";
3
+ import { A as m, a as f, E as c, b as C, c as g } from "../errors-B3bDbQbD.js";
4
+ import { S as u, V as l, c as I, a as E, g as S, s as x } from "../container-message-D1_iPvJX.js";
5
5
  import { A as U, C as _, R as D, a as T, p as h } from "../runtime-CmoG3v2m.js";
6
- import { CameraAPI as y, FileSystemAPI as F, GeolocationAPI as M, SensorsAPI as P, camera as O, fileSystem as b, geolocation as L, isMotionSupported as w, isOrientationSupported as H, requestMotionPermission as N, sensors as V, vibrate as k, watchMotion as B, watchOrientation as j } from "../device/index.js";
6
+ import { CameraAPI as y, FileSystemAPI as F, GeolocationAPI as M, SensorsAPI as P, camera as O, fileSystem as b, geolocation as L, isMotionSupported as w, isOrientationSupported as H, requestMotionPermission as N, sensors as V, vibrate as B, watchMotion as k, watchOrientation as j } from "../device/index.js";
7
7
  import { P as G, a as z, c as K, b as W, g as X, i as Y, p as J, r as Q, s as Z } from "../app-version-checker-Bz7FrEZz.js";
8
8
  import { a as ee, P as ae, p as re, b as se } from "../pwa-C5nXU0DN.js";
9
9
  import { j as te } from "../url-c26cuIpu.js";
10
10
  import { a as ne, b as pe } from "../useTweaks-C7BEMpV3.js";
11
- import { c as me, b as ce, i as fe, p as Ce, u as ge, a as ue } from "../useSound-z_eIn0KH.js";
11
+ import { c as me, b as fe, i as ce, p as Ce, u as ge, a as de } from "../useSound-z_eIn0KH.js";
12
12
  import { reportScore as le, sendEvent as Ie, updateScore as Ee } from "../leaderboard/index.js";
13
- import { D as xe, d as Re, e as Ue, c as _e, U as De, a as Te, b as he, m as ve, h as ye, i as Fe, j as Me, k as Pe, g as Oe, l as be, n as Le, p as we, r as He, f as Ne, s as Ve, v as ke } from "../helper-CCuf-R6I.js";
14
- import { c as je, u as qe } from "../hooks-D5lkty5r.js";
15
- import { hasNativeBridge as ze } from "../native-bridge-BnvipFJc.js";
13
+ import { D as xe, d as Re, e as Ue, c as _e, U as De, a as Te, b as he, j as ve, g as ye, h as Fe, i as Me, l as Pe, p as Oe, r as be, f as Le, s as we, v as He } from "../helper-BQmXeuDs.js";
14
+ import { n as Ve } from "../fetch-lzas52mc.js";
15
+ import { d as ke, g as je } from "../credits-IWtJtN8O.js";
16
+ import { c as Ge, u as ze } from "../hooks-D5lkty5r.js";
17
+ import { hasNativeBridge as We } from "../native-bridge-BnvipFJc.js";
16
18
  export {
17
19
  U as AippyRuntime,
18
20
  m as AippyRuntimeError,
@@ -23,15 +25,15 @@ export {
23
25
  Ue as DEFAULT_CHAT_SYSTEM,
24
26
  r as DEFAULT_CONFIG,
25
27
  _e as DEFAULT_UI_BASE_URL,
26
- c as ERROR_CODES,
27
- f as ERROR_PREFIX,
28
+ f as ERROR_CODES,
29
+ c as ERROR_PREFIX,
28
30
  F as FileSystemAPI,
29
31
  M as GeolocationAPI,
30
32
  ee as PWAUtils,
31
33
  ae as PerformanceMonitor,
32
34
  G as PlatformDetector,
33
35
  D as ReceiveChannel,
34
- d as SDK_NAME,
36
+ u as SDK_NAME,
35
37
  P as SensorsAPI,
36
38
  De as UI_CHAT_ENDPOINT,
37
39
  l as VERSION,
@@ -43,7 +45,7 @@ export {
43
45
  pe as aippyTweaksRuntime,
44
46
  O as camera,
45
47
  z as checkFeatureSupport,
46
- je as clearUserInfoCache,
48
+ Ge as clearUserInfoCache,
47
49
  K as compareVersions,
48
50
  ve as createAIConfig,
49
51
  I as createAippyHeaders,
@@ -51,29 +53,29 @@ export {
51
53
  C as createCodeError,
52
54
  g as createError,
53
55
  me as createHiddenMediaElement,
54
- ye as deductCredits,
56
+ ke as deductCredits,
55
57
  b as fileSystem,
56
- Fe as generateCharacter3D,
57
- Me as generateMotion,
58
+ ye as generateCharacter3D,
59
+ Fe as generateMotion,
58
60
  L as geolocation,
59
- Pe as getAIConfigValue,
61
+ Me as getAIConfigValue,
60
62
  n as getAuthTokenAsync,
61
63
  W as getAvailableFeatures,
62
64
  s as getConfigFromEnv,
63
- Oe as getCredits,
65
+ je as getCredits,
64
66
  X as getFeatureVersionRequirements,
65
67
  S as getVersionInfo,
66
- ze as hasNativeBridge,
67
- ce as isIOSDevice,
68
+ We as hasNativeBridge,
69
+ fe as isIOSDevice,
68
70
  Y as isInIframe,
69
- fe as isMediaStreamAudioSupported,
71
+ ce as isMediaStreamAudioSupported,
70
72
  w as isMotionSupported,
71
73
  H as isOrientationSupported,
72
74
  te as joinUrl,
73
- be as loadAIConfig,
75
+ Pe as loadAIConfig,
74
76
  o as mergeConfig,
75
- Le as normalizeError,
76
- we as parseAIConfig,
77
+ Ve as normalizeError,
78
+ Oe as parseAIConfig,
77
79
  Ce as patchAudioContext,
78
80
  re as performanceMonitor,
79
81
  J as platform,
@@ -83,19 +85,19 @@ export {
83
85
  N as requestMotionPermission,
84
86
  Q as requireFeatureSupport,
85
87
  p as resolveApiBaseUrl,
86
- He as resolveOpenAIConfig,
87
- Ne as resolveUIConfig,
88
- Ve as sendAIConfigToContainer,
88
+ be as resolveOpenAIConfig,
89
+ Le as resolveUIConfig,
90
+ we as sendAIConfigToContainer,
89
91
  Ie as sendEvent,
90
92
  x as sendMessageToContainer,
91
93
  V as sensors,
92
94
  Z as showAlert,
93
95
  Ee as updateScore,
94
96
  ge as useAudioContext,
95
- ue as useSound,
96
- qe as useUserInfo,
97
- ke as validateAIConfig,
98
- k as vibrate,
99
- B as watchMotion,
97
+ de as useSound,
98
+ ze as useUserInfo,
99
+ He as validateAIConfig,
100
+ B as vibrate,
101
+ k as watchMotion,
100
102
  j as watchOrientation
101
103
  };
package/dist/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export * from './tweaks';
5
5
  export * from './audio';
6
6
  export * from './leaderboard';
7
7
  export * from './ai';
8
+ export * from './credits';
8
9
  export * from './user';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aippy/runtime",
3
- "version": "0.4.2-dev.2",
3
+ "version": "0.4.2-dev.3",
4
4
  "description": "Aippy Runtime SDK - Runtime SDK for Aippy projects",
5
5
  "private": false,
6
6
  "type": "module",
@@ -40,6 +40,10 @@
40
40
  "import": "./dist/ai/index.js",
41
41
  "types": "./dist/ai.d.ts"
42
42
  },
43
+ "./credits": {
44
+ "types": "./dist/credits/index.d.ts",
45
+ "import": "./dist/credits/index.js"
46
+ },
43
47
  "./user": {
44
48
  "import": "./dist/user/index.js",
45
49
  "types": "./dist/user/index.d.ts"
@@ -127,6 +131,7 @@
127
131
  "test:openai": "pnpm run build && node --test 'tests/ai/openai/*.test.mjs'",
128
132
  "test:ui": "pnpm run build && node --test 'tests/ai/ui/*.test.mjs'",
129
133
  "test:file": "pnpm run build && node --test",
134
+ "test:credits": "pnpm run build && node --test 'tests/credits/*.test.mjs'",
130
135
  "test:build": "node -c dist/index.js"
131
136
  }
132
137
  }
@@ -1,405 +0,0 @@
1
- import { createOpenAICompatible as S } from "@ai-sdk/openai-compatible";
2
- import { j as C } from "./url-c26cuIpu.js";
3
- import { AISDKError as u, DefaultChatTransport as T } from "ai";
4
- import { E as p, b as m, a as d } from "./errors-B3bDbQbD.js";
5
- import { r as g } from "./bridge-CtB9L5kL.js";
6
- import { s as I, a as U } from "./container-message-ChLaG4Ke.js";
7
- const M = `${g()}/aisdk/v1/`, oe = `${g()}/aisdk/v1/ui/`, N = "gpt-5-nano", D = "";
8
- function v(t = {}) {
9
- return {
10
- baseUrl: t.baseUrl ?? `${g()}/aisdk/v1/`
11
- };
12
- }
13
- function R(t = {}) {
14
- return {
15
- baseUrl: t.baseUrl ?? `${g()}/aisdk/v1/ui/`
16
- };
17
- }
18
- const $ = 100003, F = "insufficient_quota";
19
- function L(t, e) {
20
- const r = t.status;
21
- if (e && typeof e == "object" && "error" in e) {
22
- const n = e, o = n.error.code, s = o != null ? `AippyAIError_${o}` : `AippyAIError_${r}`, a = e;
23
- return "appMessage" in a && a.appMessage !== void 0 && I(a.appMessage), j(n.error) && x(), new u({
24
- name: s,
25
- message: `${p.RUNTIME} ${n.error.message}`,
26
- cause: {
27
- type: n.error.type ?? void 0,
28
- param: n.error.param ?? void 0,
29
- status: r
30
- }
31
- });
32
- }
33
- if (e && typeof e == "object" && e !== null) {
34
- const n = e;
35
- "appMessage" in n && n.appMessage !== void 0 && I(n.appMessage);
36
- }
37
- return new u({
38
- name: `AippyAIError_${r}`,
39
- message: `${p.RUNTIME} Request failed with status ${r}`,
40
- cause: { status: r }
41
- });
42
- }
43
- function j(t) {
44
- return t.code === $ || t.type === F;
45
- }
46
- function x() {
47
- if (!(typeof window > "u"))
48
- try {
49
- const t = window.webkit?.messageHandlers?.aippyListener;
50
- if (!t)
51
- return;
52
- t.postMessage({ command: "wallet.showRecharge" });
53
- } catch (t) {
54
- console.warn(
55
- "⚠️ [Container Message] Failed to request native recharge:",
56
- t
57
- );
58
- }
59
- }
60
- function l() {
61
- return async (t, e) => {
62
- const r = await U(e?.headers), n = await globalThis.fetch(t, { ...e, headers: r });
63
- if (!n.ok) {
64
- const o = await n.text().catch(() => null);
65
- let s = null;
66
- if (o)
67
- try {
68
- s = JSON.parse(o);
69
- } catch {
70
- s = { message: o };
71
- }
72
- throw L(n, s);
73
- }
74
- return n;
75
- };
76
- }
77
- const P = [
78
- "gpt-image-1",
79
- "gpt-image-1-mini",
80
- "gpt-image-1.5"
81
- ];
82
- function B(t) {
83
- try {
84
- const e = JSON.parse(t);
85
- if (e.model && P.some((r) => e.model.startsWith(r))) {
86
- const { response_format: r, ...n } = e;
87
- return JSON.stringify(n);
88
- }
89
- } catch {
90
- }
91
- return t;
92
- }
93
- function ae(t = {}) {
94
- const { baseUrl: e } = v(t), r = l();
95
- return S({
96
- name: "aippy",
97
- baseURL: e,
98
- fetch: async (o, s) => o.toString().includes("/images/generations") && s?.method?.toUpperCase() === "POST" && s?.body ? r(o, {
99
- ...s,
100
- body: B(s.body)
101
- }) : r(o, s),
102
- // Enable structured outputs support (json_schema response format)
103
- // This is required for Output.object() and Output.array() to work properly
104
- supportsStructuredOutputs: !0
105
- });
106
- }
107
- const J = "/chat";
108
- function ie(t = {}) {
109
- const { baseUrl: e } = R(t), r = t.model ?? N, n = t.system ?? D, o = t.api ?? C(e, J), s = { model: r, system: n }, a = l();
110
- return {
111
- transport: new T({
112
- api: o,
113
- body: s,
114
- // Ensure token is fetched fresh for every request
115
- fetch: a
116
- })
117
- };
118
- }
119
- async function ce(t = {}) {
120
- const e = await E("GET", "credits", t);
121
- return (!e || typeof e != "object" || !("credits" in e) || !Number.isSafeInteger(e.credits)) && b(), e.credits;
122
- }
123
- async function ue(t, e) {
124
- (!Number.isSafeInteger(t) || t <= 0) && w("deductCredits amount must be a positive safe integer"), (typeof e?.eventId != "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
125
- e.eventId
126
- )) && w("deductCredits eventId must be a UUID");
127
- const r = await E("POST", "credits/deduct", e, {
128
- amount: t,
129
- eventId: e.eventId.toLowerCase()
130
- });
131
- (!r || typeof r != "object" || !("success" in r) || r.success !== !0) && b();
132
- }
133
- async function E(t, e, r, n) {
134
- const { baseUrl: o } = v(r), s = await l()(C(o, e), {
135
- method: t,
136
- cache: "no-store",
137
- signal: r.signal,
138
- headers: n === void 0 ? void 0 : { "Content-Type": "application/json" },
139
- body: n === void 0 ? void 0 : JSON.stringify(n)
140
- });
141
- try {
142
- return await s.json();
143
- } catch (a) {
144
- if (r.signal?.aborted)
145
- throw r.signal.reason ?? a;
146
- if (a instanceof Error && a.name === "AbortError")
147
- throw a;
148
- b();
149
- }
150
- }
151
- function w(t) {
152
- throw new u({
153
- name: "AippyAIError_credits_input",
154
- message: `${p.CODE} ${t}`
155
- });
156
- }
157
- function b() {
158
- throw new u({
159
- name: "AippyAIError_credits_response",
160
- message: `${p.RUNTIME} Invalid credits response`
161
- });
162
- }
163
- function y(t) {
164
- return [...t].length;
165
- }
166
- const H = 3e3, V = 5e3, k = 300 * 1e3, q = 360 * 1e3;
167
- async function pe(t) {
168
- const e = t.prompt?.trim() ?? "", r = t.imageUrl?.trim() ?? "", n = t.image;
169
- y(e) > 1e3 && c("generateCharacter3D prompt must be at most 1000 characters"), n != null && !(n instanceof Blob) && c("generateCharacter3D image must be a file"), n instanceof Blob && n.size === 0 && c("generateCharacter3D image is empty");
170
- const o = t;
171
- return O(
172
- "viggle/characters",
173
- n instanceof Blob ? G(n, e) : z(r, e),
174
- o.pollIntervalMs ?? H,
175
- o.timeoutMs ?? k,
176
- t.signal
177
- );
178
- }
179
- function G(t, e) {
180
- const r = new FormData(), n = t instanceof File && t.name ? t.name : "image.png";
181
- return r.append("image", t, n), e && r.append("prompt", e), { body: r };
182
- }
183
- function z(t, e) {
184
- !e && !t && c(
185
- "generateCharacter3D requires an image, an https imageUrl, or a prompt"
186
- ), t && !t.startsWith("https://") && c("generateCharacter3D imageUrl must be an https URL");
187
- const r = {};
188
- return t && (r.imageUrl = t), e && (r.prompt = e), {
189
- headers: { "Content-Type": "application/json" },
190
- body: JSON.stringify(r)
191
- };
192
- }
193
- async function fe(t) {
194
- const e = t.text?.trim() ?? "";
195
- (y(e) < 1 || y(e) > 400) && c("generateMotion text must be 1 to 400 characters"), t.durationSeconds != null && (!Number.isInteger(t.durationSeconds) || t.durationSeconds < 3 || t.durationSeconds > 60) && c("generateMotion durationSeconds must be an integer from 3 to 60");
196
- const r = { text: e };
197
- t.durationSeconds != null && (r.durationSeconds = t.durationSeconds);
198
- const n = t;
199
- return O(
200
- "viggle/motions",
201
- {
202
- headers: { "Content-Type": "application/json" },
203
- body: JSON.stringify(r)
204
- },
205
- n.pollIntervalMs ?? V,
206
- n.timeoutMs ?? q,
207
- t.signal
208
- );
209
- }
210
- async function O(t, e, r, n, o) {
211
- const s = l(), a = Date.now();
212
- let i = await A(s, t, {
213
- method: "POST",
214
- ...e,
215
- signal: o
216
- });
217
- for (; i.status !== "ready"; )
218
- i.status === "failed" && h(i.error || "Viggle generation failed"), Date.now() - a > n && h("Viggle generation timed out"), await K(r, o), i = await A(s, `${t}/${i.id}`, {
219
- method: "GET",
220
- signal: o
221
- });
222
- return i.url || h("Viggle generation finished without a file URL"), { url: i.url, thumbnailUrl: i.thumbnailUrl };
223
- }
224
- async function A(t, e, r) {
225
- return await (await t(W(M, e), {
226
- ...r,
227
- cache: "no-store"
228
- })).json();
229
- }
230
- function W(t, e) {
231
- return `${t.replace(/\/$/, "")}/${e.replace(/^\//, "")}`;
232
- }
233
- function K(t, e) {
234
- if (e?.aborted)
235
- throw e.reason ?? new DOMException("The operation was aborted", "AbortError");
236
- return new Promise((r, n) => {
237
- const o = setTimeout(r, t);
238
- e?.addEventListener(
239
- "abort",
240
- () => {
241
- clearTimeout(o), n(
242
- e.reason ?? new DOMException("The operation was aborted", "AbortError")
243
- );
244
- },
245
- { once: !0 }
246
- );
247
- });
248
- }
249
- function c(t) {
250
- throw new u({
251
- name: "AippyAIError_viggle_input",
252
- message: `${p.CODE} ${t}`
253
- });
254
- }
255
- function h(t) {
256
- throw new u({
257
- name: "AippyAIError_viggle",
258
- message: `${p.RUNTIME} ${t}`
259
- });
260
- }
261
- function _(t) {
262
- const e = [];
263
- if (!t || typeof t != "object")
264
- throw m("AIConfig must be an object", d.INVALID_CONFIG);
265
- const r = t;
266
- for (const [n, o] of Object.entries(r)) {
267
- if (!o || typeof o != "object") {
268
- e.push({ key: n, message: "Item must be an object" });
269
- continue;
270
- }
271
- const s = o;
272
- typeof s.index != "number" && e.push({ key: n, message: "index must be a number" }), typeof s.name != "string" && e.push({ key: n, message: "name must be a string" }), typeof s.description != "string" && e.push({ key: n, message: "description must be a string" });
273
- const a = s.type;
274
- if (!["number", "boolean", "text", "enum"].includes(a)) {
275
- e.push({
276
- key: n,
277
- message: `type must be one of: number, boolean, text, enum (got: ${a})`
278
- });
279
- continue;
280
- }
281
- if (a === "number")
282
- typeof s.value != "number" && e.push({ key: n, message: 'value must be a number for type "number"' }), s.min !== void 0 && typeof s.min != "number" && e.push({ key: n, message: "min must be a number" }), s.max !== void 0 && typeof s.max != "number" && e.push({ key: n, message: "max must be a number" }), s.step !== void 0 && typeof s.step != "number" && e.push({ key: n, message: "step must be a number" });
283
- else if (a === "boolean")
284
- typeof s.value != "boolean" && e.push({ key: n, message: 'value must be a boolean for type "boolean"' });
285
- else if (a === "text")
286
- typeof s.value != "string" && e.push({ key: n, message: 'value must be a string for type "text"' });
287
- else if (a === "enum") {
288
- if (!Array.isArray(s.options))
289
- e.push({ key: n, message: 'options must be an array for type "enum"' });
290
- else if (s.options.length === 0)
291
- e.push({ key: n, message: 'options array cannot be empty for type "enum"' });
292
- else
293
- for (let f = 0; f < s.options.length; f++)
294
- typeof s.options[f] != "string" && e.push({
295
- key: n,
296
- message: `options[${f}] must be a string`
297
- });
298
- typeof s.value != "string" ? e.push({ key: n, message: 'value must be a string for type "enum"' }) : Array.isArray(s.options) && !s.options.includes(s.value) && e.push({
299
- key: n,
300
- message: `value "${s.value}" is not in options: [${s.options.join(", ")}]`
301
- });
302
- }
303
- s.group !== void 0 && typeof s.group != "string" && e.push({ key: n, message: "group must be a string" });
304
- }
305
- if (e.length > 0)
306
- throw m(
307
- `AIConfig validation failed with ${e.length} error(s)`,
308
- d.INVALID_CONFIG,
309
- { errors: e }
310
- );
311
- }
312
- function me(t) {
313
- let e;
314
- try {
315
- e = JSON.parse(t);
316
- } catch (r) {
317
- throw m(
318
- `Invalid JSON: ${r instanceof Error ? r.message : String(r)}`,
319
- d.INVALID_CONFIG
320
- );
321
- }
322
- return _(e), e;
323
- }
324
- function Y(t) {
325
- return _(t), t;
326
- }
327
- function X(t, e) {
328
- const r = t[e];
329
- if (!r)
330
- throw m(
331
- `AIConfig key "${e}" not found`,
332
- d.INVALID_CONFIG,
333
- { key: e }
334
- );
335
- return r.value;
336
- }
337
- function Q(t) {
338
- if (!(typeof window > "u"))
339
- try {
340
- const e = window.webkit?.messageHandlers?.aippyListener;
341
- if (e)
342
- try {
343
- const r = {
344
- command: "ai.initialize",
345
- parameters: JSON.stringify(t)
346
- };
347
- e.postMessage(r);
348
- } catch (r) {
349
- console.warn("❌ [Aippy AI Config] Failed to send config to iOS app:", r);
350
- }
351
- if (window.parent && window.parent !== window)
352
- try {
353
- const r = {
354
- type: "ai.initialize",
355
- config: JSON.stringify(t)
356
- };
357
- window.parent.postMessage(r, "*");
358
- } catch (r) {
359
- console.warn("❌ [Aippy AI Config] Failed to send config to parent window:", r);
360
- }
361
- } catch (e) {
362
- console.warn("⚠️ [Aippy AI Config] Failed to send config:", e);
363
- }
364
- }
365
- function de(t) {
366
- const e = Y(t);
367
- return Q(e), new Proxy({}, {
368
- get(r, n) {
369
- if (typeof n == "string")
370
- try {
371
- return X(e, n);
372
- } catch {
373
- return;
374
- }
375
- },
376
- has(r, n) {
377
- return typeof n != "string" ? !1 : n in e;
378
- },
379
- ownKeys(r) {
380
- return Object.keys(e);
381
- }
382
- });
383
- }
384
- export {
385
- M as D,
386
- J as U,
387
- ae as a,
388
- ie as b,
389
- oe as c,
390
- N as d,
391
- D as e,
392
- R as f,
393
- ce as g,
394
- ue as h,
395
- pe as i,
396
- fe as j,
397
- X as k,
398
- Y as l,
399
- de as m,
400
- L as n,
401
- me as p,
402
- v as r,
403
- Q as s,
404
- _ as v
405
- };