tensorfeed 2.2.0__tar.gz → 3.0.0__tar.gz
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.
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/PKG-INFO +6 -12
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/README.md +5 -11
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/pyproject.toml +1 -1
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed/__init__.py +1 -1
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed/_premium_tools.py +2 -2
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed/client.py +22 -37
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed/web3_signer.py +1 -1
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed.egg-info/PKG-INFO +6 -12
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed.egg-info/SOURCES.txt +1 -0
- tensorfeed-3.0.0/tests/test_premium_auth_header.py +148 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tests/test_premium_tools.py +7 -1
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/setup.cfg +0 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed/crewai.py +0 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed/langchain.py +0 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed/llamaindex.py +0 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed.egg-info/dependency_links.txt +0 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed.egg-info/requires.txt +0 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tensorfeed.egg-info/top_level.txt +0 -0
- {tensorfeed-2.2.0 → tensorfeed-3.0.0}/tests/test_route_verdict.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tensorfeed
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, LangChain/CrewAI tool factories, agent payments via USDC on Base (optional web3 auto-send)
|
|
5
5
|
Author-email: "TensorFeed.ai" <feedback@tensorfeed.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -141,8 +141,9 @@ from tensorfeed import TensorFeed, PaymentRequired
|
|
|
141
141
|
|
|
142
142
|
tf = TensorFeed()
|
|
143
143
|
|
|
144
|
-
# Step 1: get a 30-minute quote
|
|
145
|
-
quote
|
|
144
|
+
# Step 1: get a 30-minute quote. sender_wallet is the Base wallet you will
|
|
145
|
+
# send the USDC from (required); quote['wallet'] below is where you send it.
|
|
146
|
+
quote = tf.buy_credits(amount_usd=1.00, sender_wallet="0xYourBaseWallet")
|
|
146
147
|
print(f"Send {quote['amount_usd']} USDC on Base to {quote['wallet']}")
|
|
147
148
|
print(f"Memo: {quote['memo']} (expires in {quote['ttl_seconds']}s)")
|
|
148
149
|
print(f"Will get: {quote['credits']} credits")
|
|
@@ -176,11 +177,6 @@ print(f'SWE-bench moved {scores["summary"]["delta_pp"]} pp')
|
|
|
176
177
|
uptime = tf.status_uptime(provider="anthropic")
|
|
177
178
|
print(f'Anthropic uptime: {uptime["uptime_pct"]}% over {uptime["days_with_data"]} days')
|
|
178
179
|
|
|
179
|
-
diff = tf.history_compare(
|
|
180
|
-
from_date="2026-04-01", to_date="2026-04-27", snapshot_type="pricing",
|
|
181
|
-
)
|
|
182
|
-
print(f'{len(diff["changed"])} price changes, {len(diff["added"])} new models')
|
|
183
|
-
|
|
184
180
|
# Premium webhook watches (1 credit per registration, free reads)
|
|
185
181
|
created = tf.create_watch(
|
|
186
182
|
spec={
|
|
@@ -321,7 +317,7 @@ except TensorFeedError as e:
|
|
|
321
317
|
| `tf.routing_preview(task=)` | Top-1 routing recommendation (5/day/IP) |
|
|
322
318
|
| `tf.health()` | API health check |
|
|
323
319
|
| `tf.payment_info()` | Wallet, pricing, supported payment flows |
|
|
324
|
-
| `tf.buy_credits(amount_usd=)` | Generate a 30-min payment quote |
|
|
320
|
+
| `tf.buy_credits(amount_usd=, sender_wallet=)` | Generate a 30-min payment quote |
|
|
325
321
|
| `tf.confirm(tx_hash=, nonce=)` | Verify USDC tx, mint credit token |
|
|
326
322
|
|
|
327
323
|
### Token-required
|
|
@@ -334,7 +330,6 @@ except TensorFeedError as e:
|
|
|
334
330
|
| `tf.pricing_series(model=, from_date=, to_date=)` | 1 credit | Daily price points for one model with min/max/delta summary |
|
|
335
331
|
| `tf.benchmark_series(model=, benchmark=, from_date=, to_date=)` | 1 credit | Score evolution for a benchmark on one model, returns delta_pp |
|
|
336
332
|
| `tf.status_uptime(provider=, from_date=, to_date=)` | 1 credit | Uptime % per provider with incident days (degraded = half) |
|
|
337
|
-
| `tf.history_compare(from_date=, to_date=, snapshot_type=)` | 1 credit | Diff two snapshots: added, removed, changed entries with deltas |
|
|
338
333
|
| `tf.create_watch(spec=, callback_url=, secret=, fire_cap=)` | 1 credit | Register a webhook watch (price / status / digest) |
|
|
339
334
|
| `tf.create_digest_watch(cadence=, callback_url=, secret=, fire_cap=)` | 1 credit | Convenience: scheduled daily/weekly digest of pricing changes |
|
|
340
335
|
| `tf.list_watches()` | Free | List all active watches owned by the current token |
|
|
@@ -343,8 +338,7 @@ except TensorFeedError as e:
|
|
|
343
338
|
| `tf.premium_agents_directory(category=, status=, sort=, limit=, ...)` | 1 credit | Enriched directory: status, news, traffic, pricing, trending_score per agent |
|
|
344
339
|
| `tf.news_search(q=, from_date=, to_date=, provider=, category=, limit=)` | 1 credit | Full-text news search with date/provider filters, relevance scoring, recency boost |
|
|
345
340
|
| `tf.cost_projection(models=, input_tokens_per_day=, output_tokens_per_day=, horizon=)` | 1 credit | Project workload cost across 1-10 models, 4 horizons, cheapest-monthly ranking |
|
|
346
|
-
| `tf.
|
|
347
|
-
| `tf.provider_deepdive(provider)` | 1 credit | One provider's full profile: status + all models + benchmarks joined + news + traffic |
|
|
341
|
+
| `tf.provider_deepdive(provider)` | 5 credits | One provider's full profile: status + all models + benchmarks joined + news + traffic |
|
|
348
342
|
| `tf.compare_models(ids=)` | 1 credit | Side-by-side compare of 2-5 models with normalized benchmarks + rankings |
|
|
349
343
|
| `tf.whats_new(days=, news_limit=)` | 1 credit | Agent morning brief: pricing changes + incidents + top news from last 1-7 days |
|
|
350
344
|
|
|
@@ -108,8 +108,9 @@ from tensorfeed import TensorFeed, PaymentRequired
|
|
|
108
108
|
|
|
109
109
|
tf = TensorFeed()
|
|
110
110
|
|
|
111
|
-
# Step 1: get a 30-minute quote
|
|
112
|
-
quote
|
|
111
|
+
# Step 1: get a 30-minute quote. sender_wallet is the Base wallet you will
|
|
112
|
+
# send the USDC from (required); quote['wallet'] below is where you send it.
|
|
113
|
+
quote = tf.buy_credits(amount_usd=1.00, sender_wallet="0xYourBaseWallet")
|
|
113
114
|
print(f"Send {quote['amount_usd']} USDC on Base to {quote['wallet']}")
|
|
114
115
|
print(f"Memo: {quote['memo']} (expires in {quote['ttl_seconds']}s)")
|
|
115
116
|
print(f"Will get: {quote['credits']} credits")
|
|
@@ -143,11 +144,6 @@ print(f'SWE-bench moved {scores["summary"]["delta_pp"]} pp')
|
|
|
143
144
|
uptime = tf.status_uptime(provider="anthropic")
|
|
144
145
|
print(f'Anthropic uptime: {uptime["uptime_pct"]}% over {uptime["days_with_data"]} days')
|
|
145
146
|
|
|
146
|
-
diff = tf.history_compare(
|
|
147
|
-
from_date="2026-04-01", to_date="2026-04-27", snapshot_type="pricing",
|
|
148
|
-
)
|
|
149
|
-
print(f'{len(diff["changed"])} price changes, {len(diff["added"])} new models')
|
|
150
|
-
|
|
151
147
|
# Premium webhook watches (1 credit per registration, free reads)
|
|
152
148
|
created = tf.create_watch(
|
|
153
149
|
spec={
|
|
@@ -288,7 +284,7 @@ except TensorFeedError as e:
|
|
|
288
284
|
| `tf.routing_preview(task=)` | Top-1 routing recommendation (5/day/IP) |
|
|
289
285
|
| `tf.health()` | API health check |
|
|
290
286
|
| `tf.payment_info()` | Wallet, pricing, supported payment flows |
|
|
291
|
-
| `tf.buy_credits(amount_usd=)` | Generate a 30-min payment quote |
|
|
287
|
+
| `tf.buy_credits(amount_usd=, sender_wallet=)` | Generate a 30-min payment quote |
|
|
292
288
|
| `tf.confirm(tx_hash=, nonce=)` | Verify USDC tx, mint credit token |
|
|
293
289
|
|
|
294
290
|
### Token-required
|
|
@@ -301,7 +297,6 @@ except TensorFeedError as e:
|
|
|
301
297
|
| `tf.pricing_series(model=, from_date=, to_date=)` | 1 credit | Daily price points for one model with min/max/delta summary |
|
|
302
298
|
| `tf.benchmark_series(model=, benchmark=, from_date=, to_date=)` | 1 credit | Score evolution for a benchmark on one model, returns delta_pp |
|
|
303
299
|
| `tf.status_uptime(provider=, from_date=, to_date=)` | 1 credit | Uptime % per provider with incident days (degraded = half) |
|
|
304
|
-
| `tf.history_compare(from_date=, to_date=, snapshot_type=)` | 1 credit | Diff two snapshots: added, removed, changed entries with deltas |
|
|
305
300
|
| `tf.create_watch(spec=, callback_url=, secret=, fire_cap=)` | 1 credit | Register a webhook watch (price / status / digest) |
|
|
306
301
|
| `tf.create_digest_watch(cadence=, callback_url=, secret=, fire_cap=)` | 1 credit | Convenience: scheduled daily/weekly digest of pricing changes |
|
|
307
302
|
| `tf.list_watches()` | Free | List all active watches owned by the current token |
|
|
@@ -310,8 +305,7 @@ except TensorFeedError as e:
|
|
|
310
305
|
| `tf.premium_agents_directory(category=, status=, sort=, limit=, ...)` | 1 credit | Enriched directory: status, news, traffic, pricing, trending_score per agent |
|
|
311
306
|
| `tf.news_search(q=, from_date=, to_date=, provider=, category=, limit=)` | 1 credit | Full-text news search with date/provider filters, relevance scoring, recency boost |
|
|
312
307
|
| `tf.cost_projection(models=, input_tokens_per_day=, output_tokens_per_day=, horizon=)` | 1 credit | Project workload cost across 1-10 models, 4 horizons, cheapest-monthly ranking |
|
|
313
|
-
| `tf.
|
|
314
|
-
| `tf.provider_deepdive(provider)` | 1 credit | One provider's full profile: status + all models + benchmarks joined + news + traffic |
|
|
308
|
+
| `tf.provider_deepdive(provider)` | 5 credits | One provider's full profile: status + all models + benchmarks joined + news + traffic |
|
|
315
309
|
| `tf.compare_models(ids=)` | 1 credit | Side-by-side compare of 2-5 models with normalized benchmarks + rankings |
|
|
316
310
|
| `tf.whats_new(days=, news_limit=)` | 1 credit | Agent morning brief: pricing changes + incidents + top news from last 1-7 days |
|
|
317
311
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tensorfeed"
|
|
7
|
-
version = "
|
|
7
|
+
version = "3.0.0"
|
|
8
8
|
description = "Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, LangChain/CrewAI tool factories, agent payments via USDC on Base (optional web3 auto-send)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -158,14 +158,14 @@ DESCRIPTIONS: dict[str, str] = {
|
|
|
158
158
|
"limit (1-100, default 25)."
|
|
159
159
|
),
|
|
160
160
|
"tensorfeed_provider_deepdive": (
|
|
161
|
-
"PAID,
|
|
161
|
+
"PAID, 5 credits. One AI provider's full profile in a single "
|
|
162
162
|
"call: live status, every model with pricing and benchmarks "
|
|
163
163
|
"joined in, recent news, and agent-traffic attribution. Doing "
|
|
164
164
|
"this from free endpoints takes several round-trips and a "
|
|
165
165
|
"non-trivial join. Input: provider (id or display name)."
|
|
166
166
|
),
|
|
167
167
|
"tensorfeed_status_leaderboard": (
|
|
168
|
-
"PAID,
|
|
168
|
+
"PAID, 5 credits. Cross-provider AI uptime leaderboard over the "
|
|
169
169
|
"full 90-day retention window, with incident_count and "
|
|
170
170
|
"mttr_minutes per provider. Built for vendor selection and "
|
|
171
171
|
"post-incident review. Inputs: from_date, to_date (optional, "
|
|
@@ -18,7 +18,7 @@ from typing import Any # noqa: F401 (re-exported by purchase_credits return ty
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
DEFAULT_BASE_URL = "https://tensorfeed.ai/api"
|
|
21
|
-
DEFAULT_USER_AGENT = "TensorFeed-SDK-Python/
|
|
21
|
+
DEFAULT_USER_AGENT = "TensorFeed-SDK-Python/3.0"
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class TensorFeedError(Exception):
|
|
@@ -105,10 +105,18 @@ class TensorFeed:
|
|
|
105
105
|
# Premium endpoints, balance, and explicit-require-token paths get
|
|
106
106
|
# the Authorization header. Free endpoints do not advertise a token
|
|
107
107
|
# (avoids accidentally leaking it to public-data endpoints).
|
|
108
|
+
# Paths handed to _request are RELATIVE: base_url already ends in /api,
|
|
109
|
+
# and url is built as f"{base_url}{path}". The old fallback tested for
|
|
110
|
+
# "/api/premium/" and "/api/payment/balance" in that relative path, so
|
|
111
|
+
# neither could ever match and the whole fallback was dead code. Every
|
|
112
|
+
# premium method happened to pass require_token=True explicitly, except
|
|
113
|
+
# attention_series, which therefore called a paid endpoint with no
|
|
114
|
+
# Authorization header at all and 402'd for customers holding credits.
|
|
115
|
+
# Matching the relative form makes the safety net real.
|
|
108
116
|
needs_auth = (
|
|
109
117
|
require_token
|
|
110
|
-
or "/
|
|
111
|
-
or path == "/
|
|
118
|
+
or path.startswith("/premium/")
|
|
119
|
+
or path == "/payment/balance"
|
|
112
120
|
)
|
|
113
121
|
if needs_auth and self.token:
|
|
114
122
|
headers["Authorization"] = f"Bearer {self.token}"
|
|
@@ -380,7 +388,9 @@ class TensorFeed:
|
|
|
380
388
|
kwargs["from"] = from_date
|
|
381
389
|
if to_date is not None:
|
|
382
390
|
kwargs["to"] = to_date
|
|
383
|
-
return self.
|
|
391
|
+
return self._request(
|
|
392
|
+
"GET", "/premium/attention/series", params=kwargs, require_token=True
|
|
393
|
+
)
|
|
384
394
|
|
|
385
395
|
def agent_activity(self) -> dict[str, Any]:
|
|
386
396
|
"""Get agent traffic metrics. Free."""
|
|
@@ -510,7 +520,7 @@ class TensorFeed:
|
|
|
510
520
|
|
|
511
521
|
For windows up to 90 days plus ``incident_count`` and
|
|
512
522
|
``mttr_minutes`` per provider, use ``status_leaderboard()``
|
|
513
|
-
(premium,
|
|
523
|
+
(premium, 5 credits).
|
|
514
524
|
|
|
515
525
|
Args:
|
|
516
526
|
days: 1 to 7 (default 7).
|
|
@@ -976,7 +986,7 @@ class TensorFeed:
|
|
|
976
986
|
from_date: str | None = None,
|
|
977
987
|
to_date: str | None = None,
|
|
978
988
|
) -> dict[str, Any]:
|
|
979
|
-
"""Cross-provider uptime leaderboard. Costs
|
|
989
|
+
"""Cross-provider uptime leaderboard. Costs 5 credits.
|
|
980
990
|
|
|
981
991
|
Same minute-resolution counter source as the free
|
|
982
992
|
``status_leaderboard_free()`` but extends the window to the full
|
|
@@ -1184,12 +1194,12 @@ class TensorFeed:
|
|
|
1184
1194
|
fire_cap=fire_cap,
|
|
1185
1195
|
)
|
|
1186
1196
|
|
|
1187
|
-
# ── Paid: provider deep-dive (Tier
|
|
1197
|
+
# ── Paid: provider deep-dive (Tier 3, 5 credits) ────────────────
|
|
1188
1198
|
|
|
1189
1199
|
def provider_deepdive(self, provider: str) -> dict[str, Any]:
|
|
1190
1200
|
"""Everything about a provider in one paid call.
|
|
1191
1201
|
|
|
1192
|
-
Costs
|
|
1202
|
+
Costs 5 credits. Returns the live status + status_page_url +
|
|
1193
1203
|
components, all of the provider's models with pricing, tier,
|
|
1194
1204
|
context window, capabilities, and benchmark scores joined in,
|
|
1195
1205
|
recent news mentions, and agent-traffic attribution.
|
|
@@ -1338,8 +1348,8 @@ class TensorFeed:
|
|
|
1338
1348
|
|
|
1339
1349
|
Free, no auth. Single edge-cached endpoint that fans out across
|
|
1340
1350
|
every daily TensorFeed feed (news, 3 paper feeds, HF
|
|
1341
|
-
models/datasets/Spaces, hot GitHub issues,
|
|
1342
|
-
|
|
1351
|
+
models/datasets/Spaces, hot GitHub issues, OpenRouter catalog
|
|
1352
|
+
summary, provider status) and returns a
|
|
1343
1353
|
structured response. Saves an agent from orchestrating 9
|
|
1344
1354
|
separate calls.
|
|
1345
1355
|
|
|
@@ -1455,31 +1465,6 @@ class TensorFeed:
|
|
|
1455
1465
|
"""
|
|
1456
1466
|
return self._request("GET", "/openrouter/models")
|
|
1457
1467
|
|
|
1458
|
-
def get_reddit_trending(self) -> dict[str, Any]:
|
|
1459
|
-
"""Currently-hot Reddit threads in 7 AI-relevant subreddits.
|
|
1460
|
-
|
|
1461
|
-
Free, no auth. Subreddits queried: LocalLLaMA, MachineLearning,
|
|
1462
|
-
ClaudeAI, OpenAI, singularity, artificial, AI_Agents. Pulls each
|
|
1463
|
-
subreddit's "hot" listing, dedups by post id, drops stickied and
|
|
1464
|
-
NSFW posts, ranks by score, top 30. Refreshed daily at 13:00 UTC.
|
|
1465
|
-
Companion to ``get_hot_issues()``: that surfaces developer-side
|
|
1466
|
-
conversation on GitHub, this surfaces community-side conversation
|
|
1467
|
-
on Reddit.
|
|
1468
|
-
|
|
1469
|
-
Titles pass through TensorFeed's prompt-injection sanitization at
|
|
1470
|
-
capture time, so role-confusion tokens and bidi/zero-width
|
|
1471
|
-
spoofing characters in Reddit posts cannot reach an agent.
|
|
1472
|
-
|
|
1473
|
-
Returns:
|
|
1474
|
-
Dict with ``ok`` and ``snapshot`` keys. Snapshot includes
|
|
1475
|
-
``date``, ``capturedAt``, ``total_posts``, ``subreddits_queried``,
|
|
1476
|
-
``posts`` (each with id, subreddit, title, author, score,
|
|
1477
|
-
upvote_ratio, num_comments, permalink, url, created_utc,
|
|
1478
|
-
flair, is_self, is_video), and ``summary`` (by_subreddit,
|
|
1479
|
-
top_authors).
|
|
1480
|
-
"""
|
|
1481
|
-
return self._request("GET", "/reddit/trending")
|
|
1482
|
-
|
|
1483
1468
|
def get_hot_issues(self) -> dict[str, Any]:
|
|
1484
1469
|
"""Currently-hot GitHub issues across the AI ecosystem.
|
|
1485
1470
|
|
|
@@ -1520,7 +1505,7 @@ class TensorFeed:
|
|
|
1520
1505
|
"""
|
|
1521
1506
|
return self._request("GET", "/probe/latest")
|
|
1522
1507
|
|
|
1523
|
-
# ── Paid: LLM probe time series (Tier
|
|
1508
|
+
# ── Paid: LLM probe time series (Tier 3, 5 credits) ─────────────
|
|
1524
1509
|
|
|
1525
1510
|
def get_probe_series(
|
|
1526
1511
|
self,
|
|
@@ -1531,7 +1516,7 @@ class TensorFeed:
|
|
|
1531
1516
|
) -> dict[str, Any]:
|
|
1532
1517
|
"""Daily SLA series for one LLM provider, measured by TensorFeed.
|
|
1533
1518
|
|
|
1534
|
-
Costs
|
|
1519
|
+
Costs 5 credits. Returns per-day count, success rate, ttfb
|
|
1535
1520
|
p50/p95/p99, total p50/p95/p99, incident-hour count for each
|
|
1536
1521
|
day in the requested window. Provider status pages are
|
|
1537
1522
|
politically managed; this is the measured truth.
|
|
@@ -172,7 +172,7 @@ def auto_purchase_credits(
|
|
|
172
172
|
if not result.get("ok"):
|
|
173
173
|
raise RuntimeError(
|
|
174
174
|
f"Tx {tx_hash} mined but TensorFeed rejected confirm: {result}. "
|
|
175
|
-
f"Email
|
|
175
|
+
f"Email contact@tensorfeed.ai with the tx hash for a manual reconciliation."
|
|
176
176
|
)
|
|
177
177
|
|
|
178
178
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tensorfeed
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Python SDK for the TensorFeed.ai API: AI news, status, model pricing, premium routing + history series + webhook watches, LangChain/CrewAI tool factories, agent payments via USDC on Base (optional web3 auto-send)
|
|
5
5
|
Author-email: "TensorFeed.ai" <feedback@tensorfeed.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -141,8 +141,9 @@ from tensorfeed import TensorFeed, PaymentRequired
|
|
|
141
141
|
|
|
142
142
|
tf = TensorFeed()
|
|
143
143
|
|
|
144
|
-
# Step 1: get a 30-minute quote
|
|
145
|
-
quote
|
|
144
|
+
# Step 1: get a 30-minute quote. sender_wallet is the Base wallet you will
|
|
145
|
+
# send the USDC from (required); quote['wallet'] below is where you send it.
|
|
146
|
+
quote = tf.buy_credits(amount_usd=1.00, sender_wallet="0xYourBaseWallet")
|
|
146
147
|
print(f"Send {quote['amount_usd']} USDC on Base to {quote['wallet']}")
|
|
147
148
|
print(f"Memo: {quote['memo']} (expires in {quote['ttl_seconds']}s)")
|
|
148
149
|
print(f"Will get: {quote['credits']} credits")
|
|
@@ -176,11 +177,6 @@ print(f'SWE-bench moved {scores["summary"]["delta_pp"]} pp')
|
|
|
176
177
|
uptime = tf.status_uptime(provider="anthropic")
|
|
177
178
|
print(f'Anthropic uptime: {uptime["uptime_pct"]}% over {uptime["days_with_data"]} days')
|
|
178
179
|
|
|
179
|
-
diff = tf.history_compare(
|
|
180
|
-
from_date="2026-04-01", to_date="2026-04-27", snapshot_type="pricing",
|
|
181
|
-
)
|
|
182
|
-
print(f'{len(diff["changed"])} price changes, {len(diff["added"])} new models')
|
|
183
|
-
|
|
184
180
|
# Premium webhook watches (1 credit per registration, free reads)
|
|
185
181
|
created = tf.create_watch(
|
|
186
182
|
spec={
|
|
@@ -321,7 +317,7 @@ except TensorFeedError as e:
|
|
|
321
317
|
| `tf.routing_preview(task=)` | Top-1 routing recommendation (5/day/IP) |
|
|
322
318
|
| `tf.health()` | API health check |
|
|
323
319
|
| `tf.payment_info()` | Wallet, pricing, supported payment flows |
|
|
324
|
-
| `tf.buy_credits(amount_usd=)` | Generate a 30-min payment quote |
|
|
320
|
+
| `tf.buy_credits(amount_usd=, sender_wallet=)` | Generate a 30-min payment quote |
|
|
325
321
|
| `tf.confirm(tx_hash=, nonce=)` | Verify USDC tx, mint credit token |
|
|
326
322
|
|
|
327
323
|
### Token-required
|
|
@@ -334,7 +330,6 @@ except TensorFeedError as e:
|
|
|
334
330
|
| `tf.pricing_series(model=, from_date=, to_date=)` | 1 credit | Daily price points for one model with min/max/delta summary |
|
|
335
331
|
| `tf.benchmark_series(model=, benchmark=, from_date=, to_date=)` | 1 credit | Score evolution for a benchmark on one model, returns delta_pp |
|
|
336
332
|
| `tf.status_uptime(provider=, from_date=, to_date=)` | 1 credit | Uptime % per provider with incident days (degraded = half) |
|
|
337
|
-
| `tf.history_compare(from_date=, to_date=, snapshot_type=)` | 1 credit | Diff two snapshots: added, removed, changed entries with deltas |
|
|
338
333
|
| `tf.create_watch(spec=, callback_url=, secret=, fire_cap=)` | 1 credit | Register a webhook watch (price / status / digest) |
|
|
339
334
|
| `tf.create_digest_watch(cadence=, callback_url=, secret=, fire_cap=)` | 1 credit | Convenience: scheduled daily/weekly digest of pricing changes |
|
|
340
335
|
| `tf.list_watches()` | Free | List all active watches owned by the current token |
|
|
@@ -343,8 +338,7 @@ except TensorFeedError as e:
|
|
|
343
338
|
| `tf.premium_agents_directory(category=, status=, sort=, limit=, ...)` | 1 credit | Enriched directory: status, news, traffic, pricing, trending_score per agent |
|
|
344
339
|
| `tf.news_search(q=, from_date=, to_date=, provider=, category=, limit=)` | 1 credit | Full-text news search with date/provider filters, relevance scoring, recency boost |
|
|
345
340
|
| `tf.cost_projection(models=, input_tokens_per_day=, output_tokens_per_day=, horizon=)` | 1 credit | Project workload cost across 1-10 models, 4 horizons, cheapest-monthly ranking |
|
|
346
|
-
| `tf.
|
|
347
|
-
| `tf.provider_deepdive(provider)` | 1 credit | One provider's full profile: status + all models + benchmarks joined + news + traffic |
|
|
341
|
+
| `tf.provider_deepdive(provider)` | 5 credits | One provider's full profile: status + all models + benchmarks joined + news + traffic |
|
|
348
342
|
| `tf.compare_models(ids=)` | 1 credit | Side-by-side compare of 2-5 models with normalized benchmarks + rankings |
|
|
349
343
|
| `tf.whats_new(days=, news_limit=)` | 1 credit | Agent morning brief: pricing changes + incidents + top news from last 1-7 days |
|
|
350
344
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Every premium method must send the Authorization header.
|
|
2
|
+
|
|
3
|
+
A customer buys credits, calls a documented premium method, and gets a 402
|
|
4
|
+
because the SDK never attached their token. That shipped in 2.2.1:
|
|
5
|
+
``attention_series`` was the only premium method of twenty that omitted
|
|
6
|
+
``require_token=True``, and the fallback meant to catch exactly that mistake
|
|
7
|
+
was dead code.
|
|
8
|
+
|
|
9
|
+
The fallback tested ``"/api/premium/" in path``. Paths handed to ``_request``
|
|
10
|
+
are relative, because ``base_url`` already ends in ``/api`` and the URL is
|
|
11
|
+
built as ``f"{base_url}{path}"``. So the substring could never match and the
|
|
12
|
+
safety net never fired for anything.
|
|
13
|
+
|
|
14
|
+
Two guards here, because either one alone is escapable:
|
|
15
|
+
|
|
16
|
+
1. Behavioral. Drive every premium method through a stub transport and assert
|
|
17
|
+
the header is actually present on the wire. Catches a missing flag no
|
|
18
|
+
matter how the method is written.
|
|
19
|
+
2. Static. Assert the fallback still matches the relative path form, so the
|
|
20
|
+
net cannot silently go dead again while every method happens to pass the
|
|
21
|
+
flag explicitly.
|
|
22
|
+
|
|
23
|
+
Network-free. Run from sdk/python::
|
|
24
|
+
|
|
25
|
+
python -m unittest discover -s tests -t .
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import inspect
|
|
31
|
+
import json
|
|
32
|
+
import unittest
|
|
33
|
+
from unittest import mock
|
|
34
|
+
|
|
35
|
+
from tensorfeed import TensorFeed
|
|
36
|
+
from tensorfeed import client as client_mod
|
|
37
|
+
|
|
38
|
+
TOKEN = "tf_live_testtoken0000000000"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _StubResponse:
|
|
42
|
+
"""Minimal stand-in for the urlopen context manager."""
|
|
43
|
+
|
|
44
|
+
status = 200
|
|
45
|
+
headers: dict[str, str] = {}
|
|
46
|
+
|
|
47
|
+
def read(self) -> bytes:
|
|
48
|
+
return json.dumps({"ok": True}).encode()
|
|
49
|
+
|
|
50
|
+
def __enter__(self):
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def __exit__(self, *exc):
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _premium_methods():
|
|
58
|
+
"""Public methods whose source calls a /premium/ path."""
|
|
59
|
+
for name, fn in inspect.getmembers(TensorFeed, predicate=inspect.isfunction):
|
|
60
|
+
if name.startswith("_"):
|
|
61
|
+
continue
|
|
62
|
+
try:
|
|
63
|
+
src = inspect.getsource(fn)
|
|
64
|
+
except OSError: # pragma: no cover
|
|
65
|
+
continue
|
|
66
|
+
if '"/premium/' in src or "'/premium/" in src:
|
|
67
|
+
yield name, fn
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PremiumMethodsSendTheToken(unittest.TestCase):
|
|
71
|
+
def test_at_least_a_dozen_premium_methods_are_discovered(self):
|
|
72
|
+
found = list(_premium_methods())
|
|
73
|
+
self.assertGreater(
|
|
74
|
+
len(found), 12, f"expected to discover many premium methods, got {len(found)}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def test_every_premium_method_attaches_authorization(self):
|
|
78
|
+
tf = TensorFeed(token=TOKEN)
|
|
79
|
+
offenders: list[str] = []
|
|
80
|
+
|
|
81
|
+
for name, fn in _premium_methods():
|
|
82
|
+
sig = inspect.signature(fn)
|
|
83
|
+
# Supply a placeholder for every required positional parameter so the
|
|
84
|
+
# method reaches the transport. Values are irrelevant: the stub
|
|
85
|
+
# answers everything with 200 and we only inspect the request.
|
|
86
|
+
args: list[object] = []
|
|
87
|
+
kwargs: dict[str, object] = {}
|
|
88
|
+
for pname, p in list(sig.parameters.items())[1:]:
|
|
89
|
+
if p.default is not inspect.Parameter.empty:
|
|
90
|
+
continue
|
|
91
|
+
if p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD):
|
|
92
|
+
continue
|
|
93
|
+
# Numeric-sounding params need a number; everything else takes a
|
|
94
|
+
# plausible string. The value never reaches a real service.
|
|
95
|
+
filler: object = 1000 if "tokens" in pname or pname.endswith("_per_day") else "anthropic"
|
|
96
|
+
if p.kind is p.KEYWORD_ONLY:
|
|
97
|
+
kwargs[pname] = filler
|
|
98
|
+
else:
|
|
99
|
+
args.append(filler)
|
|
100
|
+
|
|
101
|
+
captured: dict[str, object] = {}
|
|
102
|
+
|
|
103
|
+
def _fake_urlopen(req, *a, **kw):
|
|
104
|
+
captured["headers"] = {k.lower(): v for k, v in req.headers.items()}
|
|
105
|
+
captured["url"] = req.full_url
|
|
106
|
+
return _StubResponse()
|
|
107
|
+
|
|
108
|
+
with mock.patch.object(client_mod.urllib.request, "urlopen", _fake_urlopen):
|
|
109
|
+
try:
|
|
110
|
+
getattr(tf, name)(*args, **kwargs)
|
|
111
|
+
except Exception as exc: # noqa: BLE001
|
|
112
|
+
offenders.append(f"{name}: raised before transport ({exc!r})")
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
headers = captured.get("headers") or {}
|
|
116
|
+
if headers.get("authorization") != f"Bearer {TOKEN}":
|
|
117
|
+
offenders.append(
|
|
118
|
+
f"{name}: no Authorization header on {captured.get('url')}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self.assertEqual(
|
|
122
|
+
offenders,
|
|
123
|
+
[],
|
|
124
|
+
"These premium methods call a paid endpoint without the customer's token, so a "
|
|
125
|
+
"customer holding credits gets a 402 instead of data:\n " + "\n ".join(offenders),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TheAuthFallbackIsNotDeadCode(unittest.TestCase):
|
|
130
|
+
def test_fallback_matches_the_relative_path_form(self):
|
|
131
|
+
src = inspect.getsource(client_mod.TensorFeed._request)
|
|
132
|
+
self.assertIn(
|
|
133
|
+
'path.startswith("/premium/")',
|
|
134
|
+
src,
|
|
135
|
+
"The premium auth fallback must match the RELATIVE path form. base_url already "
|
|
136
|
+
"ends in /api, so a check for '/api/premium/' in path can never be true and the "
|
|
137
|
+
"safety net is dead code.",
|
|
138
|
+
)
|
|
139
|
+
self.assertNotIn(
|
|
140
|
+
'"/api/premium/" in path',
|
|
141
|
+
src,
|
|
142
|
+
"This is the dead check that let attention_series ship without a token.",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def test_base_url_still_ends_in_api(self):
|
|
146
|
+
# The premise of the test above. If this ever changes, the relative-path
|
|
147
|
+
# assumption changes with it.
|
|
148
|
+
self.assertTrue(client_mod.DEFAULT_BASE_URL.endswith("/api"))
|
|
@@ -182,7 +182,13 @@ class Formatters(unittest.TestCase):
|
|
|
182
182
|
self.assertEqual(set(pt.DESCRIPTIONS), set(pt.FORMATTERS))
|
|
183
183
|
self.assertEqual(set(pt.PREMIUM_TOOL_NAMES), set(pt.DESCRIPTIONS))
|
|
184
184
|
for name, desc in pt.DESCRIPTIONS.items():
|
|
185
|
-
|
|
185
|
+
# Every tool description must state its real credit cost. Most are
|
|
186
|
+
# 1 credit; the Tier 3 endpoints (provider deep-dive, status
|
|
187
|
+
# leaderboard) are 5 credits.
|
|
188
|
+
self.assertTrue(
|
|
189
|
+
"1 credit" in desc or "5 credits" in desc,
|
|
190
|
+
msg=f"{name} description must state its credit cost (1 or 5)",
|
|
191
|
+
)
|
|
186
192
|
|
|
187
193
|
|
|
188
194
|
class ClientIntegrationOffline(unittest.TestCase):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|