@byfriends/oauth 0.1.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/LICENSE +28 -0
- package/README.md +11 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.mjs +174 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BYF PROPRIETARY LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-2027 ByronFinn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted to copy and redistribute this software in its
|
|
6
|
+
unmodified form, without charge. This permission does not extend to modified
|
|
7
|
+
versions of the software.
|
|
8
|
+
|
|
9
|
+
Local modification of this software for personal use is permitted, provided
|
|
10
|
+
that such modifications are not distributed to third parties.
|
|
11
|
+
|
|
12
|
+
Restrictions:
|
|
13
|
+
1. You may NOT redistribute modified versions of this software.
|
|
14
|
+
2. You may NOT use this software for commercial purposes.
|
|
15
|
+
3. You may NOT sublicense, sell, or offer this software as a service.
|
|
16
|
+
|
|
17
|
+
This software is source-available but NOT open source. The source code is
|
|
18
|
+
made publicly visible for review purposes only.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
For questions about licensing, contact: https://github.com/ByronFinn/byf/issues
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @byfriends/oauth
|
|
2
|
+
|
|
3
|
+
Authentication utilities for BYF.
|
|
4
|
+
|
|
5
|
+
Part of the [BYF](https://github.com/ByronFinn/byf) monorepo.
|
|
6
|
+
|
|
7
|
+
See the main repository for documentation, issues, and contribution guidelines.
|
|
8
|
+
|
|
9
|
+
## License
|
|
10
|
+
|
|
11
|
+
Proprietary — see the root LICENSE file.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/provider-config.d.ts
|
|
2
|
+
interface ModelInfo {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly contextLength: number;
|
|
5
|
+
readonly supportsReasoning: boolean;
|
|
6
|
+
readonly supportsReasoningEffort?: boolean | undefined;
|
|
7
|
+
readonly reasoningEffortKey?: string | undefined;
|
|
8
|
+
readonly supportsImageIn: boolean;
|
|
9
|
+
readonly supportsVideoIn: boolean;
|
|
10
|
+
readonly supportsToolUse?: boolean | undefined;
|
|
11
|
+
readonly displayName?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
interface ModelAlias {
|
|
14
|
+
provider: string;
|
|
15
|
+
model: string;
|
|
16
|
+
maxContextSize: number;
|
|
17
|
+
capabilities?: string[] | undefined;
|
|
18
|
+
displayName?: string | undefined;
|
|
19
|
+
readonly [key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
interface ProviderConfig {
|
|
22
|
+
type: string;
|
|
23
|
+
baseUrl?: string | undefined;
|
|
24
|
+
apiKey?: string | undefined;
|
|
25
|
+
readonly [key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
interface ServicesConfig {
|
|
28
|
+
readonly [key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
interface ConfigShape {
|
|
31
|
+
providers: Record<string, ProviderConfig | Record<string, unknown>>;
|
|
32
|
+
models?: Record<string, ModelAlias | Record<string, unknown>> | undefined;
|
|
33
|
+
defaultModel?: string | undefined;
|
|
34
|
+
defaultThinking?: boolean | undefined;
|
|
35
|
+
services?: ServicesConfig | undefined;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
declare class ProviderApiError extends Error {
|
|
39
|
+
readonly status: number;
|
|
40
|
+
constructor(message: string, status: number);
|
|
41
|
+
}
|
|
42
|
+
declare function fetchModels(baseUrl: string, apiKey: string, fetchImpl?: typeof fetch, signal?: AbortSignal): Promise<ModelInfo[]>;
|
|
43
|
+
declare function filterModelsByPrefix(models: ModelInfo[], prefixes?: readonly string[] | undefined): ModelInfo[];
|
|
44
|
+
declare function capabilitiesForModel(model: ModelInfo): string[] | undefined;
|
|
45
|
+
interface ApplyProviderResult {
|
|
46
|
+
readonly defaultModel: string;
|
|
47
|
+
readonly defaultThinking: boolean;
|
|
48
|
+
}
|
|
49
|
+
declare function applyProviderConfig(config: ConfigShape, options: {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly baseUrl: string;
|
|
52
|
+
readonly apiKey: string;
|
|
53
|
+
readonly models: readonly ModelInfo[];
|
|
54
|
+
readonly selectedModel: ModelInfo;
|
|
55
|
+
readonly thinking: boolean;
|
|
56
|
+
}): ApplyProviderResult;
|
|
57
|
+
declare function removeProviderConfig(config: ConfigShape, providerName: string): void;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { type ApplyProviderResult, type ConfigShape, type ModelAlias, type ModelInfo, ProviderApiError, type ProviderConfig, type ServicesConfig, applyProviderConfig, capabilitiesForModel, fetchModels, filterModelsByPrefix, removeProviderConfig };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
//#region src/utils.ts
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/api-error.ts
|
|
7
|
+
const DIRECT_ERROR_KEYS = [
|
|
8
|
+
"error_description",
|
|
9
|
+
"message",
|
|
10
|
+
"detail"
|
|
11
|
+
];
|
|
12
|
+
const NESTED_ERROR_KEYS = [
|
|
13
|
+
"message",
|
|
14
|
+
"error_description",
|
|
15
|
+
"detail",
|
|
16
|
+
"code",
|
|
17
|
+
"type"
|
|
18
|
+
];
|
|
19
|
+
function extractApiErrorMessage(value) {
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
for (const item of value) {
|
|
22
|
+
const message = extractApiErrorMessage(item);
|
|
23
|
+
if (message !== void 0) return message;
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!isRecord(value)) return void 0;
|
|
28
|
+
for (const key of DIRECT_ERROR_KEYS) {
|
|
29
|
+
const message = stringField(value, key);
|
|
30
|
+
if (message !== void 0) return message;
|
|
31
|
+
}
|
|
32
|
+
const error = value["error"];
|
|
33
|
+
const errorString = nonEmptyString(error);
|
|
34
|
+
if (errorString !== void 0) return errorString;
|
|
35
|
+
if (isRecord(error)) for (const key of NESTED_ERROR_KEYS) {
|
|
36
|
+
const message = stringField(error, key);
|
|
37
|
+
if (message !== void 0) return message;
|
|
38
|
+
}
|
|
39
|
+
const errors = value["errors"];
|
|
40
|
+
if (Array.isArray(errors)) for (const item of errors) {
|
|
41
|
+
const message = extractApiErrorMessage(item);
|
|
42
|
+
if (message !== void 0) return message;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function readApiErrorMessage(response, fallback) {
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = await response.json();
|
|
49
|
+
} catch {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
return extractApiErrorMessage(parsed) ?? fallback;
|
|
53
|
+
}
|
|
54
|
+
function stringField(record, key) {
|
|
55
|
+
return nonEmptyString(record[key]);
|
|
56
|
+
}
|
|
57
|
+
function nonEmptyString(value) {
|
|
58
|
+
if (typeof value !== "string") return void 0;
|
|
59
|
+
const trimmed = value.trim();
|
|
60
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/provider-config.ts
|
|
64
|
+
var ProviderApiError = class extends Error {
|
|
65
|
+
status;
|
|
66
|
+
constructor(message, status) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.status = status;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function toModelInfo(item) {
|
|
72
|
+
if (!isRecord(item) || typeof item["id"] !== "string" || item["id"].length === 0) return;
|
|
73
|
+
const rawContextLength = Number(item["context_length"]);
|
|
74
|
+
const contextLength = Number.isInteger(rawContextLength) && rawContextLength > 0 ? rawContextLength : 2e5;
|
|
75
|
+
const displayName = item["display_name"];
|
|
76
|
+
const normalizedDisplayName = typeof displayName === "string" && displayName.length > 0 ? displayName : void 0;
|
|
77
|
+
const supportsToolUse = Object.hasOwn(item, "supports_tool_use") ? Boolean(item["supports_tool_use"]) : true;
|
|
78
|
+
const reasoningEffortKey = firstNonEmptyString(item, [
|
|
79
|
+
"reasoning_effort_key",
|
|
80
|
+
"thinking_effort_key",
|
|
81
|
+
"reasoning_effort_param",
|
|
82
|
+
"thinking_effort_param"
|
|
83
|
+
]);
|
|
84
|
+
const supportsReasoningEffort = Object.hasOwn(item, "supports_reasoning_effort") ? Boolean(item["supports_reasoning_effort"]) : reasoningEffortKey !== void 0;
|
|
85
|
+
return {
|
|
86
|
+
id: item["id"],
|
|
87
|
+
contextLength,
|
|
88
|
+
supportsReasoning: Object.hasOwn(item, "supports_reasoning") ? Boolean(item["supports_reasoning"]) : true,
|
|
89
|
+
supportsReasoningEffort,
|
|
90
|
+
reasoningEffortKey,
|
|
91
|
+
supportsImageIn: Boolean(item["supports_image_in"]),
|
|
92
|
+
supportsVideoIn: Boolean(item["supports_video_in"]),
|
|
93
|
+
supportsToolUse,
|
|
94
|
+
displayName: normalizedDisplayName
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function firstNonEmptyString(source, keys) {
|
|
98
|
+
for (const key of keys) {
|
|
99
|
+
const value = source[key];
|
|
100
|
+
if (typeof value !== "string") continue;
|
|
101
|
+
const normalized = value.trim();
|
|
102
|
+
if (normalized.length > 0) return normalized;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function fetchModels(baseUrl, apiKey, fetchImpl = fetch, signal) {
|
|
106
|
+
const res = await fetchImpl(`${baseUrl.replace(/\/+$/, "")}/models`, {
|
|
107
|
+
headers: {
|
|
108
|
+
Authorization: `Bearer ${apiKey}`,
|
|
109
|
+
Accept: "application/json"
|
|
110
|
+
},
|
|
111
|
+
signal
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok) throw new ProviderApiError(await readApiErrorMessage(res, `Failed to list models (HTTP ${res.status}).`), res.status);
|
|
114
|
+
const payload = await res.json();
|
|
115
|
+
if (!isRecord(payload) || !Array.isArray(payload["data"])) throw new Error(`Unexpected models response for ${baseUrl}.`);
|
|
116
|
+
return payload["data"].map((item) => toModelInfo(item)).filter((item) => item !== void 0);
|
|
117
|
+
}
|
|
118
|
+
function filterModelsByPrefix(models, prefixes) {
|
|
119
|
+
if (!prefixes || prefixes.length === 0) return models;
|
|
120
|
+
return models.filter((m) => prefixes.some((p) => m.id.startsWith(p)));
|
|
121
|
+
}
|
|
122
|
+
function capabilitiesForModel(model) {
|
|
123
|
+
const caps = /* @__PURE__ */ new Set();
|
|
124
|
+
if (model.supportsReasoning) caps.add("thinking");
|
|
125
|
+
if (model.supportsReasoningEffort === true) caps.add("thinking_effort");
|
|
126
|
+
if (model.supportsImageIn) caps.add("image_in");
|
|
127
|
+
if (model.supportsVideoIn) caps.add("video_in");
|
|
128
|
+
if (model.supportsToolUse ?? true) caps.add("tool_use");
|
|
129
|
+
return caps.size > 0 ? [...caps] : void 0;
|
|
130
|
+
}
|
|
131
|
+
function applyProviderConfig(config, options) {
|
|
132
|
+
const providerKey = options.name;
|
|
133
|
+
const modelKey = `${providerKey}/${options.selectedModel.id}`;
|
|
134
|
+
config.providers[providerKey] = {
|
|
135
|
+
type: "openai-completions",
|
|
136
|
+
baseUrl: options.baseUrl,
|
|
137
|
+
apiKey: options.apiKey,
|
|
138
|
+
thinkingEffortKey: options.selectedModel.reasoningEffortKey
|
|
139
|
+
};
|
|
140
|
+
const existingModels = config.models ?? {};
|
|
141
|
+
for (const [key, model] of Object.entries(existingModels)) if (isRecord(model) && model["provider"] === providerKey) delete existingModels[key];
|
|
142
|
+
for (const model of options.models) {
|
|
143
|
+
const aliasKey = `${providerKey}/${model.id}`;
|
|
144
|
+
existingModels[aliasKey] = {
|
|
145
|
+
provider: providerKey,
|
|
146
|
+
model: model.id,
|
|
147
|
+
maxContextSize: model.contextLength,
|
|
148
|
+
capabilities: capabilitiesForModel(model),
|
|
149
|
+
displayName: model.displayName
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
config.models = existingModels;
|
|
153
|
+
config.defaultModel = modelKey;
|
|
154
|
+
config.defaultThinking = options.thinking;
|
|
155
|
+
return {
|
|
156
|
+
defaultModel: modelKey,
|
|
157
|
+
defaultThinking: options.thinking
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function removeProviderConfig(config, providerName) {
|
|
161
|
+
delete config.providers[providerName];
|
|
162
|
+
let removedDefault = false;
|
|
163
|
+
const existingModels = config.models ?? {};
|
|
164
|
+
for (const [key, model] of Object.entries(existingModels)) {
|
|
165
|
+
if (!isRecord(model) || model["provider"] !== providerName) continue;
|
|
166
|
+
delete existingModels[key];
|
|
167
|
+
if (config.defaultModel === key) removedDefault = true;
|
|
168
|
+
}
|
|
169
|
+
config.models = existingModels;
|
|
170
|
+
if (removedDefault) config.defaultModel = void 0;
|
|
171
|
+
if (config["defaultProvider"] === providerName) config["defaultProvider"] = void 0;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
export { ProviderApiError, applyProviderConfig, capabilitiesForModel, fetchModels, filterModelsByPrefix, removeProviderConfig };
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@byfriends/oauth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Custom OAuth provider configuration for BYF",
|
|
5
|
+
"license": "Proprietary",
|
|
6
|
+
"author": "ByronFinn",
|
|
7
|
+
"homepage": "https://github.com/ByronFinn/byf",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/ByronFinn/byf.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/ByronFinn/byf/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"byf",
|
|
17
|
+
"oauth",
|
|
18
|
+
"provider",
|
|
19
|
+
"custom-provider",
|
|
20
|
+
"model-config"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"imports": {
|
|
27
|
+
"#/*": [
|
|
28
|
+
"./src/*.ts",
|
|
29
|
+
"./src/*/index.ts"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
|
+
"import": "./dist/index.mjs",
|
|
36
|
+
"default": "./dist/index.mjs"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
45
|
+
"clean": "rm -rf dist"
|
|
46
|
+
}
|
|
47
|
+
}
|