@usewind/node 0.1.0 → 0.1.1

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/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,10 +79,15 @@ interface ChatParams {
56
79
  signal?: AbortSignal;
57
80
  }
58
81
  interface ChatStreamChunk {
59
- /** Raw provider SSE `data:` payload, JSON-parsed. */
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
87
  /** The final chunk of a stream carries the settled `wind` block; `null` on all others. */
62
88
  wind: WindBlock | null;
89
+ /** Final token counts — on the final chunk only. */
90
+ usage?: ChatUsage;
63
91
  }
64
92
  /** 'frozen' — Wind's own risk layer, independent of anything you or the user configured; clears when the user reconnects. */
65
93
  type ConnectionStatus = 'active' | 'exhausted' | 'revoked' | 'frozen';
@@ -213,6 +241,12 @@ interface RawRequest {
213
241
  /** Use HTTP Basic client credentials for `Authorization`. */
214
242
  basic?: boolean;
215
243
  signal?: AbortSignal;
244
+ /**
245
+ * When the response is a 2xx `text/event-stream`, return its unread
246
+ * `ReadableStream` as `body` instead of buffering it — `.chatStream()`
247
+ * parses the SSE events as they arrive.
248
+ */
249
+ stream?: boolean;
216
250
  }
217
251
  interface RawResponse<T = unknown> {
218
252
  status: number;
@@ -592,7 +626,7 @@ declare class Wind {
592
626
  register: (appId: string, feature: Feature) => Promise<Feature>;
593
627
  update: (appId: string, slug: string, patch: Partial<Feature>) => Promise<Feature>;
594
628
  };
595
- /** Verify a webhook's `X-Wind-Signature` and return the typed event. Needs the raw body. */
629
+ /** Verify a webhook's `Wind-Signature` and return the typed event. Needs the raw body. */
596
630
  verify(input: {
597
631
  payload: string | Buffer;
598
632
  signature: string;
@@ -606,4 +640,4 @@ declare function createWind(config?: Partial<WindConfig>): Wind;
606
640
  */
607
641
  declare const wind: Wind;
608
642
 
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 };
643
+ export { type Account, AllocationExhaustedError, type AllocationRequest, type AllocationRequestParams, AppSuspendedError, type ChatMessage, type ChatParams, type ChatResult, 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,51 @@ 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
- const contentType = res.headers.get("content-type") ?? "";
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 };
673
683
  return;
674
684
  }
675
- yield { delta: res.body, wind: null };
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 { delta: null, wind: data.wind_final.wind ?? null, usage: data.wind_final.usage };
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 && data.wind) {
700
+ yield { delta: null, wind: data.wind, usage: data.usage };
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
+ }
676
725
  }
677
726
  var TOLERANCE_S = 300;
678
727
  var WebhookVerifier = class {
@@ -995,7 +1044,7 @@ var Wind = class {
995
1044
  update: (appId, slug, patch) => this.raw.updateFeature(appId, slug, patch)
996
1045
  };
997
1046
  // ── webhooks ─────────────────────────────────────────────────────────────
998
- /** Verify a webhook's `X-Wind-Signature` and return the typed event. Needs the raw body. */
1047
+ /** Verify a webhook's `Wind-Signature` and return the typed event. Needs the raw body. */
999
1048
  verify(input) {
1000
1049
  return this.#verifier.verify(input);
1001
1050
  }