@gravity-ai/api 1.1.3 → 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 CHANGED
@@ -1,18 +1,283 @@
1
1
  # @gravity-ai/api
2
2
 
3
- The official Node.js/TypeScript SDK for the Gravity AI advertising API.
3
+ JavaScript/TypeScript SDK for [Gravity](https://trygravity.ai) ads.
4
4
 
5
- ## Installation
5
+ ```ts
6
+ import { Gravity } from '@gravity-ai/api';
7
+
8
+ const gravity = new Gravity({ production: true }); // reads GRAVITY_API_KEY from env
9
+
10
+ const result = await gravity.getAds(req, messages, placements);
11
+ ```
12
+
13
+ Works with Express, Fastify, Next.js, Node http, Bun, and Deno.
14
+
15
+ ## Install
6
16
 
7
17
  ```bash
8
18
  npm install @gravity-ai/api
9
19
  ```
10
20
 
11
- ## Documentation
21
+ Set `GRAVITY_API_KEY` in your server environment.
22
+
23
+ ---
24
+
25
+ ## Integration
26
+
27
+ ### Client — your chat component
28
+
29
+ ```diff
30
+ + import { gravityContext } from '@gravity-ai/api';
31
+
32
+ async function sendMessage(prompt) {
33
+ + const gravity_context = gravityContext({
34
+ + sessionId: chatSession.id,
35
+ + user: { userId: currentUser.id },
36
+ + });
37
+
38
+ const res = await fetch('/api/chat', {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ - body: JSON.stringify({ messages }),
42
+ + body: JSON.stringify({ messages, gravity_context }),
43
+ });
44
+ }
45
+ ```
46
+
47
+ > **`sessionId` and `userId` are required.** Pass your chat/conversation session ID and your authenticated user's ID.
48
+
49
+ `gravityContext()` auto-detects the runtime (browser vs Node/Bun/Deno) and collects the appropriate signals — user-agent, screen size, timezone, locale, etc. Nothing is written to disk, localStorage, or cookies.
50
+
51
+ ### Server — Node.js (Express / Fastify / any framework)
52
+
53
+ ```diff
54
+ + import { Gravity } from '@gravity-ai/api';
55
+
56
+ + const gravity = new Gravity({ production: true });
57
+
58
+ app.post('/api/chat', async (req, res) => {
59
+ const { messages } = req.body;
60
+
61
+ + const adPromise = gravity.getAds(req, messages, [
62
+ + { placement: 'below_response', placement_id: 'main' },
63
+ + ]);
64
+
65
+ // stream your LLM response as usual...
66
+ res.setHeader('Content-Type', 'text/event-stream');
67
+ for await (const token of streamYourLLM(messages)) {
68
+ res.write(`data: ${JSON.stringify({ type: 'chunk', content: token })}\n\n`);
69
+ }
70
+
71
+ + const { ads } = await adPromise;
72
+ + res.write(`data: ${JSON.stringify({ type: 'done', ads })}\n\n`);
73
+ res.end();
74
+ });
75
+ ```
76
+
77
+ `gravity.getAds()` reads `gravity_context` from `req.body`, extracts the end-user's IP from headers, and calls the Gravity API. It **never throws** — returns `{ ads: [] }` on any failure so your chat flow is never blocked.
78
+
79
+ ### Next.js Route Handler
80
+
81
+ ```ts
82
+ import { Gravity } from '@gravity-ai/api';
83
+
84
+ const gravity = new Gravity({ production: true });
85
+
86
+ export async function POST(request: Request) {
87
+ const body = await request.json();
88
+ const { ads } = await gravity.getAds(
89
+ { body, headers: Object.fromEntries(request.headers) },
90
+ body.messages,
91
+ [{ placement: 'below_response', placement_id: 'main' }],
92
+ );
93
+ return Response.json({ ads });
94
+ }
95
+ ```
96
+
97
+ ### Parallel with your LLM call
98
+
99
+ ```ts
100
+ const [adResult, llmResult] = await Promise.allSettled([
101
+ gravity.getAds(req, messages, placements),
102
+ yourLLMCall(messages),
103
+ ]);
104
+ ```
105
+
106
+ Ad latency never blocks the chat response.
107
+
108
+ ---
109
+
110
+ ## Server — Python
111
+
112
+ For Python backends (FastAPI, Django, Flask), use the [`gravity-py`](https://github.com/Try-Gravity/gravity-py) package:
113
+
114
+ ```bash
115
+ pip install gravity-sdk
116
+ ```
117
+
118
+ ```diff
119
+ + from gravity_sdk import Gravity
120
+
121
+ + gravity = Gravity(production=True)
122
+
123
+ @app.post("/api/chat")
124
+ async def chat(request: Request):
125
+ body = await request.json()
126
+ messages = body["messages"]
127
+
128
+ + ad_task = asyncio.create_task(
129
+ + gravity.get_ads(request, messages, [{"placement": "chat", "placement_id": "main"}])
130
+ + )
131
+
132
+ async def event_stream():
133
+ async for token in stream_your_llm(messages):
134
+ yield f"data: {json.dumps({'type': 'chunk', 'content': token})}\n\n"
135
+
136
+ + ad_result = await ad_task
137
+ + ads = [a.to_dict() for a in ad_result.ads]
138
+ + yield f"data: {json.dumps({'type': 'done', 'ads': ads})}\n\n"
139
+
140
+ return StreamingResponse(event_stream(), media_type="text/event-stream")
141
+ ```
142
+
143
+ ---
12
144
 
13
- For full documentation, examples, and API reference, visit:
145
+ ## API Reference
146
+
147
+ ### `new Gravity(opts?)`
148
+
149
+ Configure once at startup. All options are optional.
150
+
151
+ ```ts
152
+ const gravity = new Gravity({
153
+ production: true,
154
+ relevancy: 0.3,
155
+ excludedTopics: ['gambling'],
156
+ });
157
+ ```
158
+
159
+ | Option | Type | Default | Description |
160
+ |--------|------|---------|-------------|
161
+ | `apiKey` | `string` | `process.env.GRAVITY_API_KEY` | Gravity API key |
162
+ | `production` | `boolean` | `false` | `true` = real ads. `false` = test ads (no billing). |
163
+ | `relevancy` | `number` | `0.2` | Minimum relevancy threshold (0-1). Lower = more ads. |
164
+ | `timeoutMs` | `number` | `3000` | Request timeout in ms |
165
+ | `excludedTopics` | `string[]` | — | Topics to exclude from ad matching |
166
+ | `gravityApi` | `string` | production URL | Custom API endpoint |
167
+
168
+ ### `gravity.getAds(req, messages, placements, overrides?)`
169
+
170
+ Fetch ads. **Never throws.** Returns `{ ads: [] }` on any failure.
171
+
172
+ | Parameter | Type | Required | Description |
173
+ |-----------|------|----------|-------------|
174
+ | `req` | `object` | Yes | Server request object. `gravity_context` is read from `req.body`. |
175
+ | `messages` | `MessageObject[]` | Yes | Conversation `[{ role, content }]` — last 2 turns sent |
176
+ | `placements` | `PlacementObject[]` | Yes | Ad slots, e.g. `[{ placement: "below_response", placement_id: "main" }]` |
177
+ | `overrides` | `GravityAdsOptions` | No | Per-call overrides merged on top of constructor config |
178
+
179
+ **Returns:** `{ ads: Ad[], status: number, elapsed: string, requestBody: object, error?: string }`
180
+
181
+ ### `gravityContext(overrides)`
182
+
183
+ Client-side. Collects device + user context. Returns a plain object to include in your request body.
184
+
185
+ | Parameter | Type | Required | Description |
186
+ |-----------|------|----------|-------------|
187
+ | `overrides.sessionId` | `string` | Yes | Your chat/conversation session ID |
188
+ | `overrides.user.userId` | `string` | Yes | Your authenticated user's ID |
189
+ | `overrides.device` | `object` | No | Device field overrides |
190
+
191
+ **Returns:** `{ sessionId, user: { id, ... }, device: { ua, timezone, locale, ... } }`
192
+
193
+ Auto-detected fields vary by runtime:
194
+
195
+ | Field | Browser | Node/Bun/Deno |
196
+ |-------|---------|---------------|
197
+ | `ua` | `navigator.userAgent` | `gravity-js/x.x.x (OS arch; Runtime/ver)` |
198
+ | `browser` | `"web"` | `"cli"` |
199
+ | `device_type` | `"mobile"` or `"desktop"` | `"server"` (CI) or `"desktop"` |
200
+ | `timezone` | `Intl.DateTimeFormat` | `Intl.DateTimeFormat` |
201
+ | `screen_width/height` | `screen.width/height` | `stdout.columns/rows` |
202
+ | `viewport_width/height` | `innerWidth/Height` | `stdout.columns/rows` |
203
+
204
+ ### `gravityAds(req, messages, placements, opts?)` (low-level)
205
+
206
+ Standalone function — same as `gravity.getAds()` but takes all config per-call. Useful when you don't want to instantiate a class.
207
+
208
+ ```ts
209
+ import { gravityAds } from '@gravity-ai/api';
210
+
211
+ const { ads } = await gravityAds(req, messages, placements, { production: true });
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Data Flow
217
+
218
+ ```
219
+ ┌──────────────────┐ gravity_context in body ┌──────────────────┐ POST /api/v1/ad ┌─────────┐
220
+ │ Client code │ ────────────────────────────▶ │ Your server │ ──────────────────▶ │ Gravity │
221
+ │ │ │ │ │ API │
222
+ │ gravityContext() │ { sessionId, │ gravity.getAds( │ { messages, │ │
223
+ │ auto-detects │ user: {...}, │ req, messages, │ sessionId, │ returns │
224
+ │ user + device │ device: {...} } │ placements) │ user: {...}, │ Ad[] │
225
+ │ │ │ reads ctx from │ device: {ip,...} } │ │
226
+ └──────────────────┘ │ req.body + IP │ └─────────┘
227
+ └──────────────────┘
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Rendering Ads
233
+
234
+ ### Terminal (OpenTUI)
235
+
236
+ For OpenTUI terminal apps, `createGravityAdCard()` gives you a ready-to-use ad card with impression tracking, click-to-open-browser, and hover affordance:
237
+
238
+ ```ts
239
+ import { createGravityAdCard } from '@gravity-ai/api/opentui';
240
+
241
+ const adCard = createGravityAdCard({ renderer, input });
242
+ root.add(adCard.panel);
243
+
244
+ // In your SSE stream consumer:
245
+ if (event.type === 'ad') {
246
+ adCard.showAd(event.ads[0]); // populates content, fires impression, wires click
247
+ }
248
+ ```
249
+
250
+ Every visual property is overridable — pass `panel`, `text`, `cta`, `hoverPanel` style overrides, or mutate `adCard.elements` directly. For fully custom layouts, use the low-level `gravityAdTracking()` primitive instead.
251
+
252
+ Requires `@opentui/core` as a peer dependency (`bun add @opentui/core`). See [OPENTUI.md](OPENTUI.md) for the full API reference, custom layout guide, and theme matching.
253
+
254
+ ### React
255
+
256
+ ```bash
257
+ npm install @gravity-ai/react
258
+ ```
259
+
260
+ ```tsx
261
+ import { GravityAd } from '@gravity-ai/react';
262
+
263
+ <GravityAd ad={ads[0]} variant="card" />
264
+ ```
265
+
266
+ See [@gravity-ai/react](../react/README.md) for component docs, variants, and styling.
267
+
268
+ ### Vanilla JS / any framework
269
+
270
+ Ads are plain JSON objects. Render them however you want:
271
+
272
+ ```ts
273
+ const ad = ads[0];
274
+ if (ad) {
275
+ element.innerHTML = `<a href="${ad.clickUrl}">${ad.adText}</a>`;
276
+ if (ad.impUrl) new Image().src = ad.impUrl;
277
+ }
278
+ ```
14
279
 
15
- **[https://www.trygravity.ai/api](https://www.trygravity.ai/api)**
280
+ ---
16
281
 
17
282
  ## License
18
283
 
@@ -0,0 +1,15 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+ var __commonJS = (cb, mod) => function __require2() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+
12
+ export {
13
+ __require,
14
+ __commonJS
15
+ };