@openkeyai/sdk 0.1.0 → 0.2.0

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.js CHANGED
@@ -117,6 +117,41 @@ var SecureKeyConsumedError = class extends HubSdkError {
117
117
  this.name = "SecureKeyConsumedError";
118
118
  }
119
119
  };
120
+ var ProviderNotConfiguredError = class extends HubSdkError {
121
+ constructor(provider) {
122
+ super(
123
+ "provider_not_configured",
124
+ `Provider "${provider}" is not registered with the hub proxy. Available providers are listed at /api/proxy (coming) or in the hub's src/lib/proxy/providers.ts.`,
125
+ 404
126
+ );
127
+ this.name = "ProviderNotConfiguredError";
128
+ }
129
+ };
130
+ var ProviderError = class extends HubSdkError {
131
+ /** The provider slug we tried to call. */
132
+ provider;
133
+ /** The path on the provider that returned non-2xx. */
134
+ path;
135
+ /**
136
+ * The upstream response body. Parsed as JSON when the upstream's
137
+ * content-type was JSON; raw text otherwise.
138
+ */
139
+ body;
140
+ /** The upstream response status code (relayed unchanged). */
141
+ upstreamStatus;
142
+ constructor(provider, path, upstreamStatus, body) {
143
+ super(
144
+ "provider_error",
145
+ `${provider} ${path} returned ${upstreamStatus}.`,
146
+ upstreamStatus
147
+ );
148
+ this.name = "ProviderError";
149
+ this.provider = provider;
150
+ this.path = path;
151
+ this.upstreamStatus = upstreamStatus;
152
+ this.body = body;
153
+ }
154
+ };
120
155
  function errorFromResponse(status, body, context) {
121
156
  const code = body?.error ?? "internal";
122
157
  switch (code) {
@@ -134,6 +169,8 @@ function errorFromResponse(status, body, context) {
134
169
  return new NotSubscribedError();
135
170
  case "provider_not_granted":
136
171
  return new ProviderNotGrantedError(context.provider ?? "unknown");
172
+ case "provider_not_configured":
173
+ return new ProviderNotConfiguredError(context.provider ?? "unknown");
137
174
  case "rate_limited":
138
175
  return new RateLimitedError(context.retryAfter ?? 60);
139
176
  case "key_not_found":
@@ -143,6 +180,25 @@ function errorFromResponse(status, body, context) {
143
180
  return new InternalError(status);
144
181
  }
145
182
  }
183
+ function isHubErrorBody(body) {
184
+ if (typeof body !== "object" || body === null) return false;
185
+ const errorField = body.error;
186
+ if (typeof errorField !== "string") return false;
187
+ const knownCodes = [
188
+ "missing_token",
189
+ "bad_token",
190
+ "missing_scope",
191
+ "subscription_inactive",
192
+ "tool_not_found",
193
+ "not_subscribed",
194
+ "provider_not_granted",
195
+ "rate_limited",
196
+ "key_not_found",
197
+ "internal",
198
+ "provider_not_configured"
199
+ ];
200
+ return knownCodes.includes(errorField);
201
+ }
146
202
  var resolvers = /* @__PURE__ */ new Map();
147
203
  function getJwksResolver(hubUrl) {
148
204
  const cached = resolvers.get(hubUrl);
@@ -197,6 +253,7 @@ async function verify(jwt, opts = {}) {
197
253
  const jti = payload.jti;
198
254
  const iat = payload.iat;
199
255
  const exp = payload.exp;
256
+ const rawMode = payload.mode;
200
257
  if (typeof sub !== "string" || sub.length === 0) {
201
258
  throw new BadTokenError("sub claim missing or invalid.");
202
259
  }
@@ -215,12 +272,19 @@ async function verify(jwt, opts = {}) {
215
272
  if (typeof iat !== "number" || typeof exp !== "number") {
216
273
  throw new BadTokenError("iat / exp claims missing or invalid.");
217
274
  }
275
+ const mode = typeof rawMode === "string" && (rawMode === "production" || rawMode === "owner_test") ? rawMode : rawMode === void 0 ? "production" : null;
276
+ if (mode === null) {
277
+ throw new BadTokenError(
278
+ `mode claim must be 'production' or 'owner_test' (got ${JSON.stringify(rawMode)}).`
279
+ );
280
+ }
218
281
  return {
219
282
  iss: "https://openkeyai.com",
220
283
  sub,
221
284
  aud,
222
285
  scopes,
223
286
  subscription_active: subscriptionActive,
287
+ mode,
224
288
  iat,
225
289
  exp,
226
290
  jti
@@ -355,6 +419,298 @@ async function get(jwt, provider, opts = {}) {
355
419
  });
356
420
  }
357
421
 
422
+ // src/proxy.ts
423
+ var PROXY_PATH = "/api/proxy";
424
+ function ensureBearer(token) {
425
+ if (!token || typeof token !== "string") {
426
+ throw new BadTokenError("No JWT provided.");
427
+ }
428
+ }
429
+ function buildUrl(opts) {
430
+ if (!opts.provider) {
431
+ throw new BadTokenError("ProxyCallOptions.provider is required.");
432
+ }
433
+ if (!opts.path || !opts.path.startsWith("/")) {
434
+ throw new BadTokenError(
435
+ "ProxyCallOptions.path must start with '/' (e.g. '/v1/chat/completions')."
436
+ );
437
+ }
438
+ const hubUrl = opts.hubUrl ?? "https://openkeyai.com";
439
+ return new URL(
440
+ `${PROXY_PATH}/${encodeURIComponent(opts.provider)}${opts.path}`,
441
+ hubUrl
442
+ );
443
+ }
444
+ function buildRequestInit(token, opts) {
445
+ const headers = new Headers();
446
+ if (opts.headers) {
447
+ for (const [k, v] of Object.entries(opts.headers)) {
448
+ headers.set(k, v);
449
+ }
450
+ }
451
+ headers.set("authorization", `Bearer ${token}`);
452
+ let body;
453
+ if (opts.method === "GET" || opts.method === "DELETE" || opts.body == null) {
454
+ body = void 0;
455
+ } else if (typeof opts.body === "string" || opts.body instanceof Blob || opts.body instanceof FormData || opts.body instanceof ArrayBuffer || opts.body instanceof ReadableStream) {
456
+ body = opts.body;
457
+ } else {
458
+ body = JSON.stringify(opts.body);
459
+ if (!headers.has("content-type")) {
460
+ headers.set("content-type", "application/json");
461
+ }
462
+ }
463
+ if (!headers.has("accept")) {
464
+ headers.set("accept", "application/json");
465
+ }
466
+ return {
467
+ method: opts.method,
468
+ headers,
469
+ body,
470
+ signal: opts.signal,
471
+ // @ts-expect-error duplex is part of fetch's RequestInit on Workers + recent Node
472
+ duplex: body instanceof ReadableStream ? "half" : void 0
473
+ };
474
+ }
475
+ async function rawFetch(token, opts) {
476
+ ensureBearer(token);
477
+ const url = buildUrl(opts);
478
+ try {
479
+ return await fetch(url.toString(), buildRequestInit(token, opts));
480
+ } catch (err) {
481
+ if (err instanceof DOMException && err.name === "AbortError") {
482
+ throw err;
483
+ }
484
+ throw new NetworkError(err);
485
+ }
486
+ }
487
+ async function parseBodyByContentType(response) {
488
+ const ct = response.headers.get("content-type") ?? "";
489
+ if (ct.includes("application/json")) {
490
+ try {
491
+ return await response.json();
492
+ } catch {
493
+ return null;
494
+ }
495
+ }
496
+ try {
497
+ return await response.text();
498
+ } catch {
499
+ return null;
500
+ }
501
+ }
502
+ async function handleResponse(response, opts, expectJson) {
503
+ if (response.ok) {
504
+ if (!expectJson) return response;
505
+ return await response.json();
506
+ }
507
+ const body = await parseBodyByContentType(response);
508
+ if (isHubErrorBody(body)) {
509
+ const retryAfter = parseInt(response.headers.get("retry-after") ?? "60", 10);
510
+ throw errorFromResponse(
511
+ response.status,
512
+ { error: body.error },
513
+ {
514
+ provider: opts.provider,
515
+ scopeNeeded: "keys.read",
516
+ retryAfter: Number.isFinite(retryAfter) ? retryAfter : 60
517
+ }
518
+ );
519
+ }
520
+ throw new ProviderError(opts.provider, opts.path, response.status, body);
521
+ }
522
+ async function call(token, opts) {
523
+ const response = await rawFetch(token, opts);
524
+ return handleResponse(response, opts, true);
525
+ }
526
+ async function callRaw(token, opts) {
527
+ const response = await rawFetch(token, opts);
528
+ return handleResponse(response, opts, false);
529
+ }
530
+ async function callStream(token, opts) {
531
+ const response = await rawFetch(token, opts);
532
+ if (!response.ok) {
533
+ const body = await parseBodyByContentType(response);
534
+ if (isHubErrorBody(body)) {
535
+ const retryAfter = parseInt(
536
+ response.headers.get("retry-after") ?? "60",
537
+ 10
538
+ );
539
+ throw errorFromResponse(
540
+ response.status,
541
+ { error: body.error },
542
+ {
543
+ provider: opts.provider,
544
+ scopeNeeded: "keys.read",
545
+ retryAfter: Number.isFinite(retryAfter) ? retryAfter : 60
546
+ }
547
+ );
548
+ }
549
+ throw new ProviderError(opts.provider, opts.path, response.status, body);
550
+ }
551
+ if (response.body == null) {
552
+ throw new BadTokenError("Upstream returned 2xx with no body to stream.");
553
+ }
554
+ return response.body;
555
+ }
556
+
557
+ // src/providers/openai.ts
558
+ var openai = {
559
+ images: {
560
+ generate(token, params, opts = {}) {
561
+ return call(token, {
562
+ ...opts,
563
+ provider: "openai",
564
+ method: "POST",
565
+ path: "/v1/images/generations",
566
+ body: params
567
+ });
568
+ }
569
+ },
570
+ chat: {
571
+ completions: {
572
+ create(token, params, opts = {}) {
573
+ return call(token, {
574
+ ...opts,
575
+ provider: "openai",
576
+ method: "POST",
577
+ path: "/v1/chat/completions",
578
+ body: params
579
+ });
580
+ },
581
+ stream(token, params, opts = {}) {
582
+ return callStream(token, {
583
+ ...opts,
584
+ provider: "openai",
585
+ method: "POST",
586
+ path: "/v1/chat/completions",
587
+ body: { ...params, stream: true }
588
+ });
589
+ }
590
+ }
591
+ },
592
+ embeddings: {
593
+ create(token, params, opts = {}) {
594
+ return call(token, {
595
+ ...opts,
596
+ provider: "openai",
597
+ method: "POST",
598
+ path: "/v1/embeddings",
599
+ body: params
600
+ });
601
+ }
602
+ },
603
+ audio: {
604
+ transcriptions: {
605
+ create(token, params, opts = {}) {
606
+ const fd = new FormData();
607
+ fd.append("file", params.file, params.filename ?? "audio.webm");
608
+ fd.append("model", params.model);
609
+ if (params.language) fd.append("language", params.language);
610
+ if (params.prompt) fd.append("prompt", params.prompt);
611
+ if (params.response_format)
612
+ fd.append("response_format", params.response_format);
613
+ if (params.temperature !== void 0)
614
+ fd.append("temperature", String(params.temperature));
615
+ return call(token, {
616
+ ...opts,
617
+ provider: "openai",
618
+ method: "POST",
619
+ path: "/v1/audio/transcriptions",
620
+ body: fd
621
+ });
622
+ }
623
+ },
624
+ speech: {
625
+ create(token, params, opts = {}) {
626
+ return callRaw(token, {
627
+ ...opts,
628
+ provider: "openai",
629
+ method: "POST",
630
+ path: "/v1/audio/speech",
631
+ body: params
632
+ });
633
+ }
634
+ }
635
+ }
636
+ };
637
+
638
+ // src/providers/anthropic.ts
639
+ var anthropic = {
640
+ messages: {
641
+ create(token, params, opts = {}) {
642
+ return call(token, {
643
+ ...opts,
644
+ provider: "anthropic",
645
+ method: "POST",
646
+ path: "/v1/messages",
647
+ body: params
648
+ });
649
+ },
650
+ stream(token, params, opts = {}) {
651
+ return callStream(token, {
652
+ ...opts,
653
+ provider: "anthropic",
654
+ method: "POST",
655
+ path: "/v1/messages",
656
+ body: { ...params, stream: true }
657
+ });
658
+ }
659
+ }
660
+ };
661
+
662
+ // src/providers/replicate.ts
663
+ var replicate = {
664
+ predictions: {
665
+ create(token, params, opts = {}) {
666
+ return call(token, {
667
+ ...opts,
668
+ provider: "replicate",
669
+ method: "POST",
670
+ path: "/v1/predictions",
671
+ body: params
672
+ });
673
+ },
674
+ get(token, predictionId, opts = {}) {
675
+ return call(token, {
676
+ ...opts,
677
+ provider: "replicate",
678
+ method: "GET",
679
+ path: `/v1/predictions/${encodeURIComponent(predictionId)}`
680
+ });
681
+ },
682
+ cancel(token, predictionId, opts = {}) {
683
+ return call(token, {
684
+ ...opts,
685
+ provider: "replicate",
686
+ method: "POST",
687
+ path: `/v1/predictions/${encodeURIComponent(predictionId)}/cancel`
688
+ });
689
+ },
690
+ /**
691
+ * Convenience: create + poll until the prediction reaches a terminal
692
+ * status. Polls every `pollIntervalMs` (default 1000ms) with the
693
+ * provided AbortSignal honoured.
694
+ *
695
+ * For long-running predictions consider using webhooks via
696
+ * `create({ webhook, webhook_events_filter })` instead so you're not
697
+ * holding open a long fetch.
698
+ */
699
+ async run(token, params, opts = {}) {
700
+ const interval = opts.pollIntervalMs ?? 1e3;
701
+ let prediction = await this.create(token, params, opts);
702
+ while (prediction.status === "starting" || prediction.status === "processing") {
703
+ if (opts.signal?.aborted) {
704
+ throw opts.signal.reason ?? new DOMException("Aborted", "AbortError");
705
+ }
706
+ await new Promise((resolve) => setTimeout(resolve, interval));
707
+ prediction = await this.get(token, prediction.id, opts);
708
+ }
709
+ return prediction;
710
+ }
711
+ }
712
+ };
713
+
358
714
  // src/index.ts
359
715
  var session = {
360
716
  verify
@@ -362,8 +718,13 @@ var session = {
362
718
  var keys = {
363
719
  get
364
720
  };
365
- var SDK_VERSION = "0.1.0";
721
+ var proxy = {
722
+ call,
723
+ callRaw,
724
+ callStream
725
+ };
726
+ var SDK_VERSION = "0.2.0";
366
727
 
367
- export { BadTokenError, HubSdkError, InternalError, KeyNotFoundError, MissingScopeError, MissingTokenError, NetworkError, NotSubscribedError, ProviderNotGrantedError, RateLimitedError, SDK_VERSION, SecureKey, SecureKeyConsumedError, SubscriptionInactiveError, ToolNotFoundError, keys, session };
728
+ export { BadTokenError, HubSdkError, InternalError, KeyNotFoundError, MissingScopeError, MissingTokenError, NetworkError, NotSubscribedError, ProviderError, ProviderNotConfiguredError, ProviderNotGrantedError, RateLimitedError, SDK_VERSION, SecureKey, SecureKeyConsumedError, SubscriptionInactiveError, ToolNotFoundError, anthropic, keys, openai, proxy, replicate, session };
368
729
  //# sourceMappingURL=index.js.map
369
730
  //# sourceMappingURL=index.js.map