@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.
@@ -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");async function t(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}}exports.AI_PROVIDER_HOSTS=e.o,exports.SERVER_CREDENTIAL_PREFIX=e.s,exports.buildModelIdentityIndex=e.t,exports.canonicalModelName=e.n,exports.isAIProviderTarget=e.u,exports.loadModelIdentityIndex=e.r,exports.resolveModel=e.i,exports.resolveServerCredentials=t,exports.serverCredential=e.d;
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, l as n, n as r, o as i, r as a, s as o, t as s, u as c } from "./modelIdentity-D93fYZOo.mjs";
2
- //#region src/providers/ai/server.ts
3
- async function l(e, t) {
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';
@@ -0,0 +1,3 @@
1
+ import type { ThemeDefinition } from '../src/Theme';
2
+ declare const definition: ThemeDefinition;
3
+ export default definition;
@@ -0,0 +1,3 @@
1
+ import type { ThemeDefinition } from '../src/Theme';
2
+ declare const definition: ThemeDefinition;
3
+ export default definition;
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.resolveServerCredentials({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;
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":"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"}
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 { resolveServerCredentials as t } from "./server.mjs";
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.27.0",
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%',