Files
zeekayandHanzo Dev baba2ead0f Typecheck on the native compiler, keep 6 for the tools
typescript 7.0.2 is the native compiler and exports no JavaScript API, so ts-jest,
typescript-eslint and tsup cannot read it. It comes in aliased as tsgo and does
the typechecking; typescript stays on the 6 line for the tools. Each package's
typecheck script calls that path, since pnpm cannot link two packages that both
declare the bin name tsc. 29 diagnostics across the workspace before and after.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 00:56:00 -07:00
..

@hanzo/zendesk โ€” Hanzo AI for Zendesk

A Zendesk Support app (ZAF v2, ticket_sidebar) that puts Hanzo AI next to every ticket. It reads the current ticket and its conversation, and gives the agent one-click actions โ€” Summarize ticket, Draft reply (in the agent's voice), Suggest next steps, Explain the issue, and a freeform Ask โ€” then writes the result back into the reply editor or adds it as an internal note.

Built on the published @hanzo/ai headless client against api.hanzo.ai (/v1/...). No UI framework โ€” a plain HTML/TypeScript iframe panel bundled with esbuild.

How it works

Zendesk ticket_sidebar iframe
        โ”‚
   src/panel.ts โ”€โ”€ DOM glue only
        โ”œโ”€โ”€ src/zaf.ts     read ticket context ยท write back ยท secure-proxy fetch
        โ”œโ”€โ”€ src/ticket.ts  PURE: conversation โ†’ windowed text (+ honest scope note)
        โ””โ”€โ”€ src/hanzo.ts   PURE shaping + @hanzo/ai createAiClient (ONE model path)
        โ”‚
   api.hanzo.ai /v1/chat/completions   (via the Zendesk proxy โ€” see "Security")

Ticket context โ†’ model

zaf.getTicketContext reads ticket.subject, ticket.requester.name, and ticket.comments in one batched client.get([...]). ticket.buildContext then windows the conversation to a character budget (48k), keeping the newest comments (a support reply is grounded in the latest exchange) and dropping the oldest first โ€” always including at least the most recent comment, hard-cut if it alone is over budget. Each comment is labelled with its author and whether it was a public reply or an internal note. ticket.contextNote prepends one honest sentence so the model (and the agent) knows whether older context was omitted โ€” we never claim the whole thread was sent when it wasn't.

Reply write-back

The output isn't inserted automatically. The agent clicks:

  • Insert as reply โ†’ client.set('ticket.comment.type', 'publicReply') then client.invoke('ticket.comment.appendText', text) โ€” appends to the reply editor without clobbering what the agent already typed.
  • Add as note โ†’ the same, with type: 'internalNote'.

Security โ€” the Hanzo key never reaches the browser

The Hanzo API key (sk-โ€ฆ) is a secure installation setting (manifest.json โ†’ parameters[hanzo_api_key], "secure": true, "type": "password"). Zendesk stores secure settings server-side and does not expose them to the iframe โ€” so the panel can't (and doesn't) read the key.

Instead, model traffic is sent through Zendesk's request proxy: zaf.secureFetch implements a fetch that calls client.request(...) with secure: true and an Authorization: Bearer {{setting.hanzo_api_key}} header. Zendesk substitutes the real key on its server, out of reach of the browser. We pass that fetch to createAiClient({ baseUrl: 'https://api.hanzo.ai', fetch }), so we use the published @hanzo/ai client while the secret stays secure. No key is ever stored in localStorage, committed, or logged.

The optional Default model (default_model) is a non-secure setting, so the panel reads it from client.metadata().settings and falls back to zen5.

Build

pnpm install --filter @hanzo/zendesk
pnpm --filter @hanzo/zendesk build      # โ†’ dist/  (ZAF app bundle)
pnpm --filter @hanzo/zendesk test       # vitest
pnpm --filter @hanzo/zendesk typecheck  # tsc --noEmit

build.js (esbuild + Node stdlib) produces the bundle zcli expects:

dist/
โ”œโ”€โ”€ manifest.json
โ”œโ”€โ”€ translations/en.json
โ””โ”€โ”€ assets/
    โ”œโ”€โ”€ iframe.html      the ticket_sidebar page (manifest location url = assets/iframe.html)
    โ”œโ”€โ”€ panel.js         bundled from src/panel.ts (ESM, @hanzo/ai included)
    โ”œโ”€โ”€ styles.css
    โ”œโ”€โ”€ logo.png         128px Marketplace icon
    โ””โ”€โ”€ logo-small.png   in-panel icon

The ZAF SDK is not bundled โ€” iframe.html loads it from Zendesk's CDN (https://static.zdassets.com/zendesk_app_framework_sdk/2.0/zaf_sdk.min.js), which exposes the ZAFClient global.

Validate, package, and upload (zcli)

Uses @zendesk/zcli (a devDep).

# 1. Authenticate once against your Zendesk account.
npx zcli login -i
#   (or set ZENDESK_SUBDOMAIN, ZENDESK_EMAIL, ZENDESK_API_TOKEN)

# 2. Validate the built bundle.
npx zcli apps:validate dist

# 3. Package it into a zip (writes dist/tmp/*.zip).
npx zcli apps:package dist

# 4. Upload + install it as a PRIVATE app on your account.
#    Writes zcli.apps.config.json with the assigned app_id โ€” do not commit it.
npx zcli apps:create dist

# 5. Push later changes to the same installed app.
npx zcli apps:update dist

pnpm --filter @hanzo/zendesk validate / package wrap steps 2โ€“3.

Configure the app

After install, in Admin Center โ†’ Apps and integrations โ†’ Zendesk Support apps, open the app's settings and:

  1. Paste your Hanzo API key (sk-โ€ฆ) into Hanzo API key (secure).
  2. Optionally set a Default model (defaults to zen5).
  3. Save. The panel appears in the ticket sidebar.

Generate a Hanzo API key at https://hanzo.ai.

Marketplace

To list publicly, set "private": false in manifest.json, fill in translations/en.json (short_description โ‰ค 80 chars, long_description, installation_instructions), then submit the packaged zip via the Zendesk Marketplace developer flow (Admin Center โ†’ Marketplace โ†’ submit an app). See the Zendesk app publishing guide.

Layout

File Role
src/config.ts Constants: gateway base URL, default model, setting names, context budget.
src/ticket.ts Pure. Ticket conversation โ†’ windowed, labelled, truncated text + honest scope note.
src/hanzo.ts Pure shaping + the one @hanzo/ai model path (quick-action catalog, buildMessages, stream/complete).
src/zaf.ts The ZAF boundary: init, ticket read, write-back, secure-proxy fetch.
src/panel.ts / panel.html / styles.css The iframe UI.
manifest.json ZAF v2 app manifest (location ticket_sidebar, secure key parameter).
translations/en.json App name, descriptions, and parameter labels.
build.js esbuild โ†’ dist/ ZAF app bundle.

License

MIT