@usewind/node 0.1.0 → 0.1.2
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/DESIGN.md +1 -1
- package/README.md +1 -1
- package/dist/index.cjs +65 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -4
- package/dist/index.d.ts +58 -4
- package/dist/index.js +65 -7
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.d.cts
CHANGED
|
@@ -17,6 +17,21 @@ interface ChatMessage {
|
|
|
17
17
|
content: unknown;
|
|
18
18
|
[k: string]: unknown;
|
|
19
19
|
}
|
|
20
|
+
/** OpenAI-style function tool. Wind maps it onto Anthropic and Gemini for you. */
|
|
21
|
+
interface ChatTool {
|
|
22
|
+
type: 'function';
|
|
23
|
+
function: {
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
parameters?: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
type ChatToolChoice = 'auto' | 'none' | 'required' | {
|
|
30
|
+
type: 'function';
|
|
31
|
+
function: {
|
|
32
|
+
name: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
20
35
|
/** Present on every successful `POST /v1/ai/*` response. */
|
|
21
36
|
interface WindBlock {
|
|
22
37
|
feature: string | null;
|
|
@@ -49,6 +64,14 @@ interface ChatParams {
|
|
|
49
64
|
/** Optional — hold cap resolves request → feature → app default → model ceiling. */
|
|
50
65
|
max_tokens?: number;
|
|
51
66
|
temperature?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Function tools, OpenAI format, for every provider. Send the assistant
|
|
69
|
+
* message back unchanged (including any `extra_content` on its tool calls —
|
|
70
|
+
* Gemini needs it), then one `{ role: 'tool', tool_call_id, content }` per call.
|
|
71
|
+
*/
|
|
72
|
+
tools?: ChatTool[];
|
|
73
|
+
tool_choice?: ChatToolChoice;
|
|
74
|
+
parallel_tool_calls?: boolean;
|
|
52
75
|
/** Prefer `.chatStream()` over setting this directly. */
|
|
53
76
|
stream?: boolean;
|
|
54
77
|
/** Defaults to `crypto.randomUUID()`; the same value is reused for every internal retry. */
|
|
@@ -56,11 +79,36 @@ interface ChatParams {
|
|
|
56
79
|
signal?: AbortSignal;
|
|
57
80
|
}
|
|
58
81
|
interface ChatStreamChunk {
|
|
59
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* One provider chunk, OpenAI shape (`choices[0].delta.content` /
|
|
84
|
+
* `.tool_calls`), JSON-parsed. `null` on the final, settlement-only chunk.
|
|
85
|
+
*/
|
|
60
86
|
delta: unknown;
|
|
61
|
-
/** The final chunk of a stream carries the settled `wind` block; `null` on all others
|
|
87
|
+
/** The final chunk of a stream carries the settled `wind` block; `null` on all others, and on a final chunk that isn't `settled`. */
|
|
62
88
|
wind: WindBlock | null;
|
|
89
|
+
/** Final token counts — on the final chunk only, when `settled`. */
|
|
90
|
+
usage?: ChatUsage;
|
|
91
|
+
/** How the call's charge stands — on the final chunk only. */
|
|
92
|
+
settlement?: ChatSettlement;
|
|
93
|
+
/**
|
|
94
|
+
* The call's `Idempotency-Key` — on the final chunk only. A `pending` call's
|
|
95
|
+
* `request.settled` webhook carries it as `request_id`.
|
|
96
|
+
*/
|
|
97
|
+
idempotencyKey?: string;
|
|
63
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* - `settled`: charged on the provider's own token counts; `wind` and `usage`
|
|
101
|
+
* are filled in.
|
|
102
|
+
* - `estimated`: the stream ended before the provider reported usage, and it
|
|
103
|
+
* keeps no record to fetch it from (OpenAI, Anthropic, Google direct), so it
|
|
104
|
+
* was charged on Wind's count of the tokens with the provider's tokenizer.
|
|
105
|
+
* `wind` and `usage` are filled in from that count.
|
|
106
|
+
* - `pending`: the stream ended before the provider reported usage, but the
|
|
107
|
+
* provider keeps a record (OpenRouter), so the charge isn't known yet.
|
|
108
|
+
* `wind` and `usage` are null; the user's hold stays reserved until Wind
|
|
109
|
+
* settles it, and the result arrives as a `request.settled` webhook.
|
|
110
|
+
*/
|
|
111
|
+
type ChatSettlement = 'settled' | 'estimated' | 'pending';
|
|
64
112
|
/** 'frozen' — Wind's own risk layer, independent of anything you or the user configured; clears when the user reconnects. */
|
|
65
113
|
type ConnectionStatus = 'active' | 'exhausted' | 'revoked' | 'frozen';
|
|
66
114
|
interface Connection {
|
|
@@ -213,6 +261,12 @@ interface RawRequest {
|
|
|
213
261
|
/** Use HTTP Basic client credentials for `Authorization`. */
|
|
214
262
|
basic?: boolean;
|
|
215
263
|
signal?: AbortSignal;
|
|
264
|
+
/**
|
|
265
|
+
* When the response is a 2xx `text/event-stream`, return its unread
|
|
266
|
+
* `ReadableStream` as `body` instead of buffering it — `.chatStream()`
|
|
267
|
+
* parses the SSE events as they arrive.
|
|
268
|
+
*/
|
|
269
|
+
stream?: boolean;
|
|
216
270
|
}
|
|
217
271
|
interface RawResponse<T = unknown> {
|
|
218
272
|
status: number;
|
|
@@ -592,7 +646,7 @@ declare class Wind {
|
|
|
592
646
|
register: (appId: string, feature: Feature) => Promise<Feature>;
|
|
593
647
|
update: (appId: string, slug: string, patch: Partial<Feature>) => Promise<Feature>;
|
|
594
648
|
};
|
|
595
|
-
/** Verify a webhook's `
|
|
649
|
+
/** Verify a webhook's `Wind-Signature` and return the typed event. Needs the raw body. */
|
|
596
650
|
verify(input: {
|
|
597
651
|
payload: string | Buffer;
|
|
598
652
|
signature: string;
|
|
@@ -606,4 +660,4 @@ declare function createWind(config?: Partial<WindConfig>): Wind;
|
|
|
606
660
|
*/
|
|
607
661
|
declare const wind: Wind;
|
|
608
662
|
|
|
609
|
-
export { type Account, AllocationExhaustedError, type AllocationRequest, type AllocationRequestParams, AppSuspendedError, type ChatMessage, type ChatParams, type ChatResult, type ChatStreamChunk, type ChatUsage, type Chattable, type ConnectTransaction, type Connection, ConnectionFrozenError, ConnectionRevokedError, ConnectionScope, type ConnectionStatus, type Feature, FeatureCostExceededError, FeatureModelNotConfiguredError, FeatureScope, type Grant, type GrantStore, IdempotencyKeyReusedError, ModelNotAllowedError, ProviderError, RateLimitedError, type RevokeResult, RiskThrottledError, type RoutesOptions, TokenExpiredError, type TokenResponse, type TransactionStore, type WebhookEvent, Wind, WindApiError, type WindBlock, type WindConfig, WindConfigError, WindError, type WindErrorEnvelope, type WindErrorInit, WindNotConnectedError, WindNotImplementedError, WindSignatureError, createWind, memoryGrantStore, wind };
|
|
663
|
+
export { type Account, AllocationExhaustedError, type AllocationRequest, type AllocationRequestParams, AppSuspendedError, type ChatMessage, type ChatParams, type ChatResult, type ChatSettlement, type ChatStreamChunk, type ChatTool, type ChatToolChoice, type ChatUsage, type Chattable, type ConnectTransaction, type Connection, ConnectionFrozenError, ConnectionRevokedError, ConnectionScope, type ConnectionStatus, type Feature, FeatureCostExceededError, FeatureModelNotConfiguredError, FeatureScope, type Grant, type GrantStore, IdempotencyKeyReusedError, ModelNotAllowedError, ProviderError, RateLimitedError, type RevokeResult, RiskThrottledError, type RoutesOptions, TokenExpiredError, type TokenResponse, type TransactionStore, type WebhookEvent, Wind, WindApiError, type WindBlock, type WindConfig, WindConfigError, WindError, type WindErrorEnvelope, type WindErrorInit, WindNotConnectedError, WindNotImplementedError, WindSignatureError, createWind, memoryGrantStore, wind };
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,21 @@ interface ChatMessage {
|
|
|
17
17
|
content: unknown;
|
|
18
18
|
[k: string]: unknown;
|
|
19
19
|
}
|
|
20
|
+
/** OpenAI-style function tool. Wind maps it onto Anthropic and Gemini for you. */
|
|
21
|
+
interface ChatTool {
|
|
22
|
+
type: 'function';
|
|
23
|
+
function: {
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
parameters?: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
type ChatToolChoice = 'auto' | 'none' | 'required' | {
|
|
30
|
+
type: 'function';
|
|
31
|
+
function: {
|
|
32
|
+
name: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
20
35
|
/** Present on every successful `POST /v1/ai/*` response. */
|
|
21
36
|
interface WindBlock {
|
|
22
37
|
feature: string | null;
|
|
@@ -49,6 +64,14 @@ interface ChatParams {
|
|
|
49
64
|
/** Optional — hold cap resolves request → feature → app default → model ceiling. */
|
|
50
65
|
max_tokens?: number;
|
|
51
66
|
temperature?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Function tools, OpenAI format, for every provider. Send the assistant
|
|
69
|
+
* message back unchanged (including any `extra_content` on its tool calls —
|
|
70
|
+
* Gemini needs it), then one `{ role: 'tool', tool_call_id, content }` per call.
|
|
71
|
+
*/
|
|
72
|
+
tools?: ChatTool[];
|
|
73
|
+
tool_choice?: ChatToolChoice;
|
|
74
|
+
parallel_tool_calls?: boolean;
|
|
52
75
|
/** Prefer `.chatStream()` over setting this directly. */
|
|
53
76
|
stream?: boolean;
|
|
54
77
|
/** Defaults to `crypto.randomUUID()`; the same value is reused for every internal retry. */
|
|
@@ -56,11 +79,36 @@ interface ChatParams {
|
|
|
56
79
|
signal?: AbortSignal;
|
|
57
80
|
}
|
|
58
81
|
interface ChatStreamChunk {
|
|
59
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* One provider chunk, OpenAI shape (`choices[0].delta.content` /
|
|
84
|
+
* `.tool_calls`), JSON-parsed. `null` on the final, settlement-only chunk.
|
|
85
|
+
*/
|
|
60
86
|
delta: unknown;
|
|
61
|
-
/** The final chunk of a stream carries the settled `wind` block; `null` on all others
|
|
87
|
+
/** The final chunk of a stream carries the settled `wind` block; `null` on all others, and on a final chunk that isn't `settled`. */
|
|
62
88
|
wind: WindBlock | null;
|
|
89
|
+
/** Final token counts — on the final chunk only, when `settled`. */
|
|
90
|
+
usage?: ChatUsage;
|
|
91
|
+
/** How the call's charge stands — on the final chunk only. */
|
|
92
|
+
settlement?: ChatSettlement;
|
|
93
|
+
/**
|
|
94
|
+
* The call's `Idempotency-Key` — on the final chunk only. A `pending` call's
|
|
95
|
+
* `request.settled` webhook carries it as `request_id`.
|
|
96
|
+
*/
|
|
97
|
+
idempotencyKey?: string;
|
|
63
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* - `settled`: charged on the provider's own token counts; `wind` and `usage`
|
|
101
|
+
* are filled in.
|
|
102
|
+
* - `estimated`: the stream ended before the provider reported usage, and it
|
|
103
|
+
* keeps no record to fetch it from (OpenAI, Anthropic, Google direct), so it
|
|
104
|
+
* was charged on Wind's count of the tokens with the provider's tokenizer.
|
|
105
|
+
* `wind` and `usage` are filled in from that count.
|
|
106
|
+
* - `pending`: the stream ended before the provider reported usage, but the
|
|
107
|
+
* provider keeps a record (OpenRouter), so the charge isn't known yet.
|
|
108
|
+
* `wind` and `usage` are null; the user's hold stays reserved until Wind
|
|
109
|
+
* settles it, and the result arrives as a `request.settled` webhook.
|
|
110
|
+
*/
|
|
111
|
+
type ChatSettlement = 'settled' | 'estimated' | 'pending';
|
|
64
112
|
/** 'frozen' — Wind's own risk layer, independent of anything you or the user configured; clears when the user reconnects. */
|
|
65
113
|
type ConnectionStatus = 'active' | 'exhausted' | 'revoked' | 'frozen';
|
|
66
114
|
interface Connection {
|
|
@@ -213,6 +261,12 @@ interface RawRequest {
|
|
|
213
261
|
/** Use HTTP Basic client credentials for `Authorization`. */
|
|
214
262
|
basic?: boolean;
|
|
215
263
|
signal?: AbortSignal;
|
|
264
|
+
/**
|
|
265
|
+
* When the response is a 2xx `text/event-stream`, return its unread
|
|
266
|
+
* `ReadableStream` as `body` instead of buffering it — `.chatStream()`
|
|
267
|
+
* parses the SSE events as they arrive.
|
|
268
|
+
*/
|
|
269
|
+
stream?: boolean;
|
|
216
270
|
}
|
|
217
271
|
interface RawResponse<T = unknown> {
|
|
218
272
|
status: number;
|
|
@@ -592,7 +646,7 @@ declare class Wind {
|
|
|
592
646
|
register: (appId: string, feature: Feature) => Promise<Feature>;
|
|
593
647
|
update: (appId: string, slug: string, patch: Partial<Feature>) => Promise<Feature>;
|
|
594
648
|
};
|
|
595
|
-
/** Verify a webhook's `
|
|
649
|
+
/** Verify a webhook's `Wind-Signature` and return the typed event. Needs the raw body. */
|
|
596
650
|
verify(input: {
|
|
597
651
|
payload: string | Buffer;
|
|
598
652
|
signature: string;
|
|
@@ -606,4 +660,4 @@ declare function createWind(config?: Partial<WindConfig>): Wind;
|
|
|
606
660
|
*/
|
|
607
661
|
declare const wind: Wind;
|
|
608
662
|
|
|
609
|
-
export { type Account, AllocationExhaustedError, type AllocationRequest, type AllocationRequestParams, AppSuspendedError, type ChatMessage, type ChatParams, type ChatResult, type ChatStreamChunk, type ChatUsage, type Chattable, type ConnectTransaction, type Connection, ConnectionFrozenError, ConnectionRevokedError, ConnectionScope, type ConnectionStatus, type Feature, FeatureCostExceededError, FeatureModelNotConfiguredError, FeatureScope, type Grant, type GrantStore, IdempotencyKeyReusedError, ModelNotAllowedError, ProviderError, RateLimitedError, type RevokeResult, RiskThrottledError, type RoutesOptions, TokenExpiredError, type TokenResponse, type TransactionStore, type WebhookEvent, Wind, WindApiError, type WindBlock, type WindConfig, WindConfigError, WindError, type WindErrorEnvelope, type WindErrorInit, WindNotConnectedError, WindNotImplementedError, WindSignatureError, createWind, memoryGrantStore, wind };
|
|
663
|
+
export { type Account, AllocationExhaustedError, type AllocationRequest, type AllocationRequestParams, AppSuspendedError, type ChatMessage, type ChatParams, type ChatResult, type ChatSettlement, type ChatStreamChunk, type ChatTool, type ChatToolChoice, type ChatUsage, type Chattable, type ConnectTransaction, type Connection, ConnectionFrozenError, ConnectionRevokedError, ConnectionScope, type ConnectionStatus, type Feature, FeatureCostExceededError, FeatureModelNotConfiguredError, FeatureScope, type Grant, type GrantStore, IdempotencyKeyReusedError, ModelNotAllowedError, ProviderError, RateLimitedError, type RevokeResult, RiskThrottledError, type RoutesOptions, TokenExpiredError, type TokenResponse, type TransactionStore, type WebhookEvent, Wind, WindApiError, type WindBlock, type WindConfig, WindConfigError, WindError, type WindErrorEnvelope, type WindErrorInit, WindNotConnectedError, WindNotImplementedError, WindSignatureError, createWind, memoryGrantStore, wind };
|
package/dist/index.js
CHANGED
|
@@ -234,6 +234,9 @@ var RawClient = class {
|
|
|
234
234
|
signal: req.signal
|
|
235
235
|
});
|
|
236
236
|
const requestId = res.headers.get("x-request-id");
|
|
237
|
+
if (req.stream && res.ok && res.body && (res.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
238
|
+
return { status: res.status, headers: res.headers, body: res.body, requestId };
|
|
239
|
+
}
|
|
237
240
|
let parsed = void 0;
|
|
238
241
|
const text = await res.text();
|
|
239
242
|
if (text) {
|
|
@@ -288,7 +291,8 @@ var RawClient = class {
|
|
|
288
291
|
bearer: args.accessToken,
|
|
289
292
|
headers,
|
|
290
293
|
body: args.body,
|
|
291
|
-
signal: args.signal
|
|
294
|
+
signal: args.signal,
|
|
295
|
+
stream: isStreamBody(args.body)
|
|
292
296
|
});
|
|
293
297
|
}
|
|
294
298
|
/** `POST /v1/ai/features/{slug}`. */
|
|
@@ -299,7 +303,8 @@ var RawClient = class {
|
|
|
299
303
|
bearer: args.accessToken,
|
|
300
304
|
headers: { "Idempotency-Key": args.idempotencyKey },
|
|
301
305
|
body: args.body,
|
|
302
|
-
signal: args.signal
|
|
306
|
+
signal: args.signal,
|
|
307
|
+
stream: isStreamBody(args.body)
|
|
303
308
|
});
|
|
304
309
|
}
|
|
305
310
|
// ── Management (client-credentials) ────────────────────────────────────────
|
|
@@ -362,6 +367,9 @@ var RawClient = class {
|
|
|
362
367
|
return this.request({ method: "POST", path, ...init });
|
|
363
368
|
}
|
|
364
369
|
};
|
|
370
|
+
function isStreamBody(body) {
|
|
371
|
+
return typeof body === "object" && body !== null && body.stream === true;
|
|
372
|
+
}
|
|
365
373
|
|
|
366
374
|
// src/tokens.ts
|
|
367
375
|
var ACCESS_TOKEN_SKEW_MS = 6e4;
|
|
@@ -620,6 +628,9 @@ function requestBody(params) {
|
|
|
620
628
|
messages: params.messages,
|
|
621
629
|
...params.max_tokens !== void 0 && { max_tokens: params.max_tokens },
|
|
622
630
|
...params.temperature !== void 0 && { temperature: params.temperature },
|
|
631
|
+
...params.tools !== void 0 && { tools: params.tools },
|
|
632
|
+
...params.tool_choice !== void 0 && { tool_choice: params.tool_choice },
|
|
633
|
+
...params.parallel_tool_calls !== void 0 && { parallel_tool_calls: params.parallel_tool_calls },
|
|
623
634
|
...params.stream !== void 0 && { stream: params.stream }
|
|
624
635
|
};
|
|
625
636
|
}
|
|
@@ -666,13 +677,60 @@ async function* billedStream(ctx, userId, params, call) {
|
|
|
666
677
|
if (res.status < 200 || res.status >= 300) {
|
|
667
678
|
throw WindError.fromResponse(res.status, res.body, { requestId: res.requestId });
|
|
668
679
|
}
|
|
669
|
-
|
|
670
|
-
if (!contentType.includes("text/event-stream")) {
|
|
680
|
+
if (!(res.body instanceof ReadableStream)) {
|
|
671
681
|
const result = res.body;
|
|
672
|
-
yield { delta: result, wind: result.wind };
|
|
682
|
+
yield { delta: result, wind: result.wind ?? null, usage: result.usage, settlement: "settled", idempotencyKey };
|
|
673
683
|
return;
|
|
674
684
|
}
|
|
675
|
-
|
|
685
|
+
for await (const event of sseEvents(res.body)) {
|
|
686
|
+
if (event === "[DONE]") return;
|
|
687
|
+
let data;
|
|
688
|
+
try {
|
|
689
|
+
data = JSON.parse(event);
|
|
690
|
+
} catch {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (data?.wind_final) {
|
|
694
|
+
yield finalChunk(data.wind_final, idempotencyKey);
|
|
695
|
+
} else if (data?.error) {
|
|
696
|
+
const err = data.error.error ?? data.error;
|
|
697
|
+
const status = err.code === "provider_error" ? 502 : 403;
|
|
698
|
+
throw WindError.fromResponse(status, { error: err }, { requestId: res.requestId });
|
|
699
|
+
} else if (data?.streamed) {
|
|
700
|
+
yield finalChunk(data, idempotencyKey);
|
|
701
|
+
} else {
|
|
702
|
+
yield { delta: data, wind: null };
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
async function* sseEvents(stream) {
|
|
707
|
+
const reader = stream.getReader();
|
|
708
|
+
const decoder = new TextDecoder();
|
|
709
|
+
let buffer = "";
|
|
710
|
+
try {
|
|
711
|
+
while (true) {
|
|
712
|
+
const { done, value } = await reader.read();
|
|
713
|
+
if (done) break;
|
|
714
|
+
buffer += decoder.decode(value, { stream: true });
|
|
715
|
+
const lines = buffer.split("\n");
|
|
716
|
+
buffer = lines.pop() ?? "";
|
|
717
|
+
for (const line of lines) {
|
|
718
|
+
if (line.startsWith("data:")) yield line.slice(5).trim();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (buffer.startsWith("data:")) yield buffer.slice(5).trim();
|
|
722
|
+
} finally {
|
|
723
|
+
reader.releaseLock();
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function finalChunk(final, idempotencyKey) {
|
|
727
|
+
return {
|
|
728
|
+
delta: null,
|
|
729
|
+
wind: final.wind ?? null,
|
|
730
|
+
usage: final.usage ?? void 0,
|
|
731
|
+
settlement: final.settlement ?? "settled",
|
|
732
|
+
idempotencyKey
|
|
733
|
+
};
|
|
676
734
|
}
|
|
677
735
|
var TOLERANCE_S = 300;
|
|
678
736
|
var WebhookVerifier = class {
|
|
@@ -995,7 +1053,7 @@ var Wind = class {
|
|
|
995
1053
|
update: (appId, slug, patch) => this.raw.updateFeature(appId, slug, patch)
|
|
996
1054
|
};
|
|
997
1055
|
// ── webhooks ─────────────────────────────────────────────────────────────
|
|
998
|
-
/** Verify a webhook's `
|
|
1056
|
+
/** Verify a webhook's `Wind-Signature` and return the typed event. Needs the raw body. */
|
|
999
1057
|
verify(input) {
|
|
1000
1058
|
return this.#verifier.verify(input);
|
|
1001
1059
|
}
|