@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/README.md
CHANGED
|
@@ -1,18 +1,283 @@
|
|
|
1
1
|
# @gravity-ai/api
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JavaScript/TypeScript SDK for [Gravity](https://trygravity.ai) ads.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|