@openkeyai/sdk 0.1.0 → 0.3.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,22 @@ 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
+ }
281
+ const rawName = payload.name;
282
+ const name = typeof rawName === "string" && rawName.length > 0 ? rawName : void 0;
218
283
  return {
219
284
  iss: "https://openkeyai.com",
220
285
  sub,
221
286
  aud,
222
287
  scopes,
223
288
  subscription_active: subscriptionActive,
289
+ mode,
290
+ ...name ? { name } : {},
224
291
  iat,
225
292
  exp,
226
293
  jti
@@ -355,6 +422,298 @@ async function get(jwt, provider, opts = {}) {
355
422
  });
356
423
  }
357
424
 
425
+ // src/proxy.ts
426
+ var PROXY_PATH = "/api/proxy";
427
+ function ensureBearer(token) {
428
+ if (!token || typeof token !== "string") {
429
+ throw new BadTokenError("No JWT provided.");
430
+ }
431
+ }
432
+ function buildUrl(opts) {
433
+ if (!opts.provider) {
434
+ throw new BadTokenError("ProxyCallOptions.provider is required.");
435
+ }
436
+ if (!opts.path || !opts.path.startsWith("/")) {
437
+ throw new BadTokenError(
438
+ "ProxyCallOptions.path must start with '/' (e.g. '/v1/chat/completions')."
439
+ );
440
+ }
441
+ const hubUrl = opts.hubUrl ?? "https://openkeyai.com";
442
+ return new URL(
443
+ `${PROXY_PATH}/${encodeURIComponent(opts.provider)}${opts.path}`,
444
+ hubUrl
445
+ );
446
+ }
447
+ function buildRequestInit(token, opts) {
448
+ const headers = new Headers();
449
+ if (opts.headers) {
450
+ for (const [k, v] of Object.entries(opts.headers)) {
451
+ headers.set(k, v);
452
+ }
453
+ }
454
+ headers.set("authorization", `Bearer ${token}`);
455
+ let body;
456
+ if (opts.method === "GET" || opts.method === "DELETE" || opts.body == null) {
457
+ body = void 0;
458
+ } else if (typeof opts.body === "string" || opts.body instanceof Blob || opts.body instanceof FormData || opts.body instanceof ArrayBuffer || opts.body instanceof ReadableStream) {
459
+ body = opts.body;
460
+ } else {
461
+ body = JSON.stringify(opts.body);
462
+ if (!headers.has("content-type")) {
463
+ headers.set("content-type", "application/json");
464
+ }
465
+ }
466
+ if (!headers.has("accept")) {
467
+ headers.set("accept", "application/json");
468
+ }
469
+ return {
470
+ method: opts.method,
471
+ headers,
472
+ body,
473
+ signal: opts.signal,
474
+ // @ts-expect-error duplex is part of fetch's RequestInit on Workers + recent Node
475
+ duplex: body instanceof ReadableStream ? "half" : void 0
476
+ };
477
+ }
478
+ async function rawFetch(token, opts) {
479
+ ensureBearer(token);
480
+ const url = buildUrl(opts);
481
+ try {
482
+ return await fetch(url.toString(), buildRequestInit(token, opts));
483
+ } catch (err) {
484
+ if (err instanceof DOMException && err.name === "AbortError") {
485
+ throw err;
486
+ }
487
+ throw new NetworkError(err);
488
+ }
489
+ }
490
+ async function parseBodyByContentType(response) {
491
+ const ct = response.headers.get("content-type") ?? "";
492
+ if (ct.includes("application/json")) {
493
+ try {
494
+ return await response.json();
495
+ } catch {
496
+ return null;
497
+ }
498
+ }
499
+ try {
500
+ return await response.text();
501
+ } catch {
502
+ return null;
503
+ }
504
+ }
505
+ async function handleResponse(response, opts, expectJson) {
506
+ if (response.ok) {
507
+ if (!expectJson) return response;
508
+ return await response.json();
509
+ }
510
+ const body = await parseBodyByContentType(response);
511
+ if (isHubErrorBody(body)) {
512
+ const retryAfter = parseInt(response.headers.get("retry-after") ?? "60", 10);
513
+ throw errorFromResponse(
514
+ response.status,
515
+ { error: body.error },
516
+ {
517
+ provider: opts.provider,
518
+ scopeNeeded: "keys.read",
519
+ retryAfter: Number.isFinite(retryAfter) ? retryAfter : 60
520
+ }
521
+ );
522
+ }
523
+ throw new ProviderError(opts.provider, opts.path, response.status, body);
524
+ }
525
+ async function call(token, opts) {
526
+ const response = await rawFetch(token, opts);
527
+ return handleResponse(response, opts, true);
528
+ }
529
+ async function callRaw(token, opts) {
530
+ const response = await rawFetch(token, opts);
531
+ return handleResponse(response, opts, false);
532
+ }
533
+ async function callStream(token, opts) {
534
+ const response = await rawFetch(token, opts);
535
+ if (!response.ok) {
536
+ const body = await parseBodyByContentType(response);
537
+ if (isHubErrorBody(body)) {
538
+ const retryAfter = parseInt(
539
+ response.headers.get("retry-after") ?? "60",
540
+ 10
541
+ );
542
+ throw errorFromResponse(
543
+ response.status,
544
+ { error: body.error },
545
+ {
546
+ provider: opts.provider,
547
+ scopeNeeded: "keys.read",
548
+ retryAfter: Number.isFinite(retryAfter) ? retryAfter : 60
549
+ }
550
+ );
551
+ }
552
+ throw new ProviderError(opts.provider, opts.path, response.status, body);
553
+ }
554
+ if (response.body == null) {
555
+ throw new BadTokenError("Upstream returned 2xx with no body to stream.");
556
+ }
557
+ return response.body;
558
+ }
559
+
560
+ // src/providers/openai.ts
561
+ var openai = {
562
+ images: {
563
+ generate(token, params, opts = {}) {
564
+ return call(token, {
565
+ ...opts,
566
+ provider: "openai",
567
+ method: "POST",
568
+ path: "/v1/images/generations",
569
+ body: params
570
+ });
571
+ }
572
+ },
573
+ chat: {
574
+ completions: {
575
+ create(token, params, opts = {}) {
576
+ return call(token, {
577
+ ...opts,
578
+ provider: "openai",
579
+ method: "POST",
580
+ path: "/v1/chat/completions",
581
+ body: params
582
+ });
583
+ },
584
+ stream(token, params, opts = {}) {
585
+ return callStream(token, {
586
+ ...opts,
587
+ provider: "openai",
588
+ method: "POST",
589
+ path: "/v1/chat/completions",
590
+ body: { ...params, stream: true }
591
+ });
592
+ }
593
+ }
594
+ },
595
+ embeddings: {
596
+ create(token, params, opts = {}) {
597
+ return call(token, {
598
+ ...opts,
599
+ provider: "openai",
600
+ method: "POST",
601
+ path: "/v1/embeddings",
602
+ body: params
603
+ });
604
+ }
605
+ },
606
+ audio: {
607
+ transcriptions: {
608
+ create(token, params, opts = {}) {
609
+ const fd = new FormData();
610
+ fd.append("file", params.file, params.filename ?? "audio.webm");
611
+ fd.append("model", params.model);
612
+ if (params.language) fd.append("language", params.language);
613
+ if (params.prompt) fd.append("prompt", params.prompt);
614
+ if (params.response_format)
615
+ fd.append("response_format", params.response_format);
616
+ if (params.temperature !== void 0)
617
+ fd.append("temperature", String(params.temperature));
618
+ return call(token, {
619
+ ...opts,
620
+ provider: "openai",
621
+ method: "POST",
622
+ path: "/v1/audio/transcriptions",
623
+ body: fd
624
+ });
625
+ }
626
+ },
627
+ speech: {
628
+ create(token, params, opts = {}) {
629
+ return callRaw(token, {
630
+ ...opts,
631
+ provider: "openai",
632
+ method: "POST",
633
+ path: "/v1/audio/speech",
634
+ body: params
635
+ });
636
+ }
637
+ }
638
+ }
639
+ };
640
+
641
+ // src/providers/anthropic.ts
642
+ var anthropic = {
643
+ messages: {
644
+ create(token, params, opts = {}) {
645
+ return call(token, {
646
+ ...opts,
647
+ provider: "anthropic",
648
+ method: "POST",
649
+ path: "/v1/messages",
650
+ body: params
651
+ });
652
+ },
653
+ stream(token, params, opts = {}) {
654
+ return callStream(token, {
655
+ ...opts,
656
+ provider: "anthropic",
657
+ method: "POST",
658
+ path: "/v1/messages",
659
+ body: { ...params, stream: true }
660
+ });
661
+ }
662
+ }
663
+ };
664
+
665
+ // src/providers/replicate.ts
666
+ var replicate = {
667
+ predictions: {
668
+ create(token, params, opts = {}) {
669
+ return call(token, {
670
+ ...opts,
671
+ provider: "replicate",
672
+ method: "POST",
673
+ path: "/v1/predictions",
674
+ body: params
675
+ });
676
+ },
677
+ get(token, predictionId, opts = {}) {
678
+ return call(token, {
679
+ ...opts,
680
+ provider: "replicate",
681
+ method: "GET",
682
+ path: `/v1/predictions/${encodeURIComponent(predictionId)}`
683
+ });
684
+ },
685
+ cancel(token, predictionId, opts = {}) {
686
+ return call(token, {
687
+ ...opts,
688
+ provider: "replicate",
689
+ method: "POST",
690
+ path: `/v1/predictions/${encodeURIComponent(predictionId)}/cancel`
691
+ });
692
+ },
693
+ /**
694
+ * Convenience: create + poll until the prediction reaches a terminal
695
+ * status. Polls every `pollIntervalMs` (default 1000ms) with the
696
+ * provided AbortSignal honoured.
697
+ *
698
+ * For long-running predictions consider using webhooks via
699
+ * `create({ webhook, webhook_events_filter })` instead so you're not
700
+ * holding open a long fetch.
701
+ */
702
+ async run(token, params, opts = {}) {
703
+ const interval = opts.pollIntervalMs ?? 1e3;
704
+ let prediction = await this.create(token, params, opts);
705
+ while (prediction.status === "starting" || prediction.status === "processing") {
706
+ if (opts.signal?.aborted) {
707
+ throw opts.signal.reason ?? new DOMException("Aborted", "AbortError");
708
+ }
709
+ await new Promise((resolve) => setTimeout(resolve, interval));
710
+ prediction = await this.get(token, prediction.id, opts);
711
+ }
712
+ return prediction;
713
+ }
714
+ }
715
+ };
716
+
358
717
  // src/index.ts
359
718
  var session = {
360
719
  verify
@@ -362,8 +721,13 @@ var session = {
362
721
  var keys = {
363
722
  get
364
723
  };
365
- var SDK_VERSION = "0.1.0";
724
+ var proxy = {
725
+ call,
726
+ callRaw,
727
+ callStream
728
+ };
729
+ var SDK_VERSION = "0.2.0";
366
730
 
367
- export { BadTokenError, HubSdkError, InternalError, KeyNotFoundError, MissingScopeError, MissingTokenError, NetworkError, NotSubscribedError, ProviderNotGrantedError, RateLimitedError, SDK_VERSION, SecureKey, SecureKeyConsumedError, SubscriptionInactiveError, ToolNotFoundError, keys, session };
731
+ 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
732
  //# sourceMappingURL=index.js.map
369
733
  //# sourceMappingURL=index.js.map