@brokr/sdk 1.0.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/auth.js +175 -0
- package/dist/auth.mjs +151 -0
- package/dist/brokr_runtime.py +333 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +568 -0
- package/dist/index.mjs +558 -0
- package/dist/management.js +92 -0
- package/dist/management.mjs +66 -0
- package/dist/react.js +264 -0
- package/dist/react.mjs +225 -0
- package/dist/runtime.js +512 -0
- package/dist/runtime.mjs +501 -0
- package/dist/src/auth.d.ts +70 -0
- package/dist/src/auth.d.ts.map +1 -0
- package/dist/src/management.d.ts +68 -0
- package/dist/src/management.d.ts.map +1 -0
- package/dist/src/react/auth.d.ts +50 -0
- package/dist/src/react/auth.d.ts.map +1 -0
- package/dist/src/runtime.d.ts +217 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +79 -0
package/dist/auth.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
|
|
23
|
+
// src/runtime.ts
|
|
24
|
+
var BrokrError;
|
|
25
|
+
var init_runtime = __esm({
|
|
26
|
+
"src/runtime.ts"() {
|
|
27
|
+
"use strict";
|
|
28
|
+
BrokrError = class extends Error {
|
|
29
|
+
constructor(message, code, capability, retryable = false) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.capability = capability;
|
|
33
|
+
this.retryable = retryable;
|
|
34
|
+
this.name = "BrokrError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/auth.ts
|
|
41
|
+
var auth_exports = {};
|
|
42
|
+
__export(auth_exports, {
|
|
43
|
+
BrokrAuthClient: () => BrokrAuthClient,
|
|
44
|
+
authMiddleware: () => authMiddleware
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(auth_exports);
|
|
47
|
+
function parseCookies(cookieHeader) {
|
|
48
|
+
const cookies = /* @__PURE__ */ new Map();
|
|
49
|
+
for (const pair of cookieHeader.split(";")) {
|
|
50
|
+
const eqIdx = pair.indexOf("=");
|
|
51
|
+
if (eqIdx === -1) continue;
|
|
52
|
+
const name = pair.slice(0, eqIdx).trim();
|
|
53
|
+
const value = pair.slice(eqIdx + 1).trim();
|
|
54
|
+
if (name) cookies.set(name, value);
|
|
55
|
+
}
|
|
56
|
+
return cookies;
|
|
57
|
+
}
|
|
58
|
+
function authMiddleware(options) {
|
|
59
|
+
const { protectedRoutes = [], publicOnlyRoutes = [] } = options;
|
|
60
|
+
return async function middleware(request) {
|
|
61
|
+
const url = new URL(request.url);
|
|
62
|
+
const pathname = url.pathname;
|
|
63
|
+
const isProtected = protectedRoutes.some((route) => pathname.startsWith(route));
|
|
64
|
+
const isPublicOnly = publicOnlyRoutes.some((route) => pathname.startsWith(route));
|
|
65
|
+
if (!isProtected && !isPublicOnly) return void 0;
|
|
66
|
+
const cookieHeader = request.headers.get("cookie") ?? "";
|
|
67
|
+
const cookies = parseCookies(cookieHeader);
|
|
68
|
+
const hasSession = cookies.has("better-auth.session_token");
|
|
69
|
+
if (isProtected && !hasSession) {
|
|
70
|
+
return Response.redirect(new URL("/sign-in", request.url).toString(), 302);
|
|
71
|
+
}
|
|
72
|
+
if (isPublicOnly && hasSession) {
|
|
73
|
+
return Response.redirect(new URL("/", request.url).toString(), 302);
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
var BrokrAuthClient;
|
|
79
|
+
var init_auth = __esm({
|
|
80
|
+
"src/auth.ts"() {
|
|
81
|
+
init_runtime();
|
|
82
|
+
BrokrAuthClient = class {
|
|
83
|
+
constructor(token, gatewayUrl, appUrl) {
|
|
84
|
+
this._token = token;
|
|
85
|
+
this._gatewayUrl = gatewayUrl;
|
|
86
|
+
this._appUrl = appUrl ?? (typeof process !== "undefined" ? process.env.BETTER_AUTH_URL : void 0);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get current user from an incoming request's cookies.
|
|
90
|
+
* Calls the local Better Auth API (runs inside your app).
|
|
91
|
+
*/
|
|
92
|
+
async currentUser(request) {
|
|
93
|
+
const session = await this.getSession(request);
|
|
94
|
+
return session?.user ?? null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the full session (user + metadata) from an incoming request.
|
|
98
|
+
* Calls the local Better Auth API.
|
|
99
|
+
*/
|
|
100
|
+
async getSession(request) {
|
|
101
|
+
const appUrl = this._appUrl;
|
|
102
|
+
if (!appUrl) {
|
|
103
|
+
throw new BrokrError(
|
|
104
|
+
"[brokr] BETTER_AUTH_URL is not set. Auth may not be provisioned.",
|
|
105
|
+
"AUTH_NOT_CONFIGURED",
|
|
106
|
+
"auth"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const cookieHeader = request.headers.get("cookie") ?? "";
|
|
110
|
+
if (!cookieHeader) return null;
|
|
111
|
+
const res = await fetch(`${appUrl}/api/auth/get-session`, {
|
|
112
|
+
method: "GET",
|
|
113
|
+
headers: { cookie: cookieHeader }
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) return null;
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
if (!data?.user || !data?.session) return null;
|
|
118
|
+
return {
|
|
119
|
+
user: {
|
|
120
|
+
id: data.user.id,
|
|
121
|
+
email: data.user.email,
|
|
122
|
+
name: data.user.name ?? null,
|
|
123
|
+
avatarUrl: data.user.image ?? null,
|
|
124
|
+
emailVerified: data.user.emailVerified ? /* @__PURE__ */ new Date() : null
|
|
125
|
+
},
|
|
126
|
+
sessionId: data.session.id,
|
|
127
|
+
expiresAt: new Date(data.session.expiresAt)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate an OAuth authorization URL.
|
|
132
|
+
*/
|
|
133
|
+
async getOAuthUrl(provider, options) {
|
|
134
|
+
const appUrl = this._appUrl;
|
|
135
|
+
if (!appUrl) {
|
|
136
|
+
throw new BrokrError("[brokr] BETTER_AUTH_URL is not set.", "AUTH_NOT_CONFIGURED", "auth");
|
|
137
|
+
}
|
|
138
|
+
const redirectTo = options?.redirectTo ?? "/";
|
|
139
|
+
if (!redirectTo.startsWith("/") || redirectTo.startsWith("//")) {
|
|
140
|
+
throw new BrokrError("[brokr] redirectTo must be a relative path (start with /)", "INVALID_REDIRECT", "auth");
|
|
141
|
+
}
|
|
142
|
+
const params = new URLSearchParams({ callbackURL: redirectTo });
|
|
143
|
+
return { redirectUrl: `${appUrl}/api/auth/sign-in/social?provider=${provider}&${params}` };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Send a magic link email (requires email capability).
|
|
147
|
+
*/
|
|
148
|
+
async sendMagicLink(email, options) {
|
|
149
|
+
const appUrl = this._appUrl;
|
|
150
|
+
if (!appUrl) {
|
|
151
|
+
throw new BrokrError("[brokr] BETTER_AUTH_URL is not set.", "AUTH_NOT_CONFIGURED", "auth");
|
|
152
|
+
}
|
|
153
|
+
const res = await fetch(`${appUrl}/api/auth/magic-link/send`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: { "Content-Type": "application/json" },
|
|
156
|
+
body: JSON.stringify({ email, callbackURL: options?.redirectTo ?? "/" })
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
const data = await res.json().catch(() => ({}));
|
|
160
|
+
throw new BrokrError(
|
|
161
|
+
data.message ?? `[brokr] Failed to send magic link (HTTP ${res.status})`,
|
|
162
|
+
"AUTH_MAGIC_LINK_FAILED",
|
|
163
|
+
"auth"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
init_auth();
|
|
171
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
172
|
+
0 && (module.exports = {
|
|
173
|
+
BrokrAuthClient,
|
|
174
|
+
authMiddleware
|
|
175
|
+
});
|
package/dist/auth.mjs
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/runtime.ts
|
|
7
|
+
var BrokrError;
|
|
8
|
+
var init_runtime = __esm({
|
|
9
|
+
"src/runtime.ts"() {
|
|
10
|
+
"use strict";
|
|
11
|
+
BrokrError = class extends Error {
|
|
12
|
+
constructor(message, code, capability, retryable = false) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.capability = capability;
|
|
16
|
+
this.retryable = retryable;
|
|
17
|
+
this.name = "BrokrError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// src/auth.ts
|
|
24
|
+
function parseCookies(cookieHeader) {
|
|
25
|
+
const cookies = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const pair of cookieHeader.split(";")) {
|
|
27
|
+
const eqIdx = pair.indexOf("=");
|
|
28
|
+
if (eqIdx === -1) continue;
|
|
29
|
+
const name = pair.slice(0, eqIdx).trim();
|
|
30
|
+
const value = pair.slice(eqIdx + 1).trim();
|
|
31
|
+
if (name) cookies.set(name, value);
|
|
32
|
+
}
|
|
33
|
+
return cookies;
|
|
34
|
+
}
|
|
35
|
+
function authMiddleware(options) {
|
|
36
|
+
const { protectedRoutes = [], publicOnlyRoutes = [] } = options;
|
|
37
|
+
return async function middleware(request) {
|
|
38
|
+
const url = new URL(request.url);
|
|
39
|
+
const pathname = url.pathname;
|
|
40
|
+
const isProtected = protectedRoutes.some((route) => pathname.startsWith(route));
|
|
41
|
+
const isPublicOnly = publicOnlyRoutes.some((route) => pathname.startsWith(route));
|
|
42
|
+
if (!isProtected && !isPublicOnly) return void 0;
|
|
43
|
+
const cookieHeader = request.headers.get("cookie") ?? "";
|
|
44
|
+
const cookies = parseCookies(cookieHeader);
|
|
45
|
+
const hasSession = cookies.has("better-auth.session_token");
|
|
46
|
+
if (isProtected && !hasSession) {
|
|
47
|
+
return Response.redirect(new URL("/sign-in", request.url).toString(), 302);
|
|
48
|
+
}
|
|
49
|
+
if (isPublicOnly && hasSession) {
|
|
50
|
+
return Response.redirect(new URL("/", request.url).toString(), 302);
|
|
51
|
+
}
|
|
52
|
+
return void 0;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
var BrokrAuthClient;
|
|
56
|
+
var init_auth = __esm({
|
|
57
|
+
"src/auth.ts"() {
|
|
58
|
+
init_runtime();
|
|
59
|
+
BrokrAuthClient = class {
|
|
60
|
+
constructor(token, gatewayUrl, appUrl) {
|
|
61
|
+
this._token = token;
|
|
62
|
+
this._gatewayUrl = gatewayUrl;
|
|
63
|
+
this._appUrl = appUrl ?? (typeof process !== "undefined" ? process.env.BETTER_AUTH_URL : void 0);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get current user from an incoming request's cookies.
|
|
67
|
+
* Calls the local Better Auth API (runs inside your app).
|
|
68
|
+
*/
|
|
69
|
+
async currentUser(request) {
|
|
70
|
+
const session = await this.getSession(request);
|
|
71
|
+
return session?.user ?? null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the full session (user + metadata) from an incoming request.
|
|
75
|
+
* Calls the local Better Auth API.
|
|
76
|
+
*/
|
|
77
|
+
async getSession(request) {
|
|
78
|
+
const appUrl = this._appUrl;
|
|
79
|
+
if (!appUrl) {
|
|
80
|
+
throw new BrokrError(
|
|
81
|
+
"[brokr] BETTER_AUTH_URL is not set. Auth may not be provisioned.",
|
|
82
|
+
"AUTH_NOT_CONFIGURED",
|
|
83
|
+
"auth"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const cookieHeader = request.headers.get("cookie") ?? "";
|
|
87
|
+
if (!cookieHeader) return null;
|
|
88
|
+
const res = await fetch(`${appUrl}/api/auth/get-session`, {
|
|
89
|
+
method: "GET",
|
|
90
|
+
headers: { cookie: cookieHeader }
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok) return null;
|
|
93
|
+
const data = await res.json();
|
|
94
|
+
if (!data?.user || !data?.session) return null;
|
|
95
|
+
return {
|
|
96
|
+
user: {
|
|
97
|
+
id: data.user.id,
|
|
98
|
+
email: data.user.email,
|
|
99
|
+
name: data.user.name ?? null,
|
|
100
|
+
avatarUrl: data.user.image ?? null,
|
|
101
|
+
emailVerified: data.user.emailVerified ? /* @__PURE__ */ new Date() : null
|
|
102
|
+
},
|
|
103
|
+
sessionId: data.session.id,
|
|
104
|
+
expiresAt: new Date(data.session.expiresAt)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Generate an OAuth authorization URL.
|
|
109
|
+
*/
|
|
110
|
+
async getOAuthUrl(provider, options) {
|
|
111
|
+
const appUrl = this._appUrl;
|
|
112
|
+
if (!appUrl) {
|
|
113
|
+
throw new BrokrError("[brokr] BETTER_AUTH_URL is not set.", "AUTH_NOT_CONFIGURED", "auth");
|
|
114
|
+
}
|
|
115
|
+
const redirectTo = options?.redirectTo ?? "/";
|
|
116
|
+
if (!redirectTo.startsWith("/") || redirectTo.startsWith("//")) {
|
|
117
|
+
throw new BrokrError("[brokr] redirectTo must be a relative path (start with /)", "INVALID_REDIRECT", "auth");
|
|
118
|
+
}
|
|
119
|
+
const params = new URLSearchParams({ callbackURL: redirectTo });
|
|
120
|
+
return { redirectUrl: `${appUrl}/api/auth/sign-in/social?provider=${provider}&${params}` };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Send a magic link email (requires email capability).
|
|
124
|
+
*/
|
|
125
|
+
async sendMagicLink(email, options) {
|
|
126
|
+
const appUrl = this._appUrl;
|
|
127
|
+
if (!appUrl) {
|
|
128
|
+
throw new BrokrError("[brokr] BETTER_AUTH_URL is not set.", "AUTH_NOT_CONFIGURED", "auth");
|
|
129
|
+
}
|
|
130
|
+
const res = await fetch(`${appUrl}/api/auth/magic-link/send`, {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: { "Content-Type": "application/json" },
|
|
133
|
+
body: JSON.stringify({ email, callbackURL: options?.redirectTo ?? "/" })
|
|
134
|
+
});
|
|
135
|
+
if (!res.ok) {
|
|
136
|
+
const data = await res.json().catch(() => ({}));
|
|
137
|
+
throw new BrokrError(
|
|
138
|
+
data.message ?? `[brokr] Failed to send magic link (HTTP ${res.status})`,
|
|
139
|
+
"AUTH_MAGIC_LINK_FAILED",
|
|
140
|
+
"auth"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
init_auth();
|
|
148
|
+
export {
|
|
149
|
+
BrokrAuthClient,
|
|
150
|
+
authMiddleware
|
|
151
|
+
};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brokr Runtime SDK — Python
|
|
3
|
+
==========================
|
|
4
|
+
|
|
5
|
+
Drop-in client for apps provisioned by Brokr.
|
|
6
|
+
Reads BROKR_TOKEN from env — auto-injected at deploy time.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from brokr_runtime import Brokr
|
|
11
|
+
|
|
12
|
+
brokr = Brokr()
|
|
13
|
+
reply = brokr.ai.chat([{"role": "user", "content": "Hello"}])
|
|
14
|
+
print(reply.content)
|
|
15
|
+
|
|
16
|
+
No dependencies beyond the Python standard library. Requires Python 3.8+.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import urllib.error
|
|
22
|
+
import urllib.request
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import Any, Dict, List, Optional, Union
|
|
25
|
+
|
|
26
|
+
_GATEWAY_URL = "https://api.brokr.sh"
|
|
27
|
+
_DEFAULT_TIMEOUT = 30
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Error hierarchy
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BrokrError(Exception):
|
|
36
|
+
"""Base Brokr SDK error."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, message: str, code: str = "UNKNOWN", capability: str = "",
|
|
39
|
+
retryable: bool = False):
|
|
40
|
+
super().__init__(message)
|
|
41
|
+
self.code = code
|
|
42
|
+
self.capability = capability
|
|
43
|
+
self.retryable = retryable
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BrokrAuthError(BrokrError):
|
|
47
|
+
"""Token missing or invalid."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, message: str, code: str = "BROKR_TOKEN_MISSING"):
|
|
50
|
+
super().__init__(message, code, "auth", False)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BrokrRateLimitError(BrokrError):
|
|
54
|
+
"""HTTP 429 — rate limited."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, message: str, retry_after: int = 60, capability: str = ""):
|
|
57
|
+
super().__init__(message, "RATE_LIMITED", capability, True)
|
|
58
|
+
self.retry_after = retry_after
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BrokrNetworkError(BrokrError):
|
|
62
|
+
"""Gateway unreachable."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, message: str, capability: str = ""):
|
|
65
|
+
super().__init__(message, "NETWORK_ERROR", capability, True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Response dataclasses
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class TokenUsage:
|
|
75
|
+
prompt_tokens: int
|
|
76
|
+
completion_tokens: int
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ChatResponse:
|
|
81
|
+
content: str
|
|
82
|
+
model: str
|
|
83
|
+
usage: TokenUsage
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class UploadResult:
|
|
88
|
+
key: str
|
|
89
|
+
url: str
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class EmailResult:
|
|
94
|
+
id: str
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# Internal helpers
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _validate_token(token: Optional[str], capability: str) -> str:
|
|
103
|
+
if not token:
|
|
104
|
+
raise BrokrAuthError(
|
|
105
|
+
"[brokr] BROKR_TOKEN is not set.\n"
|
|
106
|
+
"Brokr injects this automatically at deploy time.\n"
|
|
107
|
+
"For local development, run: brokr env pull --stack <name>"
|
|
108
|
+
)
|
|
109
|
+
return token
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _gateway_request(
|
|
113
|
+
gateway_url: str, token: str, path: str, payload: dict, capability: str,
|
|
114
|
+
timeout: int = _DEFAULT_TIMEOUT,
|
|
115
|
+
) -> Any:
|
|
116
|
+
data = json.dumps(payload).encode()
|
|
117
|
+
req = urllib.request.Request(
|
|
118
|
+
f"{gateway_url}{path}",
|
|
119
|
+
data=data,
|
|
120
|
+
headers={
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
"Authorization": f"Bearer {token}",
|
|
123
|
+
},
|
|
124
|
+
method="POST",
|
|
125
|
+
)
|
|
126
|
+
try:
|
|
127
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
128
|
+
return json.loads(resp.read())
|
|
129
|
+
except urllib.error.HTTPError as e:
|
|
130
|
+
if e.code == 429:
|
|
131
|
+
raise BrokrRateLimitError(
|
|
132
|
+
f"[brokr] Rate limited on {capability}.",
|
|
133
|
+
retry_after=60,
|
|
134
|
+
capability=capability,
|
|
135
|
+
) from e
|
|
136
|
+
if e.code == 401:
|
|
137
|
+
raise BrokrAuthError("[brokr] Invalid or expired BROKR_TOKEN.",
|
|
138
|
+
"BROKR_TOKEN_INVALID") from e
|
|
139
|
+
raise BrokrError(
|
|
140
|
+
f"[brokr] {capability} request failed: HTTP {e.code}",
|
|
141
|
+
f"{capability.upper()}_FAILED",
|
|
142
|
+
capability,
|
|
143
|
+
) from e
|
|
144
|
+
except (urllib.error.URLError, OSError) as e:
|
|
145
|
+
raise BrokrNetworkError(
|
|
146
|
+
f"[brokr] Could not reach Brokr gateway: {e}",
|
|
147
|
+
capability,
|
|
148
|
+
) from e
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# BrokrAI
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class BrokrAI:
|
|
157
|
+
def __init__(self, token: Optional[str], gateway_url: str) -> None:
|
|
158
|
+
self._token = token
|
|
159
|
+
self._gateway_url = gateway_url
|
|
160
|
+
|
|
161
|
+
def chat(
|
|
162
|
+
self,
|
|
163
|
+
messages: List[Dict[str, str]],
|
|
164
|
+
*,
|
|
165
|
+
model: Optional[str] = None,
|
|
166
|
+
max_tokens: Optional[int] = None,
|
|
167
|
+
temperature: Optional[float] = None,
|
|
168
|
+
) -> ChatResponse:
|
|
169
|
+
"""
|
|
170
|
+
Send messages to the Brokr AI gateway.
|
|
171
|
+
|
|
172
|
+
Returns a ChatResponse dataclass with .content, .model, and .usage.
|
|
173
|
+
"""
|
|
174
|
+
token = _validate_token(self._token, "ai")
|
|
175
|
+
payload: Dict[str, Any] = {"messages": messages}
|
|
176
|
+
if model:
|
|
177
|
+
payload["model"] = model
|
|
178
|
+
if max_tokens:
|
|
179
|
+
payload["max_tokens"] = max_tokens
|
|
180
|
+
if temperature is not None:
|
|
181
|
+
payload["temperature"] = temperature
|
|
182
|
+
|
|
183
|
+
body = _gateway_request(self._gateway_url, token,
|
|
184
|
+
"/v1/chat/completions", payload, "ai")
|
|
185
|
+
|
|
186
|
+
choices = body.get("choices") or [{}]
|
|
187
|
+
usage_raw = body.get("usage", {})
|
|
188
|
+
|
|
189
|
+
return ChatResponse(
|
|
190
|
+
content=choices[0].get("message", {}).get("content", ""),
|
|
191
|
+
model=body.get("model", ""),
|
|
192
|
+
usage=TokenUsage(
|
|
193
|
+
prompt_tokens=usage_raw.get("prompt_tokens", 0),
|
|
194
|
+
completion_tokens=usage_raw.get("completion_tokens", 0),
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def base_url(self) -> str:
|
|
200
|
+
"""OpenAI-SDK compatible base URL."""
|
|
201
|
+
return f"{self._gateway_url}/v1"
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def api_key(self) -> str:
|
|
205
|
+
"""Returns BROKR_TOKEN — use as api_key with the openai package."""
|
|
206
|
+
return _validate_token(self._token, "ai")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
# BrokrStorage
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class BrokrStorage:
|
|
215
|
+
def __init__(self, token: Optional[str], gateway_url: str) -> None:
|
|
216
|
+
self._token = token
|
|
217
|
+
self._gateway_url = gateway_url
|
|
218
|
+
|
|
219
|
+
def get_upload_url(
|
|
220
|
+
self, filename: str, content_type: str = "application/octet-stream",
|
|
221
|
+
) -> Dict[str, Any]:
|
|
222
|
+
"""Get a presigned upload URL + object key."""
|
|
223
|
+
token = _validate_token(self._token, "storage")
|
|
224
|
+
return _gateway_request(
|
|
225
|
+
self._gateway_url, token, "/v1/storage/sign-upload",
|
|
226
|
+
{"filename": filename, "contentType": content_type}, "storage",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def upload(
|
|
230
|
+
self, data: Union[bytes, str], filename: str,
|
|
231
|
+
content_type: str = "application/octet-stream",
|
|
232
|
+
) -> UploadResult:
|
|
233
|
+
"""Upload data to R2 storage."""
|
|
234
|
+
result = self.get_upload_url(filename, content_type)
|
|
235
|
+
body = data if isinstance(data, bytes) else data.encode()
|
|
236
|
+
put_req = urllib.request.Request(
|
|
237
|
+
result["url"], data=body,
|
|
238
|
+
headers={"Content-Type": content_type}, method="PUT",
|
|
239
|
+
)
|
|
240
|
+
try:
|
|
241
|
+
with urllib.request.urlopen(put_req, timeout=_DEFAULT_TIMEOUT):
|
|
242
|
+
pass
|
|
243
|
+
except urllib.error.HTTPError as e:
|
|
244
|
+
raise BrokrError(f"[brokr] Upload failed: HTTP {e.code}",
|
|
245
|
+
"STORAGE_UPLOAD_FAILED", "storage") from e
|
|
246
|
+
|
|
247
|
+
return UploadResult(key=result["key"], url=result["url"])
|
|
248
|
+
|
|
249
|
+
def url(self, key: str, expires_in: Optional[int] = None) -> Dict[str, Any]:
|
|
250
|
+
"""Get a presigned download URL for a stored object key."""
|
|
251
|
+
token = _validate_token(self._token, "storage")
|
|
252
|
+
payload: Dict[str, Any] = {"key": key}
|
|
253
|
+
if expires_in is not None:
|
|
254
|
+
payload["expiresIn"] = expires_in
|
|
255
|
+
return _gateway_request(
|
|
256
|
+
self._gateway_url, token, "/v1/storage/sign-download",
|
|
257
|
+
payload, "storage",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def get_url(self, key: str, expires_in: Optional[int] = None) -> Dict[str, Any]:
|
|
261
|
+
"""Deprecated — use url() instead."""
|
|
262
|
+
return self.url(key, expires_in)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
# BrokrEmail
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class BrokrEmail:
|
|
271
|
+
"""Routes email through the Brokr gateway — only BROKR_TOKEN required."""
|
|
272
|
+
|
|
273
|
+
def __init__(self, token: Optional[str], gateway_url: str) -> None:
|
|
274
|
+
self._token = token
|
|
275
|
+
self._gateway_url = gateway_url
|
|
276
|
+
|
|
277
|
+
def send(
|
|
278
|
+
self,
|
|
279
|
+
to: Union[str, List[str]],
|
|
280
|
+
subject: str,
|
|
281
|
+
*,
|
|
282
|
+
html: Optional[str] = None,
|
|
283
|
+
text: Optional[str] = None,
|
|
284
|
+
from_: Optional[str] = None,
|
|
285
|
+
) -> EmailResult:
|
|
286
|
+
"""Send an email via the Brokr gateway."""
|
|
287
|
+
token = _validate_token(self._token, "email")
|
|
288
|
+
payload: Dict[str, Any] = {"to": to, "subject": subject}
|
|
289
|
+
if from_:
|
|
290
|
+
payload["from"] = from_
|
|
291
|
+
if html:
|
|
292
|
+
payload["html"] = html
|
|
293
|
+
if text:
|
|
294
|
+
payload["text"] = text
|
|
295
|
+
|
|
296
|
+
result = _gateway_request(
|
|
297
|
+
self._gateway_url, token, "/v1/email/send", payload, "email",
|
|
298
|
+
)
|
|
299
|
+
return EmailResult(id=result.get("id", ""))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
# Brokr (top-level)
|
|
304
|
+
# ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class Brokr:
|
|
308
|
+
"""
|
|
309
|
+
Brokr runtime client. Zero config required.
|
|
310
|
+
|
|
311
|
+
All env vars are injected automatically by Brokr at provision time.
|
|
312
|
+
|
|
313
|
+
Example::
|
|
314
|
+
|
|
315
|
+
from brokr_runtime import Brokr
|
|
316
|
+
|
|
317
|
+
brokr = Brokr()
|
|
318
|
+
reply = brokr.ai.chat([{"role": "user", "content": "Summarize this..."}])
|
|
319
|
+
print(reply.content)
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
def __init__(
|
|
323
|
+
self,
|
|
324
|
+
*,
|
|
325
|
+
token: Optional[str] = None,
|
|
326
|
+
gateway_url: Optional[str] = None,
|
|
327
|
+
) -> None:
|
|
328
|
+
_token = token or os.environ.get("BROKR_TOKEN")
|
|
329
|
+
_gateway = gateway_url or _GATEWAY_URL
|
|
330
|
+
|
|
331
|
+
self.ai = BrokrAI(_token, _gateway)
|
|
332
|
+
self.storage = BrokrStorage(_token, _gateway)
|
|
333
|
+
self.email = BrokrEmail(_token, _gateway)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brokr SDK — root entry point
|
|
3
|
+
*
|
|
4
|
+
* Runtime client (for deployed apps):
|
|
5
|
+
* import { createBrokr } from '@brokr/sdk';
|
|
6
|
+
*
|
|
7
|
+
* Management client (for CLI / provisioning tools):
|
|
8
|
+
* import { createBrokrClient } from '@brokr/sdk';
|
|
9
|
+
*/
|
|
10
|
+
export { createBrokr, BrokrRuntime, BrokrAIClient, BrokrStorageClient, BrokrEmailClient, BrokrError } from './src/runtime';
|
|
11
|
+
export type { ChatMessage, ChatResponse, EmailParams, UploadResult, RuntimeOptions } from './src/runtime';
|
|
12
|
+
export { authMiddleware } from './src/auth';
|
|
13
|
+
export type { AuthUser, AuthSession } from './src/auth';
|
|
14
|
+
export { BrokrClient, create, default as createBrokrClient } from './src/management';
|
|
15
|
+
export type { BrokrConfig } from './src/management';
|
|
16
|
+
export type { StackResponse, ProviderResponse, CreateStackInput, AddProviderInput } from './types';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3H,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG1G,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrF,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
|