Files
zooqueenandHanzo Dev 9e6ef253b9 world: the AI is ours — one summarize route, no competitor in the product
You asked why we were using Groq. We were not — and that is the bug.

`internal/world/ai.go` has always pointed at api.hanzo.ai/v1 with model "best",
and `handleSummarize` backs BOTH /v1/world/groq-summarize and
/v1/world/openrouter-summarize with that one Hanzo-backed handler. CI ships the
Go operator Service CR; vercel.json declares no api routes, so the Vercel
`api/*.js` edge functions that really did call api.groq.com and openrouter.ai
are not deployed at all.

So production ran on our stack the whole time. What shipped was the NAMING, and
it lied in every direction:

- the client logged `[Summarization] Groq success` — chosen from the ROUTE NAME,
  not from anything Groq did. That console line is what made a security review
  report a competing AI stack running inside a Hanzo surface.
- the progress UI told users "Connecting to Groq AI..." and "Trying OpenRouter..."
  while Hanzo served the request.
- Settings offered two features, "Groq summarization" and "OpenRouter
  summarization", each asking for a provider key, with signup links to
  console.groq.com and openrouter.ai — sending our users to competitors to buy
  keys for calls we were already making and paying for.
- 13 locales told users to "configure GROQ_API_KEY in Settings", a setting that
  does nothing.

And because both endpoints were one handler, `tryGroq` and `tryOpenRouter` were
never two providers: the "fallback chain" was the identical request twice, a
retry wearing a second vendor's name. Same for the translate path.

Now: one `/v1/world/summarize`, one `tryHanzo`, one `aiSummary` feature, and no
per-user provider key — the gateway takes the signed-in user's bearer, so the
inference meters to the org the user is acting in. The two old routes stay as
aliases so a cached bundle cannot 404 mid-rollout; delete them once nothing asks.

The `api/*.js` handlers are repointed at the Hanzo gateway through one shared
`api/_hanzo-ai.js` rather than left calling Groq. They are dead in this
deployment, but dead code that still holds a third-party endpoint and reads
GROQ_API_KEY is exactly what gets revived by accident.

Groq and OpenRouter still appear in `src/config/ai-datacenters.ts` and
`tech-companies.ts`. That is deliberate: world MAPS real companies and their
datacenters, and Groq is one. Data about a competitor is not a dependency on one.

Not done: 12 non-English locales had the vendor token swapped out to keep them
truthful, but their sentences still imply a setting that no longer exists
("configure Hanzo AI in Settings"). They want a real copy pass by someone who
speaks them; I will not fabricate translations.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 03:24:52 -07:00

59 lines
2.3 KiB
JavaScript

/**
* The ONE way this app talks to a model: the Hanzo gateway.
*
* Every AI call in world used to go straight to a third party — four files to
* api.groq.com and one to openrouter.ai, each with its own URL constant, its own
* upstream key and its own copy of the fetch. That is a competing AI stack
* running inside a Hanzo surface: the spend is off our books, the traffic is
* unmetered by our gateway, the prompts leave our infrastructure, and it needs
* per-provider keys nobody rotates. It also leaked into the product — the widget
* logged `[Summarization] Groq success` into the console of any page embedding
* it, which is how it was found.
*
* The wire format is identical (OpenAI-compatible chat/completions), so this is
* a swap of endpoint, credential and model — not a rewrite of any caller's
* logic. Everything else in world already reaches api.hanzo.ai (analytics,
* insights, telemetry); the model calls were the one exception.
*/
export const HANZO_API_URL = 'https://api.hanzo.ai/v1/chat/completions';
/**
* Fast, non-reasoning, in-catalog. The same model Hanzo Chat picked for titles
* and summaries by direct probe, and the right analogue of the
* `llama-3.1-8b-instant` these callers used: this is high-throughput
* classification and summarisation, not reasoning.
*/
export const FAST_MODEL = 'zen5-flash';
/** Missing credential is NOT an error here — every caller already degrades to a
* non-AI fallback, and that path is better than a 500. */
export function hanzoKey() {
return process.env.HANZO_API_KEY || '';
}
/**
* POST a chat completion to the Hanzo gateway.
*
* Returns the raw `Response` so callers keep their own status handling; they
* differ (some pass the upstream status through, some swallow it into a
* fallback), and flattening that here would change behaviour rather than
* relocate it.
*/
export async function hanzoChat({ apiKey, model = FAST_MODEL, messages, temperature = 0, max_tokens, response_format }) {
return fetch(HANZO_API_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages,
temperature,
...(max_tokens ? { max_tokens } : {}),
...(response_format ? { response_format } : {}),
}),
});
}