@gravity-ai/api 1.1.4 → 1.1.5
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 +270 -5
- package/dist/chunk-EBO3CZXG.mjs +15 -0
- package/dist/index.d.mts +148 -217
- package/dist/index.d.ts +148 -217
- package/dist/index.js +317 -2
- package/dist/index.mjs +313 -1
- package/dist/opentui.d.mts +183 -0
- package/dist/opentui.d.ts +183 -0
- package/dist/opentui.js +338 -0
- package/dist/opentui.mjs +316 -0
- package/dist/types-DYbti_CZ.d.mts +249 -0
- package/dist/types-DYbti_CZ.d.ts +249 -0
- package/package.json +17 -3
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,13 +30,320 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// package.json
|
|
34
|
+
var require_package = __commonJS({
|
|
35
|
+
"package.json"(exports2, module2) {
|
|
36
|
+
module2.exports = {
|
|
37
|
+
name: "@gravity-ai/api",
|
|
38
|
+
version: "1.1.5",
|
|
39
|
+
description: "Gravity JS SDK for retrieving targeted advertisements",
|
|
40
|
+
main: "dist/index.js",
|
|
41
|
+
module: "dist/index.mjs",
|
|
42
|
+
types: "dist/index.d.ts",
|
|
43
|
+
files: [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
exports: {
|
|
47
|
+
".": {
|
|
48
|
+
types: "./dist/index.d.ts",
|
|
49
|
+
require: "./dist/index.js",
|
|
50
|
+
import: "./dist/index.mjs"
|
|
51
|
+
},
|
|
52
|
+
"./opentui": {
|
|
53
|
+
types: "./dist/opentui.d.ts",
|
|
54
|
+
require: "./dist/opentui.js",
|
|
55
|
+
import: "./dist/opentui.mjs"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
scripts: {
|
|
59
|
+
build: "tsup index.ts opentui.ts --format cjs,esm --dts",
|
|
60
|
+
clean: "rm -rf dist",
|
|
61
|
+
dev: "tsup index.ts opentui.ts --format cjs,esm --watch --dts",
|
|
62
|
+
lint: "tsc --noEmit",
|
|
63
|
+
test: "vitest run",
|
|
64
|
+
prepublishOnly: "npm run clean && npm run build",
|
|
65
|
+
"version:patch": "npm version patch",
|
|
66
|
+
"version:minor": "npm version minor",
|
|
67
|
+
"version:major": "npm version major",
|
|
68
|
+
"publish:patch": "npm run version:patch && npm publish",
|
|
69
|
+
"publish:minor": "npm run version:minor && npm publish",
|
|
70
|
+
"publish:major": "npm run version:major && npm publish"
|
|
71
|
+
},
|
|
72
|
+
keywords: [
|
|
73
|
+
"gravity",
|
|
74
|
+
"advertising",
|
|
75
|
+
"api",
|
|
76
|
+
"client",
|
|
77
|
+
"ads",
|
|
78
|
+
"typescript",
|
|
79
|
+
"contextual-advertising",
|
|
80
|
+
"ai-ads"
|
|
81
|
+
],
|
|
82
|
+
author: "Gravity Team",
|
|
83
|
+
license: "MIT",
|
|
84
|
+
homepage: "https://github.com/Try-Gravity/gravity-js#readme",
|
|
85
|
+
repository: {
|
|
86
|
+
type: "git",
|
|
87
|
+
url: "git+https://github.com/Try-Gravity/gravity-js.git",
|
|
88
|
+
directory: "packages/api"
|
|
89
|
+
},
|
|
90
|
+
bugs: {
|
|
91
|
+
url: "https://github.com/Try-Gravity/gravity-js/issues"
|
|
92
|
+
},
|
|
93
|
+
publishConfig: {
|
|
94
|
+
access: "public"
|
|
95
|
+
},
|
|
96
|
+
engines: {
|
|
97
|
+
node: ">=18.0.0"
|
|
98
|
+
},
|
|
99
|
+
peerDependencies: {
|
|
100
|
+
"@opentui/core": ">=0.1.0"
|
|
101
|
+
},
|
|
102
|
+
peerDependenciesMeta: {
|
|
103
|
+
"@opentui/core": {
|
|
104
|
+
optional: true
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
dependencies: {
|
|
108
|
+
axios: "^1.13.2"
|
|
109
|
+
},
|
|
110
|
+
devDependencies: {
|
|
111
|
+
"@opentui/core": "^0.1.80",
|
|
112
|
+
tsup: "^8.0.1",
|
|
113
|
+
typescript: "^5.3.3",
|
|
114
|
+
vitest: "^1.2.1"
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
30
120
|
// index.ts
|
|
31
121
|
var index_exports = {};
|
|
32
122
|
__export(index_exports, {
|
|
33
|
-
Client: () => Client
|
|
123
|
+
Client: () => Client,
|
|
124
|
+
Gravity: () => Gravity,
|
|
125
|
+
extractClientIp: () => extractClientIp,
|
|
126
|
+
gravityAds: () => gravityAds,
|
|
127
|
+
gravityContext: () => gravityContext,
|
|
128
|
+
toGravityMessages: () => toGravityMessages
|
|
34
129
|
});
|
|
35
130
|
module.exports = __toCommonJS(index_exports);
|
|
36
131
|
|
|
132
|
+
// ads.ts
|
|
133
|
+
var DEFAULT_GRAVITY_API = "https://server.trygravity.ai/api/v1/ad";
|
|
134
|
+
var DEFAULT_TIMEOUT_MS = 3e3;
|
|
135
|
+
function toGravityMessages(messages, maxTurns = 2) {
|
|
136
|
+
if (!messages || messages.length === 0) return [];
|
|
137
|
+
return messages.slice(-maxTurns).map((m) => ({
|
|
138
|
+
role: m.role,
|
|
139
|
+
content: m.content
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
function extractClientIp(req) {
|
|
143
|
+
if (!req) return void 0;
|
|
144
|
+
const headers = req.headers || {};
|
|
145
|
+
const forwarded = headers["x-forwarded-for"] || headers["x-real-ip"];
|
|
146
|
+
if (forwarded) {
|
|
147
|
+
const first = (typeof forwarded === "string" ? forwarded : forwarded[0])?.split(",")[0]?.trim();
|
|
148
|
+
if (first) return first;
|
|
149
|
+
}
|
|
150
|
+
if (req.socket?.remoteAddress) return req.socket.remoteAddress;
|
|
151
|
+
if (req.connection?.remoteAddress) return req.connection.remoteAddress;
|
|
152
|
+
if (req.ip) return req.ip;
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
async function gravityAds(req, messages, placements, opts = {}) {
|
|
156
|
+
const apiKey = opts.apiKey || (typeof process !== "undefined" ? process.env?.GRAVITY_API_KEY : void 0);
|
|
157
|
+
const gravityApi = opts.gravityApi || DEFAULT_GRAVITY_API;
|
|
158
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
159
|
+
if (!apiKey) {
|
|
160
|
+
return { ads: [], status: 0, elapsed: "0", requestBody: null };
|
|
161
|
+
}
|
|
162
|
+
const ctx = req?.body?.gravity_context;
|
|
163
|
+
const clientUser = ctx?.user || {};
|
|
164
|
+
const clientDevice = ctx?.device || {};
|
|
165
|
+
const clientIp = extractClientIp(req);
|
|
166
|
+
const body = {
|
|
167
|
+
messages: toGravityMessages(messages),
|
|
168
|
+
sessionId: ctx?.sessionId,
|
|
169
|
+
placements,
|
|
170
|
+
user: {
|
|
171
|
+
...clientUser,
|
|
172
|
+
id: clientUser.id || clientUser.userId || "anonymous"
|
|
173
|
+
},
|
|
174
|
+
device: {
|
|
175
|
+
...clientDevice,
|
|
176
|
+
...clientIp ? { ip: clientIp } : {}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
body.relevancy = opts.relevancy ?? 0.2;
|
|
180
|
+
body.testAd = opts.production === true ? false : true;
|
|
181
|
+
if (opts.excludedTopics) body.excludedTopics = opts.excludedTopics;
|
|
182
|
+
const start = performance.now();
|
|
183
|
+
try {
|
|
184
|
+
const res = await fetch(gravityApi, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: {
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
Authorization: `Bearer ${apiKey}`
|
|
189
|
+
},
|
|
190
|
+
body: JSON.stringify(body),
|
|
191
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
192
|
+
});
|
|
193
|
+
const elapsed = (performance.now() - start).toFixed(0);
|
|
194
|
+
if (res.status === 204) {
|
|
195
|
+
return { ads: [], status: 204, elapsed, requestBody: body };
|
|
196
|
+
}
|
|
197
|
+
if (!res.ok) {
|
|
198
|
+
const errText = await res.text().catch(() => "");
|
|
199
|
+
return {
|
|
200
|
+
ads: [],
|
|
201
|
+
status: res.status,
|
|
202
|
+
elapsed,
|
|
203
|
+
error: errText,
|
|
204
|
+
requestBody: body
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const data = await res.json();
|
|
208
|
+
const ads = Array.isArray(data) ? data : [data];
|
|
209
|
+
return { ads, status: res.status, elapsed, requestBody: body };
|
|
210
|
+
} catch (err) {
|
|
211
|
+
const elapsed = (performance.now() - start).toFixed(0);
|
|
212
|
+
const error = err instanceof Error ? err.name === "TimeoutError" ? `Timeout (${timeoutMs}ms)` : err.message : String(err);
|
|
213
|
+
return { ads: [], status: 0, elapsed, error, requestBody: body };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// gravity.ts
|
|
218
|
+
var Gravity = class {
|
|
219
|
+
/**
|
|
220
|
+
* @param opts - Configuration applied to every `getAds()` call.
|
|
221
|
+
*
|
|
222
|
+
* | Option | Type | Default | Description |
|
|
223
|
+
* |--------|------|---------|-------------|
|
|
224
|
+
* | `apiKey` | `string` | `process.env.GRAVITY_API_KEY` | Gravity API key |
|
|
225
|
+
* | `production` | `boolean` | `false` | `true` = real ads. `false` = test ads (no billing). |
|
|
226
|
+
* | `relevancy` | `number` | `0.2` | Minimum relevancy threshold (0-1) |
|
|
227
|
+
* | `timeoutMs` | `number` | `3000` | Request timeout in ms |
|
|
228
|
+
* | `excludedTopics` | `string[]` | — | Topics to exclude from ad matching |
|
|
229
|
+
* | `gravityApi` | `string` | production URL | Custom API endpoint |
|
|
230
|
+
*/
|
|
231
|
+
constructor(opts = {}) {
|
|
232
|
+
this.opts = opts;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Fetch ads from the Gravity API. **Never throws.**
|
|
236
|
+
*
|
|
237
|
+
* Reads `gravity_context` from `req.body` (sent by the client via
|
|
238
|
+
* `gravityContext()`), extracts the end-user's IP from request headers,
|
|
239
|
+
* and calls the Gravity ad endpoint.
|
|
240
|
+
*
|
|
241
|
+
* @param req - Server request object (Express, Fastify, Next.js, etc.)
|
|
242
|
+
* @param messages - Conversation `[{ role, content }]` — last 2 turns sent
|
|
243
|
+
* @param placements - Ad slots, e.g. `[{ placement: "below_response", placement_id: "main" }]`
|
|
244
|
+
* @param overrides - Per-call overrides that merge on top of constructor config
|
|
245
|
+
* @returns `{ ads, status, elapsed, requestBody, error? }` — always resolves
|
|
246
|
+
*/
|
|
247
|
+
async getAds(req, messages, placements, overrides = {}) {
|
|
248
|
+
return gravityAds(req, messages, placements, {
|
|
249
|
+
...this.opts,
|
|
250
|
+
...overrides
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// context.ts
|
|
256
|
+
function gravityContext(overrides) {
|
|
257
|
+
if (!overrides?.sessionId) {
|
|
258
|
+
throw new Error("gravityContext() requires sessionId");
|
|
259
|
+
}
|
|
260
|
+
if (!overrides?.user?.userId) {
|
|
261
|
+
throw new Error("gravityContext() requires user.userId");
|
|
262
|
+
}
|
|
263
|
+
const isBrowser = typeof window !== "undefined" && typeof navigator !== "undefined";
|
|
264
|
+
const device = isBrowser ? browserDevice() : nodeDevice();
|
|
265
|
+
if (overrides.device) {
|
|
266
|
+
Object.assign(device, overrides.device);
|
|
267
|
+
}
|
|
268
|
+
const { userId, ...restUser } = overrides.user;
|
|
269
|
+
return {
|
|
270
|
+
sessionId: overrides.sessionId,
|
|
271
|
+
user: { ...restUser, id: userId },
|
|
272
|
+
device
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function browserDevice() {
|
|
276
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
277
|
+
const win = typeof window !== "undefined" ? window : void 0;
|
|
278
|
+
return {
|
|
279
|
+
ua: nav?.userAgent || "",
|
|
280
|
+
browser: "web",
|
|
281
|
+
device_type: /Mobi|Android/i.test(nav?.userAgent || "") ? "mobile" : "desktop",
|
|
282
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
283
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
284
|
+
language: nav?.language || "en",
|
|
285
|
+
screen_width: win?.screen?.width,
|
|
286
|
+
screen_height: win?.screen?.height,
|
|
287
|
+
viewport_width: win?.innerWidth,
|
|
288
|
+
viewport_height: win?.innerHeight,
|
|
289
|
+
platform: nav?.platform || "web"
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function nodeDevice() {
|
|
293
|
+
let os;
|
|
294
|
+
try {
|
|
295
|
+
os = require("os");
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
const runtimeTag = (() => {
|
|
299
|
+
if (typeof globalThis !== "undefined") {
|
|
300
|
+
const g = globalThis;
|
|
301
|
+
if (g.Bun && typeof g.Bun === "object" && "version" in g.Bun) {
|
|
302
|
+
return `Bun/${g.Bun.version}`;
|
|
303
|
+
}
|
|
304
|
+
if (g.Deno && typeof g.Deno === "object") {
|
|
305
|
+
const deno = g.Deno;
|
|
306
|
+
const ver = deno.version;
|
|
307
|
+
return `Deno/${ver?.deno || "unknown"}`;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
311
|
+
return `Node/${process.versions.node}`;
|
|
312
|
+
}
|
|
313
|
+
return "unknown";
|
|
314
|
+
})();
|
|
315
|
+
const cols = typeof process !== "undefined" ? process.stdout?.columns || 80 : 80;
|
|
316
|
+
const rows = typeof process !== "undefined" ? process.stdout?.rows || 24 : 24;
|
|
317
|
+
const env = typeof process !== "undefined" ? process.env : {};
|
|
318
|
+
return {
|
|
319
|
+
ua: `gravity-js/${PKG_VERSION} (${os?.type() || "unknown"} ${os?.arch() || "unknown"}; ${runtimeTag})`,
|
|
320
|
+
os: os ? `${os.type()} ${os.release()}` : void 0,
|
|
321
|
+
browser: "cli",
|
|
322
|
+
device_type: env.CI ? "server" : "desktop",
|
|
323
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
324
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
325
|
+
language: env.LANG?.split(".")[0]?.replace("_", "-") || "en",
|
|
326
|
+
platform: typeof process !== "undefined" ? process.platform : void 0,
|
|
327
|
+
arch: os?.arch(),
|
|
328
|
+
screen_width: cols,
|
|
329
|
+
screen_height: rows,
|
|
330
|
+
viewport_width: cols,
|
|
331
|
+
viewport_height: rows,
|
|
332
|
+
runtime: runtimeTag,
|
|
333
|
+
term_program: env.TERM_PROGRAM || void 0,
|
|
334
|
+
term: env.TERM || void 0,
|
|
335
|
+
colorterm: env.COLORTERM || void 0,
|
|
336
|
+
is_ci: !!env.CI
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
var PKG_VERSION = (() => {
|
|
340
|
+
try {
|
|
341
|
+
return require_package().version;
|
|
342
|
+
} catch {
|
|
343
|
+
return "0.0.0";
|
|
344
|
+
}
|
|
345
|
+
})();
|
|
346
|
+
|
|
37
347
|
// client.ts
|
|
38
348
|
var import_axios = __toESM(require("axios"));
|
|
39
349
|
var DEFAULT_ENDPOINT = "https://server.trygravity.ai";
|
|
@@ -186,5 +496,10 @@ var Client = class {
|
|
|
186
496
|
};
|
|
187
497
|
// Annotate the CommonJS export names for ESM import in node:
|
|
188
498
|
0 && (module.exports = {
|
|
189
|
-
Client
|
|
499
|
+
Client,
|
|
500
|
+
Gravity,
|
|
501
|
+
extractClientIp,
|
|
502
|
+
gravityAds,
|
|
503
|
+
gravityContext,
|
|
504
|
+
toGravityMessages
|
|
190
505
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,310 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__commonJS,
|
|
3
|
+
__require
|
|
4
|
+
} from "./chunk-EBO3CZXG.mjs";
|
|
5
|
+
|
|
6
|
+
// package.json
|
|
7
|
+
var require_package = __commonJS({
|
|
8
|
+
"package.json"(exports, module) {
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: "@gravity-ai/api",
|
|
11
|
+
version: "1.1.5",
|
|
12
|
+
description: "Gravity JS SDK for retrieving targeted advertisements",
|
|
13
|
+
main: "dist/index.js",
|
|
14
|
+
module: "dist/index.mjs",
|
|
15
|
+
types: "dist/index.d.ts",
|
|
16
|
+
files: [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
exports: {
|
|
20
|
+
".": {
|
|
21
|
+
types: "./dist/index.d.ts",
|
|
22
|
+
require: "./dist/index.js",
|
|
23
|
+
import: "./dist/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./opentui": {
|
|
26
|
+
types: "./dist/opentui.d.ts",
|
|
27
|
+
require: "./dist/opentui.js",
|
|
28
|
+
import: "./dist/opentui.mjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
scripts: {
|
|
32
|
+
build: "tsup index.ts opentui.ts --format cjs,esm --dts",
|
|
33
|
+
clean: "rm -rf dist",
|
|
34
|
+
dev: "tsup index.ts opentui.ts --format cjs,esm --watch --dts",
|
|
35
|
+
lint: "tsc --noEmit",
|
|
36
|
+
test: "vitest run",
|
|
37
|
+
prepublishOnly: "npm run clean && npm run build",
|
|
38
|
+
"version:patch": "npm version patch",
|
|
39
|
+
"version:minor": "npm version minor",
|
|
40
|
+
"version:major": "npm version major",
|
|
41
|
+
"publish:patch": "npm run version:patch && npm publish",
|
|
42
|
+
"publish:minor": "npm run version:minor && npm publish",
|
|
43
|
+
"publish:major": "npm run version:major && npm publish"
|
|
44
|
+
},
|
|
45
|
+
keywords: [
|
|
46
|
+
"gravity",
|
|
47
|
+
"advertising",
|
|
48
|
+
"api",
|
|
49
|
+
"client",
|
|
50
|
+
"ads",
|
|
51
|
+
"typescript",
|
|
52
|
+
"contextual-advertising",
|
|
53
|
+
"ai-ads"
|
|
54
|
+
],
|
|
55
|
+
author: "Gravity Team",
|
|
56
|
+
license: "MIT",
|
|
57
|
+
homepage: "https://github.com/Try-Gravity/gravity-js#readme",
|
|
58
|
+
repository: {
|
|
59
|
+
type: "git",
|
|
60
|
+
url: "git+https://github.com/Try-Gravity/gravity-js.git",
|
|
61
|
+
directory: "packages/api"
|
|
62
|
+
},
|
|
63
|
+
bugs: {
|
|
64
|
+
url: "https://github.com/Try-Gravity/gravity-js/issues"
|
|
65
|
+
},
|
|
66
|
+
publishConfig: {
|
|
67
|
+
access: "public"
|
|
68
|
+
},
|
|
69
|
+
engines: {
|
|
70
|
+
node: ">=18.0.0"
|
|
71
|
+
},
|
|
72
|
+
peerDependencies: {
|
|
73
|
+
"@opentui/core": ">=0.1.0"
|
|
74
|
+
},
|
|
75
|
+
peerDependenciesMeta: {
|
|
76
|
+
"@opentui/core": {
|
|
77
|
+
optional: true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
dependencies: {
|
|
81
|
+
axios: "^1.13.2"
|
|
82
|
+
},
|
|
83
|
+
devDependencies: {
|
|
84
|
+
"@opentui/core": "^0.1.80",
|
|
85
|
+
tsup: "^8.0.1",
|
|
86
|
+
typescript: "^5.3.3",
|
|
87
|
+
vitest: "^1.2.1"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ads.ts
|
|
94
|
+
var DEFAULT_GRAVITY_API = "https://server.trygravity.ai/api/v1/ad";
|
|
95
|
+
var DEFAULT_TIMEOUT_MS = 3e3;
|
|
96
|
+
function toGravityMessages(messages, maxTurns = 2) {
|
|
97
|
+
if (!messages || messages.length === 0) return [];
|
|
98
|
+
return messages.slice(-maxTurns).map((m) => ({
|
|
99
|
+
role: m.role,
|
|
100
|
+
content: m.content
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
function extractClientIp(req) {
|
|
104
|
+
if (!req) return void 0;
|
|
105
|
+
const headers = req.headers || {};
|
|
106
|
+
const forwarded = headers["x-forwarded-for"] || headers["x-real-ip"];
|
|
107
|
+
if (forwarded) {
|
|
108
|
+
const first = (typeof forwarded === "string" ? forwarded : forwarded[0])?.split(",")[0]?.trim();
|
|
109
|
+
if (first) return first;
|
|
110
|
+
}
|
|
111
|
+
if (req.socket?.remoteAddress) return req.socket.remoteAddress;
|
|
112
|
+
if (req.connection?.remoteAddress) return req.connection.remoteAddress;
|
|
113
|
+
if (req.ip) return req.ip;
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
async function gravityAds(req, messages, placements, opts = {}) {
|
|
117
|
+
const apiKey = opts.apiKey || (typeof process !== "undefined" ? process.env?.GRAVITY_API_KEY : void 0);
|
|
118
|
+
const gravityApi = opts.gravityApi || DEFAULT_GRAVITY_API;
|
|
119
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
120
|
+
if (!apiKey) {
|
|
121
|
+
return { ads: [], status: 0, elapsed: "0", requestBody: null };
|
|
122
|
+
}
|
|
123
|
+
const ctx = req?.body?.gravity_context;
|
|
124
|
+
const clientUser = ctx?.user || {};
|
|
125
|
+
const clientDevice = ctx?.device || {};
|
|
126
|
+
const clientIp = extractClientIp(req);
|
|
127
|
+
const body = {
|
|
128
|
+
messages: toGravityMessages(messages),
|
|
129
|
+
sessionId: ctx?.sessionId,
|
|
130
|
+
placements,
|
|
131
|
+
user: {
|
|
132
|
+
...clientUser,
|
|
133
|
+
id: clientUser.id || clientUser.userId || "anonymous"
|
|
134
|
+
},
|
|
135
|
+
device: {
|
|
136
|
+
...clientDevice,
|
|
137
|
+
...clientIp ? { ip: clientIp } : {}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
body.relevancy = opts.relevancy ?? 0.2;
|
|
141
|
+
body.testAd = opts.production === true ? false : true;
|
|
142
|
+
if (opts.excludedTopics) body.excludedTopics = opts.excludedTopics;
|
|
143
|
+
const start = performance.now();
|
|
144
|
+
try {
|
|
145
|
+
const res = await fetch(gravityApi, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: {
|
|
148
|
+
"Content-Type": "application/json",
|
|
149
|
+
Authorization: `Bearer ${apiKey}`
|
|
150
|
+
},
|
|
151
|
+
body: JSON.stringify(body),
|
|
152
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
153
|
+
});
|
|
154
|
+
const elapsed = (performance.now() - start).toFixed(0);
|
|
155
|
+
if (res.status === 204) {
|
|
156
|
+
return { ads: [], status: 204, elapsed, requestBody: body };
|
|
157
|
+
}
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
const errText = await res.text().catch(() => "");
|
|
160
|
+
return {
|
|
161
|
+
ads: [],
|
|
162
|
+
status: res.status,
|
|
163
|
+
elapsed,
|
|
164
|
+
error: errText,
|
|
165
|
+
requestBody: body
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const data = await res.json();
|
|
169
|
+
const ads = Array.isArray(data) ? data : [data];
|
|
170
|
+
return { ads, status: res.status, elapsed, requestBody: body };
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const elapsed = (performance.now() - start).toFixed(0);
|
|
173
|
+
const error = err instanceof Error ? err.name === "TimeoutError" ? `Timeout (${timeoutMs}ms)` : err.message : String(err);
|
|
174
|
+
return { ads: [], status: 0, elapsed, error, requestBody: body };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// gravity.ts
|
|
179
|
+
var Gravity = class {
|
|
180
|
+
/**
|
|
181
|
+
* @param opts - Configuration applied to every `getAds()` call.
|
|
182
|
+
*
|
|
183
|
+
* | Option | Type | Default | Description |
|
|
184
|
+
* |--------|------|---------|-------------|
|
|
185
|
+
* | `apiKey` | `string` | `process.env.GRAVITY_API_KEY` | Gravity API key |
|
|
186
|
+
* | `production` | `boolean` | `false` | `true` = real ads. `false` = test ads (no billing). |
|
|
187
|
+
* | `relevancy` | `number` | `0.2` | Minimum relevancy threshold (0-1) |
|
|
188
|
+
* | `timeoutMs` | `number` | `3000` | Request timeout in ms |
|
|
189
|
+
* | `excludedTopics` | `string[]` | — | Topics to exclude from ad matching |
|
|
190
|
+
* | `gravityApi` | `string` | production URL | Custom API endpoint |
|
|
191
|
+
*/
|
|
192
|
+
constructor(opts = {}) {
|
|
193
|
+
this.opts = opts;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Fetch ads from the Gravity API. **Never throws.**
|
|
197
|
+
*
|
|
198
|
+
* Reads `gravity_context` from `req.body` (sent by the client via
|
|
199
|
+
* `gravityContext()`), extracts the end-user's IP from request headers,
|
|
200
|
+
* and calls the Gravity ad endpoint.
|
|
201
|
+
*
|
|
202
|
+
* @param req - Server request object (Express, Fastify, Next.js, etc.)
|
|
203
|
+
* @param messages - Conversation `[{ role, content }]` — last 2 turns sent
|
|
204
|
+
* @param placements - Ad slots, e.g. `[{ placement: "below_response", placement_id: "main" }]`
|
|
205
|
+
* @param overrides - Per-call overrides that merge on top of constructor config
|
|
206
|
+
* @returns `{ ads, status, elapsed, requestBody, error? }` — always resolves
|
|
207
|
+
*/
|
|
208
|
+
async getAds(req, messages, placements, overrides = {}) {
|
|
209
|
+
return gravityAds(req, messages, placements, {
|
|
210
|
+
...this.opts,
|
|
211
|
+
...overrides
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// context.ts
|
|
217
|
+
function gravityContext(overrides) {
|
|
218
|
+
if (!overrides?.sessionId) {
|
|
219
|
+
throw new Error("gravityContext() requires sessionId");
|
|
220
|
+
}
|
|
221
|
+
if (!overrides?.user?.userId) {
|
|
222
|
+
throw new Error("gravityContext() requires user.userId");
|
|
223
|
+
}
|
|
224
|
+
const isBrowser = typeof window !== "undefined" && typeof navigator !== "undefined";
|
|
225
|
+
const device = isBrowser ? browserDevice() : nodeDevice();
|
|
226
|
+
if (overrides.device) {
|
|
227
|
+
Object.assign(device, overrides.device);
|
|
228
|
+
}
|
|
229
|
+
const { userId, ...restUser } = overrides.user;
|
|
230
|
+
return {
|
|
231
|
+
sessionId: overrides.sessionId,
|
|
232
|
+
user: { ...restUser, id: userId },
|
|
233
|
+
device
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function browserDevice() {
|
|
237
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
238
|
+
const win = typeof window !== "undefined" ? window : void 0;
|
|
239
|
+
return {
|
|
240
|
+
ua: nav?.userAgent || "",
|
|
241
|
+
browser: "web",
|
|
242
|
+
device_type: /Mobi|Android/i.test(nav?.userAgent || "") ? "mobile" : "desktop",
|
|
243
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
244
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
245
|
+
language: nav?.language || "en",
|
|
246
|
+
screen_width: win?.screen?.width,
|
|
247
|
+
screen_height: win?.screen?.height,
|
|
248
|
+
viewport_width: win?.innerWidth,
|
|
249
|
+
viewport_height: win?.innerHeight,
|
|
250
|
+
platform: nav?.platform || "web"
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function nodeDevice() {
|
|
254
|
+
let os;
|
|
255
|
+
try {
|
|
256
|
+
os = __require("os");
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
const runtimeTag = (() => {
|
|
260
|
+
if (typeof globalThis !== "undefined") {
|
|
261
|
+
const g = globalThis;
|
|
262
|
+
if (g.Bun && typeof g.Bun === "object" && "version" in g.Bun) {
|
|
263
|
+
return `Bun/${g.Bun.version}`;
|
|
264
|
+
}
|
|
265
|
+
if (g.Deno && typeof g.Deno === "object") {
|
|
266
|
+
const deno = g.Deno;
|
|
267
|
+
const ver = deno.version;
|
|
268
|
+
return `Deno/${ver?.deno || "unknown"}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
272
|
+
return `Node/${process.versions.node}`;
|
|
273
|
+
}
|
|
274
|
+
return "unknown";
|
|
275
|
+
})();
|
|
276
|
+
const cols = typeof process !== "undefined" ? process.stdout?.columns || 80 : 80;
|
|
277
|
+
const rows = typeof process !== "undefined" ? process.stdout?.rows || 24 : 24;
|
|
278
|
+
const env = typeof process !== "undefined" ? process.env : {};
|
|
279
|
+
return {
|
|
280
|
+
ua: `gravity-js/${PKG_VERSION} (${os?.type() || "unknown"} ${os?.arch() || "unknown"}; ${runtimeTag})`,
|
|
281
|
+
os: os ? `${os.type()} ${os.release()}` : void 0,
|
|
282
|
+
browser: "cli",
|
|
283
|
+
device_type: env.CI ? "server" : "desktop",
|
|
284
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
285
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
286
|
+
language: env.LANG?.split(".")[0]?.replace("_", "-") || "en",
|
|
287
|
+
platform: typeof process !== "undefined" ? process.platform : void 0,
|
|
288
|
+
arch: os?.arch(),
|
|
289
|
+
screen_width: cols,
|
|
290
|
+
screen_height: rows,
|
|
291
|
+
viewport_width: cols,
|
|
292
|
+
viewport_height: rows,
|
|
293
|
+
runtime: runtimeTag,
|
|
294
|
+
term_program: env.TERM_PROGRAM || void 0,
|
|
295
|
+
term: env.TERM || void 0,
|
|
296
|
+
colorterm: env.COLORTERM || void 0,
|
|
297
|
+
is_ci: !!env.CI
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
var PKG_VERSION = (() => {
|
|
301
|
+
try {
|
|
302
|
+
return require_package().version;
|
|
303
|
+
} catch {
|
|
304
|
+
return "0.0.0";
|
|
305
|
+
}
|
|
306
|
+
})();
|
|
307
|
+
|
|
1
308
|
// client.ts
|
|
2
309
|
import axios from "axios";
|
|
3
310
|
var DEFAULT_ENDPOINT = "https://server.trygravity.ai";
|
|
@@ -149,5 +456,10 @@ var Client = class {
|
|
|
149
456
|
}
|
|
150
457
|
};
|
|
151
458
|
export {
|
|
152
|
-
Client
|
|
459
|
+
Client,
|
|
460
|
+
Gravity,
|
|
461
|
+
extractClientIp,
|
|
462
|
+
gravityAds,
|
|
463
|
+
gravityContext,
|
|
464
|
+
toGravityMessages
|
|
153
465
|
};
|