@surfmate.team/digital-human-llm
v0.3.1
Published
LLM adapters behind digital-human-ports' ChatPort (prompt → completion text). Grok (via a Supabase Edge Function) is the first; new providers are just another factory behind the same port.
Downloads
1,394
Maintainers
Readme
@surfmate.team/digital-human-llm
Zero-dependency LLM adapters behind a tiny ChatPort:
interface ChatPort {
complete(prompt: string): Promise<string>
}Each provider is a factory that returns a ChatPort. The package owns the port it
implements, so it depends on nothing — any consumer expecting the same shape can
use it directly (structural typing).
Install
npm i @surfmate.team/digital-human-llmUsage — Grok (via a Supabase Edge Function)
The provider's API key stays server-side behind the Edge Function; the browser only sends the caller's auth token.
import { createGrokChat } from '@surfmate.team/digital-human-llm'
const chat = createGrokChat({
url: import.meta.env.VITE_GROK_API_URL,
getAccessToken: async () => (await supabase.auth.getSession()).data.session?.access_token ?? null,
})
const reply = await chat.complete('Say hello in one short sentence.')createGrokChat(config)
| field | type | notes |
| ---------------- | ------------------------------------- | ------------------------------------------------ |
| url | string | Edge Function URL. |
| getAccessToken | () => Promise<string \| null> | Bearer token for the request (e.g. a session). |
| fetchImpl | typeof fetch (optional) | Override fetch (tests / non-browser). |
Adding a provider (OpenAI, Claude, …) is just another factory returning a
ChatPort — consumers never change.
Server side — the Grok Edge Function
createGrokChat keeps the x.ai API key off the client. Point url at a small
Edge Function that holds the key and talks to x.ai. The wire contract is:
- Request —
POSTwithAuthorization: Bearer <token>and JSON body{ prompt, operation, bookmark_id }. - Response — JSON
{ success: true, text }on success, or{ success: false, error }(HTTP non-2xx is also treated as an error).
A minimal reference (Supabase Edge Function, Deno). Auth is optional; add metering/billing as you see fit:
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const cors = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
const json = (body: unknown, status = 200) =>
new Response(JSON.stringify(body), { status, headers: { ...cors, 'Content-Type': 'application/json' } })
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '',
)
serve(async (req) => {
if (req.method === 'OPTIONS') return new Response('ok', { headers: cors })
try {
const apiKey = Deno.env.get('GROK_API_KEY') // <- the secret lives here, server-side
if (!apiKey) return json({ success: false, error: 'API key not configured' }, 500)
// Optional: authenticate the caller (the Bearer token createGrokChat sends).
const token = req.headers.get('Authorization')?.replace('Bearer ', '')
if (!token) return json({ success: false, error: 'Missing authorization' }, 401)
const { data: { user }, error } = await supabase.auth.getUser(token)
if (error || !user) return json({ success: false, error: 'Invalid token' }, 401)
const { prompt } = await req.json()
if (!prompt) return json({ success: false, error: 'Missing prompt' }, 400)
const res = await fetch('https://api.x.ai/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({
model: 'grok-4-1-fast-non-reasoning',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
max_tokens: 4096,
stream: false,
}),
})
if (!res.ok) return json({ success: false, error: `Grok ${res.status}: ${await res.text()}` }, 502)
const data = await res.json()
const text = data.choices?.[0]?.message?.content
if (!text) return json({ success: false, error: 'No text generated' }, 502)
return json({ success: true, text }) // <- createGrokChat reads `text`
} catch (e) {
return json({ success: false, error: e instanceof Error ? e.message : 'Unknown error' }, 500)
}
})Secrets (
GROK_API_KEY,SUPABASE_SERVICE_ROLE_KEY) are environment variables on the function — never shipped to the browser. To add token metering, deduct from your own balance table after readingdata.usagefrom the x.ai response.
Reference deployment
The reference Grok Edge Function (with the GROK_API_KEY_SURF secret and the
user_balance / token_usage tables) is hosted in a Supabase project under the
[email protected] account. Log in there to find / edit / redeploy it or
rotate the key. The client only needs the function URL (VITE_GROK_API_URL) plus
the caller's Supabase session token as Bearer.
License
MIT
