@llmnative/react 1.27.0 → 1.29.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.css +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2341 -2074
- package/dist/index.mjs.map +1 -1
- package/dist/server-BFQ0FDEk.js +2 -0
- package/dist/server-BFQ0FDEk.js.map +1 -0
- package/dist/server-BYI2zjyG.mjs +107 -0
- package/dist/server-BYI2zjyG.mjs.map +1 -0
- package/dist/server.js +1 -2
- package/dist/server.mjs +3 -41
- package/dist/types/src/Config.d.ts +5 -0
- package/dist/types/src/Theme.d.ts +3 -1
- package/dist/types/src/index.d.ts +3 -1
- package/dist/types/src/providers/ai/server.d.ts +2 -0
- package/dist/types/src/providers/auth/github/GitHubAuthProvider.d.ts +38 -0
- package/dist/types/src/providers/auth/github/githubOAuth.d.ts +43 -0
- package/dist/types/src/providers/manifest.d.ts +3 -2
- package/dist/types/themes/vscode-dark-modern.d.ts +3 -0
- package/dist/types/themes/vscode-light-modern.d.ts +3 -0
- package/dist/vite.js +2 -2
- package/dist/vite.js.map +1 -1
- package/dist/vite.mjs +1 -1
- package/llms-full.txt +29 -3
- package/package.json +1 -1
- package/scripts/cli/setup-project.js +1 -0
- package/themes/default.ts +2 -0
- package/themes/tokyo-night-light.ts +41 -19
- package/themes/tokyo-night.ts +41 -19
- package/themes/vscode-dark-modern.ts +60 -0
- package/themes/vscode-dark-plus.ts +41 -10
- package/themes/vscode-light-modern.ts +60 -0
- package/themes/vscode-light-plus.ts +41 -10
- package/dist/server.js.map +0 -1
- package/dist/server.mjs.map +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./modelIdentity-BEUaBIlb.js");var t=class extends Error{status;constructor(e,t){super(e),this.status=t}},n=`https://github.com`,r=`https://api.github.com`;function i(e,i=globalThis.fetch){let a=async(e,n)=>{let a=await i(`${r}${e}`,{headers:{Accept:`application/vnd.github+json`,Authorization:`Bearer ${n}`,"X-GitHub-Api-Version":`2022-11-28`}});if(!a.ok)throw new t(`GitHub ${e}: HTTP ${a.status}`,a.status);return a.json()};return{authorizeUrl({state:t,redirectUri:r,scopes:i=e.scopes,codeChallenge:a}){let o=new URLSearchParams({client_id:e.clientId,redirect_uri:r,state:t});return i?.length&&o.set(`scope`,i.join(` `)),a&&(o.set(`code_challenge`,a),o.set(`code_challenge_method`,`S256`)),`${n}/login/oauth/authorize?${o.toString()}`},async exchangeCode({code:r,redirectUri:a,codeVerifier:o}){let s=await i(`${n}/login/oauth/access_token`,{method:`POST`,headers:{Accept:`application/json`,"Content-Type":`application/json`},body:JSON.stringify({client_id:e.clientId,client_secret:e.clientSecret,code:r,redirect_uri:a,...o?{code_verifier:o}:{}})}),c=await s.json().catch(()=>({}));if(!s.ok||typeof c.access_token!=`string`)throw new t(String(c.error_description??c.error??`HTTP ${s.status}`),s.status);return{accessToken:c.access_token,scope:String(c.scope??``)}},async getUser(e){let t=await a(`/user`,e);return{login:t.login,name:t.name??null,avatarUrl:t.avatar_url??null,email:t.email??null}},async getOrganizations(e){return(await a(`/user/memberships/orgs?state=active&per_page=100`,e)).map(({role:e,organization:t})=>({login:t.login,avatarUrl:t.avatar_url??null,role:e===`admin`?`admin`:`member`}))}}}async function a(t,n){let r=[t.url,...Object.values(t.headers)].flatMap(e.l);if(r.length===0)return{ok:!0,url:t.url,headers:t.headers};let i=new URL(t.url),{url:a}=t,o={...t.headers},s=new Set;for(let{placeholder:t,scope:c,provider:l}of r){if(s.has(t))continue;s.add(t);let r=await n(c,l);if(!r?.apiKey)return{ok:!1,status:404,error:`unknown-credential`,provider:l};if(!e.u({...r,id:l},i))return{ok:!1,status:403,error:`forbidden-target`,provider:l};a=a.split(t).join(encodeURIComponent(r.apiKey)),o=Object.fromEntries(Object.entries(o).map(([e,n])=>[e,n.split(t).join(r.apiKey)]))}return{ok:!0,url:a,headers:o}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return a}});
|
|
2
|
+
//# sourceMappingURL=server-BFQ0FDEk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-BFQ0FDEk.js","names":["findServerCredentials","isAIProviderTarget","require_modelIdentity","GitHubOAuthError","message","status","GITHUB","API","createGitHubOAuth","api","response","fetchFn","path","token","config","params","redirectUri","scopes","codeChallenge","codeVerifier","body","user","organization","role","resolveServerCredentials","found","request","target","headers","done","placeholder","secret","lookup","scope","provider","url","name","value"],"sources":["../src/providers/auth/github/githubOAuth.ts","../src/providers/ai/server.ts"],"sourcesContent":["// The GitHub side of the sign-in of an app whose server holds the session (CR-104): the authorize\n// URL, the code exchange (it needs the client secret: server only), the profile and the\n// organizations of the person. Pure: no React, no browser API; `fetchFn` is replaceable in tests.\n\nexport type GitHubOAuthConfig = {\n clientId: string;\n clientSecret: string;\n /** Default scopes, e.g. `['read:org']` to list the organizations of the person. */\n scopes?: string[];\n};\n\nexport type GitHubOAuthUser = { login: string; name: string | null; avatarUrl: string | null; email: string | null };\n\nexport type GitHubOAuthOrganization = { login: string; avatarUrl: string | null; role: 'admin' | 'member' };\n\nexport class GitHubOAuthError extends Error {\n constructor(message: string, readonly status?: number) {\n super(message);\n }\n}\n\nconst GITHUB = 'https://github.com';\nconst API = 'https://api.github.com';\n\nexport function createGitHubOAuth(config: GitHubOAuthConfig, fetchFn: typeof fetch = globalThis.fetch) {\n const api = async <T>(path: string, token: string): Promise<T> => {\n const response = await fetchFn(`${API}${path}`, {\n headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' },\n });\n if (!response.ok) throw new GitHubOAuthError(`GitHub ${path}: HTTP ${response.status}`, response.status);\n return response.json() as Promise<T>;\n };\n\n return {\n /** Where the person authorizes the app. `state` ties the callback to this request; `codeChallenge` (S256) adds PKCE. */\n authorizeUrl({ state, redirectUri, scopes = config.scopes, codeChallenge }: { state: string; redirectUri: string; scopes?: string[]; codeChallenge?: string }): string {\n const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: redirectUri, state });\n if (scopes?.length) params.set('scope', scopes.join(' '));\n if (codeChallenge) {\n params.set('code_challenge', codeChallenge);\n params.set('code_challenge_method', 'S256');\n }\n return `${GITHUB}/login/oauth/authorize?${params.toString()}`;\n },\n\n /** The access token for the code GitHub sent back. */\n async exchangeCode({ code, redirectUri, codeVerifier }: { code: string; redirectUri: string; codeVerifier?: string }): Promise<{ accessToken: string; scope: string }> {\n const response = await fetchFn(`${GITHUB}/login/oauth/access_token`, {\n method: 'POST',\n headers: { Accept: 'application/json', 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n redirect_uri: redirectUri,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (!response.ok || typeof body.access_token !== 'string') {\n throw new GitHubOAuthError(String(body.error_description ?? body.error ?? `HTTP ${response.status}`), response.status);\n }\n return { accessToken: body.access_token, scope: String(body.scope ?? '') };\n },\n\n async getUser(token: string): Promise<GitHubOAuthUser> {\n const user = await api<{ login: string; name?: string | null; avatar_url?: string | null; email?: string | null }>('/user', token);\n return { login: user.login, name: user.name ?? null, avatarUrl: user.avatar_url ?? null, email: user.email ?? null };\n },\n\n /** The organizations the person belongs to, with their role (needs `read:org`). */\n async getOrganizations(token: string): Promise<GitHubOAuthOrganization[]> {\n const memberships = await api<Array<{ role: string; organization: { login: string; avatar_url?: string | null } }>>('/user/memberships/orgs?state=active&per_page=100', token);\n return memberships.map(({ role, organization }) => ({\n login: organization.login,\n avatarUrl: organization.avatar_url ?? null,\n role: role === 'admin' ? 'admin' : 'member',\n }));\n },\n };\n}\n\nexport type GitHubOAuth = ReturnType<typeof createGitHubOAuth>;\n","// `@llmnative/react/server`: what a Node proxy needs for server-held AI credentials (CR-102).\r\n// No React and no browser API: it runs in the app's own server or in the Vite dev proxy.\r\n\r\nimport { findServerCredentials, isAIProviderTarget, type ServerAIConnection } from './serverCredentials';\r\n\r\nexport { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';\r\nexport type { ServerAIConnection } from './serverCredentials';\r\n// Sign-in with GitHub held by the app's server (CR-104): authorize URL, code exchange, profile, organizations.\r\nexport { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';\r\nexport type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';\r\n// Provider-agnostic identity of models (CR-103): pure, so a server or a generator can resolve them too.\r\nexport { canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './modelIdentity';\r\nexport type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel } from './modelIdentity';\r\n\r\n/** The connection of a provider as the server keeps it, key included. */\r\nexport type ServerAISecret = ServerAIConnection & { apiKey: string };\r\n\r\n/** The key of `provider` for `scope` (e.g. a company), or nothing when it is not connected. */\r\nexport type ServerCredentialLookup = (scope: string, provider: string) => ServerAISecret | undefined | Promise<ServerAISecret | undefined>;\r\n\r\nexport type ServerCredentialRequest = { url: string; headers: Record<string, string> };\r\n\r\nexport type ServerCredentialResolution =\r\n | { ok: true; url: string; headers: Record<string, string> }\r\n /** 404: the provider is not connected for that scope; 403: the target is not a host of that provider. */\r\n | { ok: false; status: 403 | 404; error: 'unknown-credential' | 'forbidden-target'; provider: string };\r\n\r\n/**\r\n * Swaps every server-credential placeholder of a proxied request (headers and URL) for the key it\r\n * stands for. The key only goes to a host of its provider: any other target is refused, so a\r\n * placeholder can never carry a key elsewhere. A request without placeholders passes unchanged.\r\n */\r\nexport async function resolveServerCredentials(request: ServerCredentialRequest, lookup: ServerCredentialLookup): Promise<ServerCredentialResolution> {\r\n const found = [request.url, ...Object.values(request.headers)].flatMap(findServerCredentials);\r\n if (found.length === 0) return { ok: true, url: request.url, headers: request.headers };\r\n\r\n const target = new URL(request.url);\r\n let { url } = request;\r\n let headers = { ...request.headers };\r\n const done = new Set<string>();\r\n for (const { placeholder, scope, provider } of found) {\r\n if (done.has(placeholder)) continue;\r\n done.add(placeholder);\r\n const secret = await lookup(scope, provider);\r\n if (!secret?.apiKey) return { ok: false, status: 404, error: 'unknown-credential', provider };\r\n if (!isAIProviderTarget({ ...secret, id: provider }, target)) return { ok: false, status: 403, error: 'forbidden-target', provider };\r\n url = url.split(placeholder).join(encodeURIComponent(secret.apiKey));\r\n headers = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, value.split(placeholder).join(secret.apiKey)]));\r\n }\r\n return { ok: true, url, headers };\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { l as e, u as t } from "./modelIdentity-D93fYZOo.mjs";
|
|
2
|
+
//#region src/providers/auth/github/githubOAuth.ts
|
|
3
|
+
var n = class extends Error {
|
|
4
|
+
status;
|
|
5
|
+
constructor(e, t) {
|
|
6
|
+
super(e), this.status = t;
|
|
7
|
+
}
|
|
8
|
+
}, r = "https://github.com", i = "https://api.github.com";
|
|
9
|
+
function a(e, t = globalThis.fetch) {
|
|
10
|
+
let a = async (e, r) => {
|
|
11
|
+
let a = await t(`${i}${e}`, { headers: {
|
|
12
|
+
Accept: "application/vnd.github+json",
|
|
13
|
+
Authorization: `Bearer ${r}`,
|
|
14
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
15
|
+
} });
|
|
16
|
+
if (!a.ok) throw new n(`GitHub ${e}: HTTP ${a.status}`, a.status);
|
|
17
|
+
return a.json();
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
authorizeUrl({ state: t, redirectUri: n, scopes: i = e.scopes, codeChallenge: a }) {
|
|
21
|
+
let o = new URLSearchParams({
|
|
22
|
+
client_id: e.clientId,
|
|
23
|
+
redirect_uri: n,
|
|
24
|
+
state: t
|
|
25
|
+
});
|
|
26
|
+
return i?.length && o.set("scope", i.join(" ")), a && (o.set("code_challenge", a), o.set("code_challenge_method", "S256")), `${r}/login/oauth/authorize?${o.toString()}`;
|
|
27
|
+
},
|
|
28
|
+
async exchangeCode({ code: i, redirectUri: a, codeVerifier: o }) {
|
|
29
|
+
let s = await t(`${r}/login/oauth/access_token`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
Accept: "application/json",
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
client_id: e.clientId,
|
|
37
|
+
client_secret: e.clientSecret,
|
|
38
|
+
code: i,
|
|
39
|
+
redirect_uri: a,
|
|
40
|
+
...o ? { code_verifier: o } : {}
|
|
41
|
+
})
|
|
42
|
+
}), c = await s.json().catch(() => ({}));
|
|
43
|
+
if (!s.ok || typeof c.access_token != "string") throw new n(String(c.error_description ?? c.error ?? `HTTP ${s.status}`), s.status);
|
|
44
|
+
return {
|
|
45
|
+
accessToken: c.access_token,
|
|
46
|
+
scope: String(c.scope ?? "")
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
async getUser(e) {
|
|
50
|
+
let t = await a("/user", e);
|
|
51
|
+
return {
|
|
52
|
+
login: t.login,
|
|
53
|
+
name: t.name ?? null,
|
|
54
|
+
avatarUrl: t.avatar_url ?? null,
|
|
55
|
+
email: t.email ?? null
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
async getOrganizations(e) {
|
|
59
|
+
return (await a("/user/memberships/orgs?state=active&per_page=100", e)).map(({ role: e, organization: t }) => ({
|
|
60
|
+
login: t.login,
|
|
61
|
+
avatarUrl: t.avatar_url ?? null,
|
|
62
|
+
role: e === "admin" ? "admin" : "member"
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/providers/ai/server.ts
|
|
69
|
+
async function o(n, r) {
|
|
70
|
+
let i = [n.url, ...Object.values(n.headers)].flatMap(e);
|
|
71
|
+
if (i.length === 0) return {
|
|
72
|
+
ok: !0,
|
|
73
|
+
url: n.url,
|
|
74
|
+
headers: n.headers
|
|
75
|
+
};
|
|
76
|
+
let a = new URL(n.url), { url: o } = n, s = { ...n.headers }, c = /* @__PURE__ */ new Set();
|
|
77
|
+
for (let { placeholder: e, scope: n, provider: l } of i) {
|
|
78
|
+
if (c.has(e)) continue;
|
|
79
|
+
c.add(e);
|
|
80
|
+
let i = await r(n, l);
|
|
81
|
+
if (!i?.apiKey) return {
|
|
82
|
+
ok: !1,
|
|
83
|
+
status: 404,
|
|
84
|
+
error: "unknown-credential",
|
|
85
|
+
provider: l
|
|
86
|
+
};
|
|
87
|
+
if (!t({
|
|
88
|
+
...i,
|
|
89
|
+
id: l
|
|
90
|
+
}, a)) return {
|
|
91
|
+
ok: !1,
|
|
92
|
+
status: 403,
|
|
93
|
+
error: "forbidden-target",
|
|
94
|
+
provider: l
|
|
95
|
+
};
|
|
96
|
+
o = o.split(e).join(encodeURIComponent(i.apiKey)), s = Object.fromEntries(Object.entries(s).map(([t, n]) => [t, n.split(e).join(i.apiKey)]));
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
ok: !0,
|
|
100
|
+
url: o,
|
|
101
|
+
headers: s
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { n, a as r, o as t };
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=server-BYI2zjyG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-BYI2zjyG.mjs","names":["findServerCredentials","isAIProviderTarget","GitHubOAuthError","message","status","GITHUB","API","createGitHubOAuth","api","response","fetchFn","path","token","config","params","redirectUri","scopes","codeChallenge","codeVerifier","body","user","organization","role","resolveServerCredentials","found","request","target","headers","done","placeholder","secret","lookup","scope","provider","url","name","value"],"sources":["../src/providers/auth/github/githubOAuth.ts","../src/providers/ai/server.ts"],"sourcesContent":["// The GitHub side of the sign-in of an app whose server holds the session (CR-104): the authorize\n// URL, the code exchange (it needs the client secret: server only), the profile and the\n// organizations of the person. Pure: no React, no browser API; `fetchFn` is replaceable in tests.\n\nexport type GitHubOAuthConfig = {\n clientId: string;\n clientSecret: string;\n /** Default scopes, e.g. `['read:org']` to list the organizations of the person. */\n scopes?: string[];\n};\n\nexport type GitHubOAuthUser = { login: string; name: string | null; avatarUrl: string | null; email: string | null };\n\nexport type GitHubOAuthOrganization = { login: string; avatarUrl: string | null; role: 'admin' | 'member' };\n\nexport class GitHubOAuthError extends Error {\n constructor(message: string, readonly status?: number) {\n super(message);\n }\n}\n\nconst GITHUB = 'https://github.com';\nconst API = 'https://api.github.com';\n\nexport function createGitHubOAuth(config: GitHubOAuthConfig, fetchFn: typeof fetch = globalThis.fetch) {\n const api = async <T>(path: string, token: string): Promise<T> => {\n const response = await fetchFn(`${API}${path}`, {\n headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' },\n });\n if (!response.ok) throw new GitHubOAuthError(`GitHub ${path}: HTTP ${response.status}`, response.status);\n return response.json() as Promise<T>;\n };\n\n return {\n /** Where the person authorizes the app. `state` ties the callback to this request; `codeChallenge` (S256) adds PKCE. */\n authorizeUrl({ state, redirectUri, scopes = config.scopes, codeChallenge }: { state: string; redirectUri: string; scopes?: string[]; codeChallenge?: string }): string {\n const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: redirectUri, state });\n if (scopes?.length) params.set('scope', scopes.join(' '));\n if (codeChallenge) {\n params.set('code_challenge', codeChallenge);\n params.set('code_challenge_method', 'S256');\n }\n return `${GITHUB}/login/oauth/authorize?${params.toString()}`;\n },\n\n /** The access token for the code GitHub sent back. */\n async exchangeCode({ code, redirectUri, codeVerifier }: { code: string; redirectUri: string; codeVerifier?: string }): Promise<{ accessToken: string; scope: string }> {\n const response = await fetchFn(`${GITHUB}/login/oauth/access_token`, {\n method: 'POST',\n headers: { Accept: 'application/json', 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n redirect_uri: redirectUri,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (!response.ok || typeof body.access_token !== 'string') {\n throw new GitHubOAuthError(String(body.error_description ?? body.error ?? `HTTP ${response.status}`), response.status);\n }\n return { accessToken: body.access_token, scope: String(body.scope ?? '') };\n },\n\n async getUser(token: string): Promise<GitHubOAuthUser> {\n const user = await api<{ login: string; name?: string | null; avatar_url?: string | null; email?: string | null }>('/user', token);\n return { login: user.login, name: user.name ?? null, avatarUrl: user.avatar_url ?? null, email: user.email ?? null };\n },\n\n /** The organizations the person belongs to, with their role (needs `read:org`). */\n async getOrganizations(token: string): Promise<GitHubOAuthOrganization[]> {\n const memberships = await api<Array<{ role: string; organization: { login: string; avatar_url?: string | null } }>>('/user/memberships/orgs?state=active&per_page=100', token);\n return memberships.map(({ role, organization }) => ({\n login: organization.login,\n avatarUrl: organization.avatar_url ?? null,\n role: role === 'admin' ? 'admin' : 'member',\n }));\n },\n };\n}\n\nexport type GitHubOAuth = ReturnType<typeof createGitHubOAuth>;\n","// `@llmnative/react/server`: what a Node proxy needs for server-held AI credentials (CR-102).\r\n// No React and no browser API: it runs in the app's own server or in the Vite dev proxy.\r\n\r\nimport { findServerCredentials, isAIProviderTarget, type ServerAIConnection } from './serverCredentials';\r\n\r\nexport { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';\r\nexport type { ServerAIConnection } from './serverCredentials';\r\n// Sign-in with GitHub held by the app's server (CR-104): authorize URL, code exchange, profile, organizations.\r\nexport { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';\r\nexport type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';\r\n// Provider-agnostic identity of models (CR-103): pure, so a server or a generator can resolve them too.\r\nexport { canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './modelIdentity';\r\nexport type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel } from './modelIdentity';\r\n\r\n/** The connection of a provider as the server keeps it, key included. */\r\nexport type ServerAISecret = ServerAIConnection & { apiKey: string };\r\n\r\n/** The key of `provider` for `scope` (e.g. a company), or nothing when it is not connected. */\r\nexport type ServerCredentialLookup = (scope: string, provider: string) => ServerAISecret | undefined | Promise<ServerAISecret | undefined>;\r\n\r\nexport type ServerCredentialRequest = { url: string; headers: Record<string, string> };\r\n\r\nexport type ServerCredentialResolution =\r\n | { ok: true; url: string; headers: Record<string, string> }\r\n /** 404: the provider is not connected for that scope; 403: the target is not a host of that provider. */\r\n | { ok: false; status: 403 | 404; error: 'unknown-credential' | 'forbidden-target'; provider: string };\r\n\r\n/**\r\n * Swaps every server-credential placeholder of a proxied request (headers and URL) for the key it\r\n * stands for. The key only goes to a host of its provider: any other target is refused, so a\r\n * placeholder can never carry a key elsewhere. A request without placeholders passes unchanged.\r\n */\r\nexport async function resolveServerCredentials(request: ServerCredentialRequest, lookup: ServerCredentialLookup): Promise<ServerCredentialResolution> {\r\n const found = [request.url, ...Object.values(request.headers)].flatMap(findServerCredentials);\r\n if (found.length === 0) return { ok: true, url: request.url, headers: request.headers };\r\n\r\n const target = new URL(request.url);\r\n let { url } = request;\r\n let headers = { ...request.headers };\r\n const done = new Set<string>();\r\n for (const { placeholder, scope, provider } of found) {\r\n if (done.has(placeholder)) continue;\r\n done.add(placeholder);\r\n const secret = await lookup(scope, provider);\r\n if (!secret?.apiKey) return { ok: false, status: 404, error: 'unknown-credential', provider };\r\n if (!isAIProviderTarget({ ...secret, id: provider }, target)) return { ok: false, status: 403, error: 'forbidden-target', provider };\r\n url = url.split(placeholder).join(encodeURIComponent(secret.apiKey));\r\n headers = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, value.split(placeholder).join(secret.apiKey)]));\r\n }\r\n return { ok: true, url, headers };\r\n}\r\n"],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./modelIdentity-BEUaBIlb.js")
|
|
2
|
-
//# sourceMappingURL=server.js.map
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./modelIdentity-BEUaBIlb.js"),t=require("./server-BFQ0FDEk.js");exports.AI_PROVIDER_HOSTS=e.o,exports.GitHubOAuthError=t.n,exports.SERVER_CREDENTIAL_PREFIX=e.s,exports.buildModelIdentityIndex=e.t,exports.canonicalModelName=e.n,exports.createGitHubOAuth=t.r,exports.isAIProviderTarget=e.u,exports.loadModelIdentityIndex=e.r,exports.resolveModel=e.i,exports.resolveServerCredentials=t.t,exports.serverCredential=e.d;
|
package/dist/server.mjs
CHANGED
|
@@ -1,41 +1,3 @@
|
|
|
1
|
-
import { d as e, i as t,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
let r = [e.url, ...Object.values(e.headers)].flatMap(n);
|
|
5
|
-
if (r.length === 0) return {
|
|
6
|
-
ok: !0,
|
|
7
|
-
url: e.url,
|
|
8
|
-
headers: e.headers
|
|
9
|
-
};
|
|
10
|
-
let i = new URL(e.url), { url: a } = e, o = { ...e.headers }, s = /* @__PURE__ */ new Set();
|
|
11
|
-
for (let { placeholder: e, scope: n, provider: l } of r) {
|
|
12
|
-
if (s.has(e)) continue;
|
|
13
|
-
s.add(e);
|
|
14
|
-
let r = await t(n, l);
|
|
15
|
-
if (!r?.apiKey) return {
|
|
16
|
-
ok: !1,
|
|
17
|
-
status: 404,
|
|
18
|
-
error: "unknown-credential",
|
|
19
|
-
provider: l
|
|
20
|
-
};
|
|
21
|
-
if (!c({
|
|
22
|
-
...r,
|
|
23
|
-
id: l
|
|
24
|
-
}, i)) return {
|
|
25
|
-
ok: !1,
|
|
26
|
-
status: 403,
|
|
27
|
-
error: "forbidden-target",
|
|
28
|
-
provider: l
|
|
29
|
-
};
|
|
30
|
-
a = a.split(e).join(encodeURIComponent(r.apiKey)), o = Object.fromEntries(Object.entries(o).map(([t, n]) => [t, n.split(e).join(r.apiKey)]));
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
ok: !0,
|
|
34
|
-
url: a,
|
|
35
|
-
headers: o
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
//#endregion
|
|
39
|
-
export { i as AI_PROVIDER_HOSTS, o as SERVER_CREDENTIAL_PREFIX, s as buildModelIdentityIndex, r as canonicalModelName, c as isAIProviderTarget, a as loadModelIdentityIndex, t as resolveModel, l as resolveServerCredentials, e as serverCredential };
|
|
40
|
-
|
|
41
|
-
//# sourceMappingURL=server.mjs.map
|
|
1
|
+
import { d as e, i as t, n, o as r, r as i, s as a, t as o, u as s } from "./modelIdentity-D93fYZOo.mjs";
|
|
2
|
+
import { n as c, r as l, t as u } from "./server-BYI2zjyG.mjs";
|
|
3
|
+
export { r as AI_PROVIDER_HOSTS, c as GitHubOAuthError, a as SERVER_CREDENTIAL_PREFIX, o as buildModelIdentityIndex, n as canonicalModelName, l as createGitHubOAuth, s as isAIProviderTarget, i as loadModelIdentityIndex, t as resolveModel, u as resolveServerCredentials, e as serverCredential };
|
|
@@ -34,6 +34,10 @@ export type DropboxConfig = {
|
|
|
34
34
|
clientId: string;
|
|
35
35
|
rootPath: string;
|
|
36
36
|
};
|
|
37
|
+
/** Sign-in with GitHub through the app's server (CR-104): where its session endpoints live (login, me, logout). */
|
|
38
|
+
export type GitHubAuthConfig = {
|
|
39
|
+
sessionUrl: string;
|
|
40
|
+
};
|
|
37
41
|
export type AIConfig = {
|
|
38
42
|
geminiApiKey?: string;
|
|
39
43
|
openaiApiKey?: string;
|
|
@@ -82,6 +86,7 @@ export type Config = {
|
|
|
82
86
|
firebase?: FirebaseConfig;
|
|
83
87
|
google?: GoogleConfig;
|
|
84
88
|
dropbox?: DropboxConfig;
|
|
89
|
+
github?: GitHubAuthConfig;
|
|
85
90
|
supabase?: SupabaseConfig;
|
|
86
91
|
ai?: AIConfig;
|
|
87
92
|
scrape?: ScrapeConfig;
|
|
@@ -14,6 +14,8 @@ export type ThemeMode = 'light' | 'dark' | 'system';
|
|
|
14
14
|
export interface ColorScale {
|
|
15
15
|
background?: string;
|
|
16
16
|
foreground?: string;
|
|
17
|
+
/** Page floor behind cards and panels (`bg-canvas`). Falls back to `background` when not set. */
|
|
18
|
+
canvas?: string;
|
|
17
19
|
card?: string;
|
|
18
20
|
cardForeground?: string;
|
|
19
21
|
popover?: string;
|
|
@@ -404,7 +406,7 @@ export interface ThemeDefinition {
|
|
|
404
406
|
components: Theme;
|
|
405
407
|
}
|
|
406
408
|
/** IDs of built-in themes. Extend by adding entries to BUILT_IN_THEMES. */
|
|
407
|
-
export declare const BUILT_IN_THEME_IDS: readonly ["default", "flat", "cyber", "vscode-dark-plus", "vscode-light-plus", "tokyo-night", "tokyo-night-light"];
|
|
409
|
+
export declare const BUILT_IN_THEME_IDS: readonly ["default", "flat", "cyber", "vscode-dark-modern", "vscode-light-modern", "vscode-dark-plus", "vscode-light-plus", "tokyo-night", "tokyo-night-light"];
|
|
408
410
|
export type BuiltInThemeId = (typeof BUILT_IN_THEME_IDS)[number];
|
|
409
411
|
export type AppThemeProviderConfig = BuiltInThemeId | ThemeDefinition | {
|
|
410
412
|
defaultMode?: ThemeMode;
|
|
@@ -4,7 +4,7 @@ export { useMenu } from './App';
|
|
|
4
4
|
export type { AppProps, AppProviderProps, AppProvidersConfig, MenuConfig } from './App';
|
|
5
5
|
export type { AIConfig, FirebaseConfig } from './Config';
|
|
6
6
|
export type { ServicesConfig, SupabaseProviderConfig, GoogleProviderConfig, MockProviderConfig, DataDriverName, StorageDriverName, AuthDriverName, EmailDriverName, AIDriverName, } from './providers/manifest';
|
|
7
|
-
export type { ProxyConfig } from './Config';
|
|
7
|
+
export type { ProxyConfig, GitHubAuthConfig } from './Config';
|
|
8
8
|
export { Head, HeadProvider, DEFAULT_DOCUMENT_HEAD, DEFAULT_HEAD_APP_NAME, DEFAULT_LANGUAGE_HEAD, useAssetsHead, useDocumentHead, useHead, useHeadLinks, useLanguageHead, usePaginationHead, usePwaHead, useSchemaOrgHead, useSocialHead, } from './Head';
|
|
9
9
|
export type { HeadController, AssetsHeadState, DocumentBaseHead, DocumentHeadState, DocumentHttpEquivMeta, HeadFontAsset, HeadLinkDescriptor, HeadLinksState, HeadMetaAttributes, HeadProviderProps, HeadScriptAsset, HeadStyleAsset, LanguageAlternateLink, LanguageHeadState, OpenGraphHeadState, PageMetadataState, PaginationHeadState, PwaIconLink, PwaHeadState, SchemaOrgHeadState, SchemaOrgJson, SocialHeadState, TwitterHeadState, } from './Head';
|
|
10
10
|
export { useTheme, useThemeController, BUILT_IN_THEMES, BUILT_IN_THEME_IDS } from './Theme';
|
|
@@ -58,6 +58,8 @@ export { FirebaseAuthProvider } from './providers/auth/firebase/FirebaseAuthProv
|
|
|
58
58
|
export { callFirebaseFunction } from './providers/firebase-init';
|
|
59
59
|
export { SupabaseAuthProvider } from './providers/auth/supabase/SupabaseAuthProvider';
|
|
60
60
|
export { DropboxAuthProvider } from './providers/auth/dropbox/DropboxAuthProvider';
|
|
61
|
+
export { GitHubAuthProvider } from './providers/auth/github/GitHubAuthProvider';
|
|
62
|
+
export type { GitHubSessionProfile } from './providers/auth/github/GitHubAuthProvider';
|
|
61
63
|
export type { CredentialsAdapter } from './providers/credentials/CredentialsProvider';
|
|
62
64
|
export { useCredentialsProvider } from './providers/credentials/CredentialsProviderContext';
|
|
63
65
|
export { GoogleServiceAccountProvider } from './providers/credentials/google/GoogleServiceAccountProvider';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ServerAIConnection } from './serverCredentials';
|
|
2
2
|
export { AI_PROVIDER_HOSTS, isAIProviderTarget, serverCredential, SERVER_CREDENTIAL_PREFIX } from './serverCredentials';
|
|
3
3
|
export type { ServerAIConnection } from './serverCredentials';
|
|
4
|
+
export { createGitHubOAuth, GitHubOAuthError } from '../auth/github/githubOAuth';
|
|
5
|
+
export type { GitHubOAuth, GitHubOAuthConfig, GitHubOAuthOrganization, GitHubOAuthUser } from '../auth/github/githubOAuth';
|
|
4
6
|
export { canonicalModelName, buildModelIdentityIndex, resolveModel, loadModelIdentityIndex } from './modelIdentity';
|
|
5
7
|
export type { ModelIdentity, ModelIdentityEntry, ModelIdentityIndex, ResolvedModel } from './modelIdentity';
|
|
6
8
|
/** The connection of a provider as the server keeps it, key included. */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { GitHubAuthConfig } from '../../../Config';
|
|
2
|
+
import { type ProviderConfigurationState } from '../../ProviderConfiguration';
|
|
3
|
+
import type { AuthProviderAdapter, AuthSignInOptions, UserProfile } from '../AuthProvider';
|
|
4
|
+
/** The profile the session endpoint of the app answers with (`<sessionUrl>/me`). */
|
|
5
|
+
export type GitHubSessionProfile = {
|
|
6
|
+
login: string;
|
|
7
|
+
name?: string | null;
|
|
8
|
+
avatarUrl?: string | null;
|
|
9
|
+
email?: string | null;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Sign-in with GitHub through a session held by the app's server (CR-104). GitHub needs the client
|
|
13
|
+
* secret to turn the authorization code into a token, so the exchange, the token and the session
|
|
14
|
+
* stay on the server (`createGitHubOAuth` from `@llmnative/react/server` does the GitHub part); the
|
|
15
|
+
* browser only ever sees the profile. Endpoints of the app, under `sessionUrl`:
|
|
16
|
+
* GET /login?returnTo=<path> → redirects to GitHub and back, sets the session cookie
|
|
17
|
+
* GET /me → the profile, 401 without a session
|
|
18
|
+
* POST /logout → ends the session
|
|
19
|
+
*/
|
|
20
|
+
export declare class GitHubAuthProvider implements AuthProviderAdapter {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private user;
|
|
23
|
+
private readonly listeners;
|
|
24
|
+
private loading;
|
|
25
|
+
constructor(config: GitHubAuthConfig);
|
|
26
|
+
getConfigurationState(): ProviderConfigurationState;
|
|
27
|
+
isConfigured(): boolean;
|
|
28
|
+
private get base();
|
|
29
|
+
getUser(): UserProfile | null;
|
|
30
|
+
isAuthenticated(): boolean;
|
|
31
|
+
/** Asks the server who is signed in; the listeners hear of it when it changed. */
|
|
32
|
+
refresh(): Promise<UserProfile | null>;
|
|
33
|
+
/** Leaves for GitHub through the server, and comes back to the page it left from. */
|
|
34
|
+
signIn(options?: AuthSignInOptions): Promise<UserProfile | null>;
|
|
35
|
+
signOut(): Promise<void>;
|
|
36
|
+
onAuthChange(callback: (user: UserProfile | null) => void): () => void;
|
|
37
|
+
private setUser;
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type GitHubOAuthConfig = {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret: string;
|
|
4
|
+
/** Default scopes, e.g. `['read:org']` to list the organizations of the person. */
|
|
5
|
+
scopes?: string[];
|
|
6
|
+
};
|
|
7
|
+
export type GitHubOAuthUser = {
|
|
8
|
+
login: string;
|
|
9
|
+
name: string | null;
|
|
10
|
+
avatarUrl: string | null;
|
|
11
|
+
email: string | null;
|
|
12
|
+
};
|
|
13
|
+
export type GitHubOAuthOrganization = {
|
|
14
|
+
login: string;
|
|
15
|
+
avatarUrl: string | null;
|
|
16
|
+
role: 'admin' | 'member';
|
|
17
|
+
};
|
|
18
|
+
export declare class GitHubOAuthError extends Error {
|
|
19
|
+
readonly status?: number | undefined;
|
|
20
|
+
constructor(message: string, status?: number | undefined);
|
|
21
|
+
}
|
|
22
|
+
export declare function createGitHubOAuth(config: GitHubOAuthConfig, fetchFn?: typeof fetch): {
|
|
23
|
+
/** Where the person authorizes the app. `state` ties the callback to this request; `codeChallenge` (S256) adds PKCE. */
|
|
24
|
+
authorizeUrl({ state, redirectUri, scopes, codeChallenge }: {
|
|
25
|
+
state: string;
|
|
26
|
+
redirectUri: string;
|
|
27
|
+
scopes?: string[];
|
|
28
|
+
codeChallenge?: string;
|
|
29
|
+
}): string;
|
|
30
|
+
/** The access token for the code GitHub sent back. */
|
|
31
|
+
exchangeCode({ code, redirectUri, codeVerifier }: {
|
|
32
|
+
code: string;
|
|
33
|
+
redirectUri: string;
|
|
34
|
+
codeVerifier?: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
accessToken: string;
|
|
37
|
+
scope: string;
|
|
38
|
+
}>;
|
|
39
|
+
getUser(token: string): Promise<GitHubOAuthUser>;
|
|
40
|
+
/** The organizations the person belongs to, with their role (needs `read:org`). */
|
|
41
|
+
getOrganizations(token: string): Promise<GitHubOAuthOrganization[]>;
|
|
42
|
+
};
|
|
43
|
+
export type GitHubOAuth = ReturnType<typeof createGitHubOAuth>;
|
|
@@ -5,7 +5,7 @@ import type { DataProviderAdapter } from './data/DataProvider';
|
|
|
5
5
|
import type { StorageProviderAdapter } from './storage/StorageProvider';
|
|
6
6
|
import type { AuthProviderAdapter } from './auth/AuthProvider';
|
|
7
7
|
import type { EmailProviderAdapter } from './email/EmailProvider';
|
|
8
|
-
import type { AIConfig, DropboxConfig, FirebaseConfig, GoogleOAuth2, GoogleServiceAccount, SupabaseConfig } from '../Config';
|
|
8
|
+
import type { AIConfig, DropboxConfig, FirebaseConfig, GitHubAuthConfig, GoogleOAuth2, GoogleServiceAccount, SupabaseConfig } from '../Config';
|
|
9
9
|
import type { ProviderService } from './ProviderRegistryContext';
|
|
10
10
|
/** The 6 built-in categories — alias of ProviderService (ProviderRegistryContext.tsx), derived from ProviderAdapterMap so the two can't drift. */
|
|
11
11
|
export type ServiceCategory = ProviderService;
|
|
@@ -27,13 +27,14 @@ export type MockProviderConfig = {
|
|
|
27
27
|
export declare const FIREBASE_MANIFEST: DriverManifest<FirebaseConfig>;
|
|
28
28
|
export declare const GOOGLE_MANIFEST: DriverManifest<GoogleProviderConfig>;
|
|
29
29
|
export declare const DROPBOX_MANIFEST: DriverManifest<DropboxConfig>;
|
|
30
|
+
export declare const GITHUB_MANIFEST: DriverManifest<GitHubAuthConfig>;
|
|
30
31
|
export declare const SUPABASE_MANIFEST: DriverManifest<SupabaseProviderConfig>;
|
|
31
32
|
export declare const MOCK_MANIFEST: DriverManifest<MockProviderConfig>;
|
|
32
33
|
export declare const AI_MANIFEST: DriverManifest<AIConfig>;
|
|
33
34
|
export declare const PROVIDER_MANIFESTS: Record<string, DriverManifest<any>>;
|
|
34
35
|
export type DataDriverName = 'dbRealtime' | 'firestoreDb' | 'supabaseDb' | 'mock';
|
|
35
36
|
export type StorageDriverName = 'firestorage' | 'supabaseStorage';
|
|
36
|
-
export type AuthDriverName = 'googleAuth' | 'firebaseAuth' | 'dropboxAuth' | 'supabaseAuth';
|
|
37
|
+
export type AuthDriverName = 'googleAuth' | 'firebaseAuth' | 'dropboxAuth' | 'supabaseAuth' | 'githubAuth';
|
|
37
38
|
export type EmailDriverName = 'gmail';
|
|
38
39
|
export type AIDriverName = 'openai' | 'openrouter' | 'opencode' | 'openai-compatible' | 'deepseek' | 'gemini' | 'anthropic' | 'mistral' | 'cloudflare' | 'groq';
|
|
39
40
|
export type CredentialsDriverName = 'googleServiceAccount';
|
package/dist/vite.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./logHeader-CUsdmGAo.js"),t=require("./server.js");let n=require("node:fs/promises"),r=require("node:path");r=e.r(r);var i=`x-llmnative-proxy`,a=`/api/proxy`,o=async e=>{let t=[];for await(let n of e)t.push(Buffer.isBuffer(n)?n:Buffer.from(n));return t.length===0?void 0:Buffer.concat(t)};function s(e){return e.replace(/[^a-zA-Z0-9_-]/g,``).slice(0,128)||`unknown`}function c(e){if(!e.trim())return`(empty body)`;try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}async function l(e,t,i,a,o,l){try{await(0,n.mkdir)(e.dir,{recursive:!0});let u=[``,`=`.repeat(80),`Turn @ ${new Date().toISOString()}`,`Target: ${i}`,`--- REQUEST BODY ---`,c(a),`--- RESPONSE BODY (status ${o}) ---`,c(l),`=`.repeat(80),``].join(`
|
|
2
|
-
`);await(0,n.appendFile)(r.default.join(e.dir,`${s(t)}.txt`),u,`utf-8`)}catch(e){console.warn(`[llmnative-dev-proxy] Failed to write LLM log:`,e)}}var u=async(n,r,a,s,c)=>{let u=new URL(n.url||`/`,`http://localhost`).searchParams.get(`url`);if(n.headers[i]!==`1`){r.statusCode=403,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Proxy access denied.`}));return}if(!u){r.statusCode=400,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Missing "url" query parameter.`}));return}try{let d=new URL(u);if(d.pathname.startsWith(a)&&[`localhost`,`127.0.0.1`].includes(d.hostname)){r.statusCode=508,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Recursive proxy target detected.`,target:u}));return}let f=await o(n),p=n.headers[e.t],m=new Headers,h=new Set([i,e.t,`host`,`origin`,`referer`,`content-length`,`transfer-encoding`,`connection`]);Object.entries(n.headers).forEach(([e,t])=>{t&&!h.has(e.toLowerCase())&&m.set(e,Array.isArray(t)?t.join(`, `):t)});let g=await t.
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./logHeader-CUsdmGAo.js"),t=require("./server-BFQ0FDEk.js");let n=require("node:fs/promises"),r=require("node:path");r=e.r(r);var i=`x-llmnative-proxy`,a=`/api/proxy`,o=async e=>{let t=[];for await(let n of e)t.push(Buffer.isBuffer(n)?n:Buffer.from(n));return t.length===0?void 0:Buffer.concat(t)};function s(e){return e.replace(/[^a-zA-Z0-9_-]/g,``).slice(0,128)||`unknown`}function c(e){if(!e.trim())return`(empty body)`;try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}async function l(e,t,i,a,o,l){try{await(0,n.mkdir)(e.dir,{recursive:!0});let u=[``,`=`.repeat(80),`Turn @ ${new Date().toISOString()}`,`Target: ${i}`,`--- REQUEST BODY ---`,c(a),`--- RESPONSE BODY (status ${o}) ---`,c(l),`=`.repeat(80),``].join(`
|
|
2
|
+
`);await(0,n.appendFile)(r.default.join(e.dir,`${s(t)}.txt`),u,`utf-8`)}catch(e){console.warn(`[llmnative-dev-proxy] Failed to write LLM log:`,e)}}var u=async(n,r,a,s,c)=>{let u=new URL(n.url||`/`,`http://localhost`).searchParams.get(`url`);if(n.headers[i]!==`1`){r.statusCode=403,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Proxy access denied.`}));return}if(!u){r.statusCode=400,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Missing "url" query parameter.`}));return}try{let d=new URL(u);if(d.pathname.startsWith(a)&&[`localhost`,`127.0.0.1`].includes(d.hostname)){r.statusCode=508,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:`Recursive proxy target detected.`,target:u}));return}let f=await o(n),p=n.headers[e.t],m=new Headers,h=new Set([i,e.t,`host`,`origin`,`referer`,`content-length`,`transfer-encoding`,`connection`]);Object.entries(n.headers).forEach(([e,t])=>{t&&!h.has(e.toLowerCase())&&m.set(e,Array.isArray(t)?t.join(`, `):t)});let g=await t.t({url:u,headers:Object.fromEntries(m)},c??(()=>void 0));if(!g.ok){r.statusCode=g.status,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:g.error,provider:g.provider}));return}let _=await fetch(g.url,{method:n.method||`GET`,headers:new Headers(g.headers),body:f&&![`GET`,`HEAD`].includes((n.method||`GET`).toUpperCase())?Uint8Array.from(f):void 0,redirect:`follow`}),v=Buffer.from(await _.arrayBuffer());s?.enabled&&typeof p==`string`&&p&&l(s,p,u,f?.toString(`utf-8`)??``,_.status,v.toString(`utf-8`)),r.statusCode=_.status,_.headers.forEach((e,t)=>{t.toLowerCase()!==`content-encoding`&&r.setHeader(t,e)}),r.end(v)}catch(e){r.statusCode=502,r.setHeader(`Content-Type`,`application/json`),r.end(JSON.stringify({error:e instanceof Error?e.message:`Proxy request failed.`}))}},d=(e=a,t,n)=>({name:`llmnative-dev-proxy`,configureServer(r){r.middlewares.use(e,(r,i)=>{u(r,i,e,t,n)})},configurePreviewServer(r){r.middlewares.use(e,(r,i)=>{u(r,i,e,t,n)})}});exports.PROXY_PATH=a,exports.createProxyPlugin=d;
|
|
3
3
|
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","names":["mkdir","appendFile","path","LLM_LOG_ID_HEADER","resolveServerCredentials","require_logHeader","require_server","node_fs_promises","node_path","PROXY_CALLER_HEADER","PROXY_PATH","readRequestBody","chunks","chunk","req","sanitizeLogId","logId","prettyJsonOrRaw","text","appendLogBlock","logOptions","block","target","requestBody","status","responseBody","error","writeProxyResponse","res","targetUrl","route","body","headers","skipHeaders","key","value","resolved","credentials","upstream","responseBuffer","createProxyPlugin","server"],"sources":["../src/providers/proxy/vite.ts"],"sourcesContent":["import { appendFile, mkdir } from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport type { IncomingMessage, ServerResponse } from 'node:http';\r\nimport type { Plugin } from 'vite';\r\nimport { LLM_LOG_ID_HEADER } from './logHeader';\r\nimport { resolveServerCredentials, type ServerCredentialLookup } from '../ai/server';\r\n\r\nconst PROXY_CALLER_HEADER = 'x-llmnative-proxy';\r\nexport const PROXY_PATH = '/api/proxy';\r\n\r\n/** Logging file di richiesta/risposta LLM — SOLO dev, mai attivo di default. Un file per\r\n * conversazione: il client (useAgent, CMS) genera un `logId` stabile per l'intera sessione e\r\n * lo manda su ogni chiamata come header `x-llmnative-log-id`; il proxy accoda ogni turno allo\r\n * stesso file, non lo sovrascrive. Vedi AICompleteRequest.logId per il lato client. */\r\nexport interface ProxyLogOptions {\r\n enabled: boolean;\r\n /** Cartella di destinazione, relativa alla cwd del dev server (il progetto consumer, es.\r\n * il CMS) o assoluta. Creata se assente. */\r\n dir: string;\r\n}\r\n\r\nconst readRequestBody = async (req: IncomingMessage): Promise<Buffer | undefined> => {\r\n const chunks: Buffer[] = [];\r\n for await (const chunk of req) {\r\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\r\n }\r\n return chunks.length === 0 ? undefined : Buffer.concat(chunks);\r\n};\r\n\r\n/** Solo caratteri sicuri per un nome file — l'id è generato da noi (crypto.randomUUID() lato\r\n * client), ma niente path traversal per costruzione anche se un client si comportasse male. */\r\nfunction sanitizeLogId(logId: string): string {\r\n return logId.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 128) || 'unknown';\r\n}\r\n\r\nfunction prettyJsonOrRaw(text: string): string {\r\n if (!text.trim()) return '(empty body)';\r\n try {\r\n return JSON.stringify(JSON.parse(text), null, 2);\r\n } catch {\r\n return text;\r\n }\r\n}\r\n\r\nasync function appendLogBlock(logOptions: ProxyLogOptions, logId: string, target: string, requestBody: string, status: number, responseBody: string) {\r\n try {\r\n await mkdir(logOptions.dir, { recursive: true });\r\n const block = [\r\n '',\r\n '='.repeat(80),\r\n `Turn @ ${new Date().toISOString()}`,\r\n `Target: ${target}`,\r\n '--- REQUEST BODY ---',\r\n prettyJsonOrRaw(requestBody),\r\n `--- RESPONSE BODY (status ${status}) ---`,\r\n prettyJsonOrRaw(responseBody),\r\n '='.repeat(80),\r\n '',\r\n ].join('\\n');\r\n await appendFile(path.join(logOptions.dir, `${sanitizeLogId(logId)}.txt`), block, 'utf-8');\r\n } catch (error) {\r\n // Un log fallito non deve mai far fallire la vera richiesta — solo un avviso in console.\r\n console.warn('[llmnative-dev-proxy] Failed to write LLM log:', error);\r\n }\r\n}\r\n\r\nconst writeProxyResponse = async (req: IncomingMessage, res: ServerResponse, route: string, logOptions?: ProxyLogOptions, credentials?: ServerCredentialLookup) => {\r\n const requestUrl = new URL(req.url || '/', 'http://localhost');\r\n const target = requestUrl.searchParams.get('url');\r\n\r\n if (req.headers[PROXY_CALLER_HEADER] !== '1') {\r\n res.statusCode = 403;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Proxy access denied.' }));\r\n return;\r\n }\r\n\r\n if (!target) {\r\n res.statusCode = 400;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Missing \"url\" query parameter.' }));\r\n return;\r\n }\r\n\r\n try {\r\n const targetUrl = new URL(target);\r\n if (targetUrl.pathname.startsWith(route) && ['localhost', '127.0.0.1'].includes(targetUrl.hostname)) {\r\n res.statusCode = 508;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Recursive proxy target detected.', target }));\r\n return;\r\n }\r\n\r\n const body = await readRequestBody(req);\r\n const logId = req.headers[LLM_LOG_ID_HEADER];\r\n const headers = new Headers();\r\n // L'header di log è consumato QUI, mai inoltrato al provider upstream (stesso\r\n // trattamento del PROXY_CALLER_HEADER) — è un dettaglio nostro, non dell'API esterna.\r\n const skipHeaders = new Set([PROXY_CALLER_HEADER, LLM_LOG_ID_HEADER, 'host', 'origin', 'referer', 'content-length', 'transfer-encoding', 'connection']);\r\n\r\n Object.entries(req.headers).forEach(([key, value]) => {\r\n if (!value || skipHeaders.has(key.toLowerCase())) return;\r\n headers.set(key, Array.isArray(value) ? value.join(', ') : value);\r\n });\r\n\r\n // Server-held AI credentials (CR-102): the placeholder becomes the key, only towards its\r\n // provider. Without a lookup a placeholder finds no key and the request is refused.\r\n const resolved = await resolveServerCredentials({ url: target, headers: Object.fromEntries(headers) }, credentials ?? (() => undefined));\r\n if (!resolved.ok) {\r\n res.statusCode = resolved.status;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: resolved.error, provider: resolved.provider }));\r\n return;\r\n }\r\n\r\n const upstream = await fetch(resolved.url, {\r\n method: req.method || 'GET',\r\n headers: new Headers(resolved.headers),\r\n body: body && !['GET', 'HEAD'].includes((req.method || 'GET').toUpperCase())\r\n ? Uint8Array.from(body)\r\n : undefined,\r\n redirect: 'follow',\r\n });\r\n\r\n const responseBuffer = Buffer.from(await upstream.arrayBuffer());\r\n\r\n if (logOptions?.enabled && typeof logId === 'string' && logId) {\r\n void appendLogBlock(logOptions, logId, target, body?.toString('utf-8') ?? '', upstream.status, responseBuffer.toString('utf-8'));\r\n }\r\n\r\n res.statusCode = upstream.status;\r\n upstream.headers.forEach((value, key) => {\r\n if (key.toLowerCase() === 'content-encoding') return;\r\n res.setHeader(key, value);\r\n });\r\n res.end(responseBuffer);\r\n } catch (error) {\r\n res.statusCode = 502;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: error instanceof Error ? error.message : 'Proxy request failed.' }));\r\n }\r\n};\r\n\r\n/** `credentials`: the keys of the providers held by the server (CR-102), for the adapters built\r\n * with createServerCredentialAIRegistry(). */\r\nexport const createProxyPlugin = (route = PROXY_PATH, logOptions?: ProxyLogOptions, credentials?: ServerCredentialLookup): Plugin => ({\r\n name: 'llmnative-dev-proxy',\r\n configureServer(server) {\r\n server.middlewares.use(route, (req, res) => { void writeProxyResponse(req, res, route, logOptions, credentials); });\r\n },\r\n configurePreviewServer(server) {\r\n server.middlewares.use(route, (req, res) => { void writeProxyResponse(req, res, route, logOptions, credentials); });\r\n },\r\n});\r\n"],"mappings":"yMAOA,IAAMS,EAAsB,oBACfC,EAAa,aAapBC,EAAkB,KAAO,IAAsD,CACjF,IAAMC,EAAmB,CAAC,EAC1B,UAAW,IAAMC,KAASC,EACtBF,EAAO,KAAK,OAAO,SAASC,CAAK,EAAIA,EAAQ,OAAO,KAAKA,CAAK,CAAC,EAEnE,OAAOD,EAAO,SAAW,EAAI,IAAA,GAAY,OAAO,OAAOA,CAAM,CACjE,EAIA,SAASG,EAAc,EAAuB,CAC1C,OAAOC,EAAM,QAAQ,kBAAmB,EAAE,CAAC,CAAC,MAAM,EAAG,GAAG,GAAK,SACjE,CAEA,SAASC,EAAgB,EAAsB,CAC3C,GAAI,CAACC,EAAK,KAAK,EAAG,MAAO,eACzB,GAAI,CACA,OAAO,KAAK,UAAU,KAAK,MAAMA,CAAI,EAAG,KAAM,CAAC,CACnD,MAAQ,CACJ,OAAOA,CACX,CACJ,CAEA,eAAeC,EAAe,EAA6B,EAAe,EAAgB,EAAqB,EAAgB,EAAsB,CACjJ,GAAI,CACA,MAAA,EAAMnB,EAAAA,MAAAA,CAAMoB,EAAW,IAAK,CAAE,UAAW,EAAK,CAAC,EAC/C,IAAMC,EAAQ,CACV,GACA,IAAI,OAAO,EAAE,EACb,UAAU,IAAI,KAAK,CAAA,CAAE,YAAY,IACjC,WAAWC,IACX,uBACAL,EAAgBM,CAAW,EAC3B,6BAA6BC,EAAO,OACpCP,EAAgBQ,CAAY,EAC5B,IAAI,OAAO,EAAE,EACb,EACJ,CAAC,CAAC,KAAK;CAAI,EACX,MAAA,EAAMxB,EAAAA,WAAAA,CAAWC,EAAAA,QAAK,KAAKkB,EAAW,IAAK,GAAGL,EAAcC,CAAK,EAAE,KAAK,EAAGK,EAAO,OAAO,CAC7F,OAASK,EAAO,CAEZ,QAAQ,KAAK,iDAAkDA,CAAK,CACxE,CACJ,CAEA,IAAMC,EAAqB,MAAO,EAAsB,EAAqB,EAAe,EAA8B,IAAyC,CAE/J,IAAML,EAAS,IADQ,IAAIR,EAAI,KAAO,IAAK,kBAC5B,CAAA,CAAW,aAAa,IAAI,KAAK,EAEhD,GAAIA,EAAI,QAAQL,KAAyB,IAAK,CAC1C,EAAI,WAAa,IACjBmB,EAAI,UAAU,eAAgB,kBAAkB,EAChDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,sBAAuB,CAAC,CAAC,EACzD,MACJ,CAEA,GAAI,CAACN,EAAQ,CACT,EAAI,WAAa,IACjBM,EAAI,UAAU,eAAgB,kBAAkB,EAChDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,gCAAiC,CAAC,CAAC,EACnE,MACJ,CAEA,GAAI,CACA,IAAMC,EAAY,IAAI,IAAIP,CAAM,EAChC,GAAIO,EAAU,SAAS,WAAWC,CAAK,GAAK,CAAC,YAAa,WAAW,CAAC,CAAC,SAASD,EAAU,QAAQ,EAAG,CACjG,EAAI,WAAa,IACjBD,EAAI,UAAU,eAAgB,kBAAkB,EAChDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,mCAAoC,QAAO,CAAC,CAAC,EAC7E,MACJ,CAEA,IAAMG,EAAO,MAAMpB,EAAgBG,CAAG,EAChCE,EAAQF,EAAI,QAAQX,EAAAA,GACpB6B,EAAU,IAAI,QAGdC,EAAc,IAAI,IAAI,CAACxB,EAAqBN,EAAAA,EAAmB,OAAQ,SAAU,UAAW,iBAAkB,oBAAqB,YAAY,CAAC,EAEtJ,OAAO,QAAQW,EAAI,OAAO,CAAC,CAAC,SAAS,CAACoB,EAAKC,KAAW,CAC7CA,GAAS,GAAY,IAAID,EAAI,YAAY,CAAC,GAC/CF,EAAQ,IAAIE,EAAK,MAAM,QAAQC,CAAK,EAAIA,EAAM,KAAK,IAAI,EAAIA,CAAK,CACpE,CAAC,EAID,IAAMC,EAAW,MAAMhC,EAAAA,yBAAyB,CAAE,IAAKkB,EAAQ,QAAS,OAAO,YAAYU,CAAO,CAAE,EAAGK,QAAsB,IAAA,GAAU,EACvI,GAAI,CAACD,EAAS,GAAI,CACd,EAAI,WAAaA,EAAS,OAC1BR,EAAI,UAAU,eAAgB,kBAAkB,EAChDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAOQ,EAAS,MAAO,SAAUA,EAAS,QAAS,CAAC,CAAC,EAC9E,MACJ,CAEA,IAAME,EAAW,MAAM,MAAMF,EAAS,IAAK,CACvC,OAAQtB,EAAI,QAAU,MACtB,QAAS,IAAI,QAAQsB,EAAS,OAAO,EACrC,KAAML,GAAQ,CAAC,CAAC,MAAO,MAAM,CAAC,CAAC,UAAUjB,EAAI,QAAU,MAAA,CAAO,YAAY,CAAC,EACrE,WAAW,KAAKiB,CAAI,EACpB,IAAA,GACN,SAAU,QACd,CAAC,EAEKQ,EAAiB,OAAO,KAAK,MAAMD,EAAS,YAAY,CAAC,EAE3DlB,GAAY,SAAW,OAAOJ,GAAU,UAAYA,GACpDG,EAAoBC,EAAYJ,EAAOM,EAAQS,GAAM,SAAS,OAAO,GAAK,GAAIO,EAAS,OAAQC,EAAe,SAAS,OAAO,CAAC,EAGnI,EAAI,WAAaD,EAAS,OAC1BA,EAAS,QAAQ,SAAS,EAAO,IAAQ,CACjCJ,EAAI,YAAY,IAAM,oBAC1BN,EAAI,UAAUM,EAAKC,CAAK,CAC5B,CAAC,EACDP,EAAI,IAAIW,CAAc,CAC1B,OAASb,EAAO,CACZ,EAAI,WAAa,IACjBE,EAAI,UAAU,eAAgB,kBAAkB,EAChDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAOF,aAAiB,MAAQA,EAAM,QAAU,uBAAwB,CAAC,CAAC,CACvG,CACJ,EAIac,GAAqB,EAAQ9B,EAAY,EAA8B,KAAkD,CAClI,KAAM,sBACN,gBAAgB,EAAQ,CACpB+B,EAAO,YAAY,IAAIX,GAAQ,EAAK,IAAQ,CAAEH,EAAwBb,EAAKc,EAAKE,EAAOV,EAAYiB,CAAW,CAAG,CAAC,CACtH,EACA,uBAAuB,EAAQ,CAC3BI,EAAO,YAAY,IAAIX,GAAQ,EAAK,IAAQ,CAAEH,EAAwBb,EAAKc,EAAKE,EAAOV,EAAYiB,CAAW,CAAG,CAAC,CACtH,CACJ"}
|
|
1
|
+
{"version":3,"file":"vite.js","names":["mkdir","appendFile","path","LLM_LOG_ID_HEADER","resolveServerCredentials","require_logHeader","require_server","node_fs_promises","node_path","PROXY_CALLER_HEADER","PROXY_PATH","readRequestBody","chunks","chunk","req","sanitizeLogId","logId","prettyJsonOrRaw","text","appendLogBlock","logOptions","block","target","requestBody","status","responseBody","error","writeProxyResponse","res","targetUrl","route","body","headers","skipHeaders","key","value","resolved","credentials","upstream","responseBuffer","createProxyPlugin","server"],"sources":["../src/providers/proxy/vite.ts"],"sourcesContent":["import { appendFile, mkdir } from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport type { IncomingMessage, ServerResponse } from 'node:http';\r\nimport type { Plugin } from 'vite';\r\nimport { LLM_LOG_ID_HEADER } from './logHeader';\r\nimport { resolveServerCredentials, type ServerCredentialLookup } from '../ai/server';\r\n\r\nconst PROXY_CALLER_HEADER = 'x-llmnative-proxy';\r\nexport const PROXY_PATH = '/api/proxy';\r\n\r\n/** Logging file di richiesta/risposta LLM — SOLO dev, mai attivo di default. Un file per\r\n * conversazione: il client (useAgent, CMS) genera un `logId` stabile per l'intera sessione e\r\n * lo manda su ogni chiamata come header `x-llmnative-log-id`; il proxy accoda ogni turno allo\r\n * stesso file, non lo sovrascrive. Vedi AICompleteRequest.logId per il lato client. */\r\nexport interface ProxyLogOptions {\r\n enabled: boolean;\r\n /** Cartella di destinazione, relativa alla cwd del dev server (il progetto consumer, es.\r\n * il CMS) o assoluta. Creata se assente. */\r\n dir: string;\r\n}\r\n\r\nconst readRequestBody = async (req: IncomingMessage): Promise<Buffer | undefined> => {\r\n const chunks: Buffer[] = [];\r\n for await (const chunk of req) {\r\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\r\n }\r\n return chunks.length === 0 ? undefined : Buffer.concat(chunks);\r\n};\r\n\r\n/** Solo caratteri sicuri per un nome file — l'id è generato da noi (crypto.randomUUID() lato\r\n * client), ma niente path traversal per costruzione anche se un client si comportasse male. */\r\nfunction sanitizeLogId(logId: string): string {\r\n return logId.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 128) || 'unknown';\r\n}\r\n\r\nfunction prettyJsonOrRaw(text: string): string {\r\n if (!text.trim()) return '(empty body)';\r\n try {\r\n return JSON.stringify(JSON.parse(text), null, 2);\r\n } catch {\r\n return text;\r\n }\r\n}\r\n\r\nasync function appendLogBlock(logOptions: ProxyLogOptions, logId: string, target: string, requestBody: string, status: number, responseBody: string) {\r\n try {\r\n await mkdir(logOptions.dir, { recursive: true });\r\n const block = [\r\n '',\r\n '='.repeat(80),\r\n `Turn @ ${new Date().toISOString()}`,\r\n `Target: ${target}`,\r\n '--- REQUEST BODY ---',\r\n prettyJsonOrRaw(requestBody),\r\n `--- RESPONSE BODY (status ${status}) ---`,\r\n prettyJsonOrRaw(responseBody),\r\n '='.repeat(80),\r\n '',\r\n ].join('\\n');\r\n await appendFile(path.join(logOptions.dir, `${sanitizeLogId(logId)}.txt`), block, 'utf-8');\r\n } catch (error) {\r\n // Un log fallito non deve mai far fallire la vera richiesta — solo un avviso in console.\r\n console.warn('[llmnative-dev-proxy] Failed to write LLM log:', error);\r\n }\r\n}\r\n\r\nconst writeProxyResponse = async (req: IncomingMessage, res: ServerResponse, route: string, logOptions?: ProxyLogOptions, credentials?: ServerCredentialLookup) => {\r\n const requestUrl = new URL(req.url || '/', 'http://localhost');\r\n const target = requestUrl.searchParams.get('url');\r\n\r\n if (req.headers[PROXY_CALLER_HEADER] !== '1') {\r\n res.statusCode = 403;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Proxy access denied.' }));\r\n return;\r\n }\r\n\r\n if (!target) {\r\n res.statusCode = 400;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Missing \"url\" query parameter.' }));\r\n return;\r\n }\r\n\r\n try {\r\n const targetUrl = new URL(target);\r\n if (targetUrl.pathname.startsWith(route) && ['localhost', '127.0.0.1'].includes(targetUrl.hostname)) {\r\n res.statusCode = 508;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Recursive proxy target detected.', target }));\r\n return;\r\n }\r\n\r\n const body = await readRequestBody(req);\r\n const logId = req.headers[LLM_LOG_ID_HEADER];\r\n const headers = new Headers();\r\n // L'header di log è consumato QUI, mai inoltrato al provider upstream (stesso\r\n // trattamento del PROXY_CALLER_HEADER) — è un dettaglio nostro, non dell'API esterna.\r\n const skipHeaders = new Set([PROXY_CALLER_HEADER, LLM_LOG_ID_HEADER, 'host', 'origin', 'referer', 'content-length', 'transfer-encoding', 'connection']);\r\n\r\n Object.entries(req.headers).forEach(([key, value]) => {\r\n if (!value || skipHeaders.has(key.toLowerCase())) return;\r\n headers.set(key, Array.isArray(value) ? value.join(', ') : value);\r\n });\r\n\r\n // Server-held AI credentials (CR-102): the placeholder becomes the key, only towards its\r\n // provider. Without a lookup a placeholder finds no key and the request is refused.\r\n const resolved = await resolveServerCredentials({ url: target, headers: Object.fromEntries(headers) }, credentials ?? (() => undefined));\r\n if (!resolved.ok) {\r\n res.statusCode = resolved.status;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: resolved.error, provider: resolved.provider }));\r\n return;\r\n }\r\n\r\n const upstream = await fetch(resolved.url, {\r\n method: req.method || 'GET',\r\n headers: new Headers(resolved.headers),\r\n body: body && !['GET', 'HEAD'].includes((req.method || 'GET').toUpperCase())\r\n ? Uint8Array.from(body)\r\n : undefined,\r\n redirect: 'follow',\r\n });\r\n\r\n const responseBuffer = Buffer.from(await upstream.arrayBuffer());\r\n\r\n if (logOptions?.enabled && typeof logId === 'string' && logId) {\r\n void appendLogBlock(logOptions, logId, target, body?.toString('utf-8') ?? '', upstream.status, responseBuffer.toString('utf-8'));\r\n }\r\n\r\n res.statusCode = upstream.status;\r\n upstream.headers.forEach((value, key) => {\r\n if (key.toLowerCase() === 'content-encoding') return;\r\n res.setHeader(key, value);\r\n });\r\n res.end(responseBuffer);\r\n } catch (error) {\r\n res.statusCode = 502;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: error instanceof Error ? error.message : 'Proxy request failed.' }));\r\n }\r\n};\r\n\r\n/** `credentials`: the keys of the providers held by the server (CR-102), for the adapters built\r\n * with createServerCredentialAIRegistry(). */\r\nexport const createProxyPlugin = (route = PROXY_PATH, logOptions?: ProxyLogOptions, credentials?: ServerCredentialLookup): Plugin => ({\r\n name: 'llmnative-dev-proxy',\r\n configureServer(server) {\r\n server.middlewares.use(route, (req, res) => { void writeProxyResponse(req, res, route, logOptions, credentials); });\r\n },\r\n configurePreviewServer(server) {\r\n server.middlewares.use(route, (req, res) => { void writeProxyResponse(req, res, route, logOptions, credentials); });\r\n },\r\n});\r\n"],"mappings":"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"}
|
package/dist/vite.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as e } from "./logHeader-Busbc8OO.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { t } from "./server-BYI2zjyG.mjs";
|
|
3
3
|
import { appendFile as n, mkdir as r } from "node:fs/promises";
|
|
4
4
|
import i from "node:path";
|
|
5
5
|
//#region src/providers/proxy/vite.ts
|
package/llms-full.txt
CHANGED
|
@@ -77,7 +77,10 @@ import '@llmnative/react/dist/index.css'
|
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
Built-in themes: `default`, `flat`, `cyber`.
|
|
80
|
+
Built-in themes: `default`, `flat`, `cyber`, `vscode-dark-modern`, `vscode-light-modern`, `vscode-dark-plus`, `vscode-light-plus`, `tokyo-night`, `tokyo-night-light`.
|
|
81
|
+
The VS Code and Tokyo Night themes are compiled from the original VS Code theme JSON (`scripts/themes/vscode`), filling the keys a theme does not list with VS Code's color registry defaults; each one has both modes (`vscode-dark-*` starts dark, `vscode-light-*` light). Status colors (destructive/success/warning/info) stay the framework's.
|
|
82
|
+
|
|
83
|
+
`ColorScale` tokens: `background`, `foreground`, `canvas` (page floor behind cards and panels, `bg-canvas`; falls back to `background`), `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `destructive`, `success`, `warning`, `info` (each with its `*Foreground`), `border`, `input` (border of form fields), `ring`. A consumer bridging the tokens in its own `@theme inline` adds `--color-canvas: hsl(var(--rf-canvas, var(--rf-background)));`.
|
|
81
84
|
|
|
82
85
|
---
|
|
83
86
|
|
|
@@ -89,7 +92,7 @@ Built-in themes: `default`, `flat`, `cyber`.
|
|
|
89
92
|
|---|---|---|---|---|
|
|
90
93
|
| data | `DataProviderAdapter` | `mock`, `dbRealtime`, `firestoreDb`, `supabaseDb` | `providers.mock/firebase/supabase` | sì (fallback: mock) |
|
|
91
94
|
| storage | `StorageProviderAdapter` | `firestorage`, `supabaseStorage` | `providers.firebase/supabase` | no |
|
|
92
|
-
| auth | `AuthProviderAdapter` | `googleAuth`, `firebaseAuth`, `supabaseAuth`, `dropboxAuth` | `providers.google/firebase/supabase/dropbox` | sì (fallback: googleAuth) |
|
|
95
|
+
| auth | `AuthProviderAdapter` | `googleAuth`, `firebaseAuth`, `supabaseAuth`, `dropboxAuth`, `githubAuth` | `providers.google/firebase/supabase/dropbox/github` | sì (fallback: googleAuth) |
|
|
93
96
|
| email | `EmailProviderAdapter` | `gmail` | `providers.google` | no |
|
|
94
97
|
| ai | `AIProviderAdapter` | `openai`, `gemini`, `anthropic`, `deepseek`, `mistral`, `openrouter`, `opencode`, `cloudflare`, `groq` | `Config.tsx AIConfig` | no |
|
|
95
98
|
| credentials | `CredentialsAdapter` | `googleServiceAccount` | `providers.google.serviceAccount` | no |
|
|
@@ -108,6 +111,29 @@ const creds = useCredentialsProvider() // CredentialsAdapter | null
|
|
|
108
111
|
const supabase = useDataProvider('supabase')
|
|
109
112
|
```
|
|
110
113
|
|
|
114
|
+
### GitHub (`githubAuth`, sessione sul server dell'app)
|
|
115
|
+
|
|
116
|
+
GitHub richiede il client secret per scambiare il codice: scambio, token e sessione stanno sul server
|
|
117
|
+
dell'app, il browser vede solo il profilo. Config `providers.github = { sessionUrl: '/api/auth/github' }`,
|
|
118
|
+
driver `auth: 'githubAuth'`; funziona con `AuthButton` (pulsante e `aspect="avatar"`) e
|
|
119
|
+
`useAuthProvider('githubAuth')`. Il server espone sotto `sessionUrl`:
|
|
120
|
+
|
|
121
|
+
| Rotta | Risposta |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `GET /login?returnTo=<path>[&scope=…]` | redirect a GitHub, poi di nuovo a `<path>` |
|
|
124
|
+
| `GET /me` | `{ login, name, avatarUrl, email }`, 401 senza sessione |
|
|
125
|
+
| `POST /logout` | chiude la sessione |
|
|
126
|
+
|
|
127
|
+
Lato server, da `@llmnative/react/server`:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const oauth = createGitHubOAuth({ clientId, clientSecret, scopes: ['read:org'] })
|
|
131
|
+
oauth.authorizeUrl({ state, redirectUri, codeChallenge }) // PKCE S256
|
|
132
|
+
const { accessToken } = await oauth.exchangeCode({ code, redirectUri, codeVerifier }) // GitHubOAuthError se rifiutato
|
|
133
|
+
await oauth.getUser(accessToken) // { login, name, avatarUrl, email }
|
|
134
|
+
await oauth.getOrganizations(accessToken) // [{ login, avatarUrl, role: 'admin' | 'member' }]
|
|
135
|
+
```
|
|
136
|
+
|
|
111
137
|
### Interfacce principali
|
|
112
138
|
|
|
113
139
|
```ts
|
|
@@ -1011,7 +1037,7 @@ Configurazione API key in `Config.tsx`: `geminiApiKey`, `openaiApiKey`, `anthrop
|
|
|
1011
1037
|
|
|
1012
1038
|
```tsx
|
|
1013
1039
|
const theme = useThemeController()
|
|
1014
|
-
theme.applyTheme('flat') // 'default' | 'flat' | 'cyber' | custom
|
|
1040
|
+
theme.applyTheme('flat') // BuiltInThemeId ('default' | 'flat' | 'cyber' | 'vscode-dark-modern' | …) | custom
|
|
1015
1041
|
theme.setMode('dark') // 'light' | 'dark' | 'system'
|
|
1016
1042
|
|
|
1017
1043
|
const icons = useIconController()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmnative/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "LLM-native React framework for deterministic UI generation. Data-driven by default, schema-driven optional. Ultra-low token consumption — built for AI agents, not just humans.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -409,6 +409,7 @@ function createSourceFiles(params) {
|
|
|
409
409
|
@theme inline {
|
|
410
410
|
--color-background: hsl(var(--rf-background));
|
|
411
411
|
--color-foreground: hsl(var(--rf-foreground));
|
|
412
|
+
--color-canvas: hsl(var(--rf-canvas, var(--rf-background)));
|
|
412
413
|
--color-card: hsl(var(--rf-card));
|
|
413
414
|
--color-card-foreground: hsl(var(--rf-card-foreground));
|
|
414
415
|
--color-popover: hsl(var(--rf-popover));
|
package/themes/default.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const preset: ThemePresetConfig = {
|
|
|
7
7
|
colors: {
|
|
8
8
|
background: '0 0% 100%',
|
|
9
9
|
foreground: '222.2 84% 4.9%',
|
|
10
|
+
canvas: '210 40% 98%',
|
|
10
11
|
card: '0 0% 100%',
|
|
11
12
|
cardForeground: '222.2 84% 4.9%',
|
|
12
13
|
popover: '0 0% 100%',
|
|
@@ -34,6 +35,7 @@ export const preset: ThemePresetConfig = {
|
|
|
34
35
|
dark: {
|
|
35
36
|
background: '222.2 84% 4.9%',
|
|
36
37
|
foreground: '210 40% 98%',
|
|
38
|
+
canvas: '222.2 84% 4.9%',
|
|
37
39
|
card: '222.2 84% 4.9%',
|
|
38
40
|
cardForeground: '210 40% 98%',
|
|
39
41
|
popover: '222.2 84% 4.9%',
|