@specstream/sdk 1.3.0 → 1.3.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 +31 -1
- package/dist/index.js +27 -8
- package/dist/work-types.d.ts +15 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,36 @@ const extract = await client.extractPages({
|
|
|
35
35
|
for (const page of extract.results) console.log(page.url, page.status, page.text);
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Drop-in for the API you already call
|
|
39
|
+
|
|
40
|
+
SpecStream answers calls written for these search APIs at its own address, in each API's own reply shape. To switch, change the base address and the key. Each search call is one Search API web search on your SpecStream key: $0.005 for up to 10 results ($5 per 1,000 searches), and each 10 results more in the same call add $0.006.
|
|
41
|
+
|
|
42
|
+
| API you call | Base address you have | SpecStream address | Where the SpecStream key goes |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| Tavily | `https://api.tavily.com` | `https://api.specstream.ai/tavily` | Bearer token or `api_key` |
|
|
45
|
+
| Exa | `https://api.exa.ai` | `https://api.specstream.ai/exa` | `x-api-key` or Bearer token |
|
|
46
|
+
| Brave Search API | `https://api.search.brave.com` | `https://api.specstream.ai` | `X-Subscription-Token` |
|
|
47
|
+
| Serper | `https://google.serper.dev` | `https://api.specstream.ai/serper` | `X-API-KEY` |
|
|
48
|
+
| Bing Web Search API v7 | `https://api.bing.microsoft.com/v7.0/search` | `https://api.specstream.ai/v7.0/search` | `Ocp-Apim-Subscription-Key` |
|
|
49
|
+
| Google Custom Search JSON API | `https://www.googleapis.com` or `https://customsearch.googleapis.com` | `https://api.specstream.ai` | `key=` (`cx` is accepted and not used) |
|
|
50
|
+
| SearXNG (JSON) | your instance address | `https://api.specstream.ai/searxng/<key>` | in the address, `key=`, `X-API-Key` or Bearer token |
|
|
51
|
+
| Firecrawl v2 | `https://api.firecrawl.dev` | `https://api.specstream.ai/firecrawl` | Bearer token |
|
|
52
|
+
|
|
53
|
+
The official Tavily and Exa Python SDKs take the address as an argument:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import os
|
|
57
|
+
from tavily import TavilyClient
|
|
58
|
+
from exa_py import Exa
|
|
59
|
+
|
|
60
|
+
tavily = TavilyClient(api_key=os.environ["SPECSTREAM_API_KEY"], api_base_url="https://api.specstream.ai/tavily")
|
|
61
|
+
exa = Exa(api_key=os.environ["SPECSTREAM_API_KEY"], base_url="https://api.specstream.ai/exa")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- A Brave call that sends no `count` is priced as 20 results ($0.011). Send `count=10` for $0.005.
|
|
65
|
+
- A Firecrawl `/v2/scrape` call is one Extract API page at $0.0008. The Firecrawl JavaScript SDK works with this address; the Firecrawl Python SDK does not yet.
|
|
66
|
+
- Each route's request and reply fields: [specstream.ai/agents.md](https://specstream.ai/agents.md). Create a key under **Agents & API** at [specstream.ai/workspace](https://specstream.ai/workspace). New accounts start with $10 of credit, no card.
|
|
67
|
+
|
|
38
68
|
## Read a request, get the price
|
|
39
69
|
|
|
40
70
|
```ts
|
|
@@ -84,7 +114,7 @@ If the response is lost, `getCapacityRequest(savedRequestKey)` returns the same
|
|
|
84
114
|
|
|
85
115
|
## Pricing
|
|
86
116
|
|
|
87
|
-
New accounts start with $10 of credit
|
|
117
|
+
New accounts start with $10 of credit, no card. The Search API costs $5 per 1,000 searches and the Extract API $0.80 per 1,000 pages. In Deep Search, a delivered result costs 5 cents, 15 cents or $1 depending on how many facts it carries. No result, no charge. See [pricing](https://specstream.ai/pricing).
|
|
88
118
|
|
|
89
119
|
## Links
|
|
90
120
|
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,17 @@ export function assertFamily(entityType) {
|
|
|
56
56
|
}
|
|
57
57
|
return entityType;
|
|
58
58
|
}
|
|
59
|
+
const RETRY_AFTER_MAX_MS = 10_000;
|
|
60
|
+
/** Milliseconds to wait before the one retry of a 503 that names its Retry-After, or null. */
|
|
61
|
+
function retryAfterMs(response) {
|
|
62
|
+
if (response.status !== 503)
|
|
63
|
+
return null;
|
|
64
|
+
const value = (response.headers?.get("retry-after") || "").trim();
|
|
65
|
+
if (!value)
|
|
66
|
+
return null;
|
|
67
|
+
const ms = /^\d+(\.\d+)?$/.test(value) ? Number(value) * 1000 : Date.parse(value) - Date.now();
|
|
68
|
+
return Number.isFinite(ms) && ms >= 0 && ms <= RETRY_AFTER_MAX_MS ? ms : null;
|
|
69
|
+
}
|
|
59
70
|
export class Client {
|
|
60
71
|
apiKey;
|
|
61
72
|
baseUrl;
|
|
@@ -1012,21 +1023,29 @@ export class Client {
|
|
|
1012
1023
|
Authorization: token,
|
|
1013
1024
|
Accept: "application/json",
|
|
1014
1025
|
"Content-Type": "application/json",
|
|
1015
|
-
"User-Agent": "specstream-typescript/1.3.
|
|
1026
|
+
"User-Agent": "specstream-typescript/1.3.3",
|
|
1016
1027
|
};
|
|
1017
1028
|
}
|
|
1018
1029
|
async request(method, path, body) {
|
|
1019
1030
|
const url = this.baseUrl + path;
|
|
1031
|
+
const send = () => this.fetchFn(url, {
|
|
1032
|
+
method,
|
|
1033
|
+
headers: this.headers(),
|
|
1034
|
+
redirect: "error",
|
|
1035
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
1036
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
1037
|
+
});
|
|
1020
1038
|
let response;
|
|
1021
1039
|
let text;
|
|
1022
1040
|
try {
|
|
1023
|
-
response = await
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1041
|
+
response = await send();
|
|
1042
|
+
const wait = retryAfterMs(response);
|
|
1043
|
+
if (wait !== null) {
|
|
1044
|
+
// The server did nothing and names when to ask again: ask once more.
|
|
1045
|
+
await response.body?.cancel();
|
|
1046
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
1047
|
+
response = await send();
|
|
1048
|
+
}
|
|
1030
1049
|
text = await response.text();
|
|
1031
1050
|
}
|
|
1032
1051
|
catch (error) {
|
package/dist/work-types.d.ts
CHANGED
|
@@ -671,7 +671,7 @@ export interface Schemas {
|
|
|
671
671
|
[key: string]: unknown;
|
|
672
672
|
};
|
|
673
673
|
};
|
|
674
|
-
"ErrorCode": "unsupported_query" | "unsupported_predicate" | "unsupported_entity_family" | "ambiguous_geo" | "capability_pending" | "source_blocked" | "challenge_page" | "unquoted_plan" | "compiler_unavailable" | "parse_failed" | "payments_not_live" | "capacity" | "kill_switch" | "internal_failure" | "plan_required" | "invalid_token";
|
|
674
|
+
"ErrorCode": "unsupported_query" | "unsupported_predicate" | "unsupported_entity_family" | "ambiguous_geo" | "capability_pending" | "source_blocked" | "challenge_page" | "unquoted_plan" | "missing_api_key" | "owner_only" | "compiler_unavailable" | "parse_failed" | "payments_not_live" | "capacity" | "kill_switch" | "internal_failure" | "plan_required" | "invalid_token" | "insufficient_balance";
|
|
675
675
|
"ErrorEnvelope": {
|
|
676
676
|
"error": Schemas["ErrorBody"];
|
|
677
677
|
};
|
|
@@ -713,6 +713,10 @@ export interface Schemas {
|
|
|
713
713
|
"ExportRequest": {
|
|
714
714
|
"format": Schemas["ExportFormat"];
|
|
715
715
|
};
|
|
716
|
+
"ExtractHtml": {
|
|
717
|
+
"html": string;
|
|
718
|
+
"url"?: (string | null);
|
|
719
|
+
};
|
|
716
720
|
"ExtractPage": {
|
|
717
721
|
"request_id": string;
|
|
718
722
|
"results": Array<Schemas["ExtractedPage"]>;
|
|
@@ -720,16 +724,17 @@ export interface Schemas {
|
|
|
720
724
|
"price_usd": number;
|
|
721
725
|
};
|
|
722
726
|
"ExtractRequest": {
|
|
723
|
-
"urls"
|
|
727
|
+
"urls"?: Array<string>;
|
|
728
|
+
"pages"?: Array<Schemas["ExtractHtml"]>;
|
|
724
729
|
"format"?: "markdown" | "text";
|
|
725
730
|
};
|
|
726
731
|
"ExtractedPage": {
|
|
727
|
-
"url": string;
|
|
732
|
+
"url": (string | null);
|
|
728
733
|
"status": "ok" | "blocked" | "not_found" | "empty" | "unsupported" | "invalid_url" | "failed";
|
|
729
734
|
"title": (string | null);
|
|
730
735
|
"text": string;
|
|
731
736
|
"published": (string | null);
|
|
732
|
-
"read_from"?: ("live" | "site" | "archive" | null);
|
|
737
|
+
"read_from"?: ("live" | "site" | "archive" | "html" | null);
|
|
733
738
|
"captured_at"?: (string | null);
|
|
734
739
|
};
|
|
735
740
|
"FieldObservation": {
|
|
@@ -1027,6 +1032,7 @@ export interface Schemas {
|
|
|
1027
1032
|
"project_id": (string | null);
|
|
1028
1033
|
"spend_cap_cents": number;
|
|
1029
1034
|
"expires_at": (string | null);
|
|
1035
|
+
"zero_retention": boolean;
|
|
1030
1036
|
};
|
|
1031
1037
|
"KeyCreateRequest": {
|
|
1032
1038
|
"name"?: string;
|
|
@@ -1034,6 +1040,7 @@ export interface Schemas {
|
|
|
1034
1040
|
"project_id"?: (string | null);
|
|
1035
1041
|
"spend_cap_cents"?: number;
|
|
1036
1042
|
"expires_in_days"?: number;
|
|
1043
|
+
"zero_retention"?: boolean;
|
|
1037
1044
|
};
|
|
1038
1045
|
"KeyListItem": {
|
|
1039
1046
|
"key_id": string;
|
|
@@ -1045,6 +1052,10 @@ export interface Schemas {
|
|
|
1045
1052
|
"spend_cap_cents": number;
|
|
1046
1053
|
"expires_at": (string | null);
|
|
1047
1054
|
"last_used_at": (string | null);
|
|
1055
|
+
"zero_retention": boolean;
|
|
1056
|
+
};
|
|
1057
|
+
"KeyUpdateRequest": {
|
|
1058
|
+
"zero_retention": boolean;
|
|
1048
1059
|
};
|
|
1049
1060
|
"LineagePage": {
|
|
1050
1061
|
"finds": Array<Schemas["FindLineage"]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@specstream/sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "TypeScript client for building lead lists and company lists from the live web. Every match comes with its source, priced before it runs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|