feat: llm.chat.v1 multi-turn supplier type + free Kimi K2.6 chat demo
Some checks failed
CI / typecheck + vitest (Node 20) (push) Has been cancelled

Adds a conversational supplier capability and a free demo, both streaming.

New paid `llm.chat.v1` type: one escrow per conversation (flat fee). "Start
chat" posts the escrow with a session-init placeholder prompt_hash and the
supplier Claims (reserving its single slot); turns then stream off-chain via
SSE with zero chain interaction; "End chat" has the supplier Submit a receipt
over the full transcript and the buyer Accept — the moment the user is charged.
The on-chain validator is content-agnostic (pins but never verifies the
hashes), so this reuses the existing contract unchanged. A supplier-side idle
watchdog auto-ends abandoned sessions before deliver_by.

Free Kimi K2.6 chat demo: a synthetic "demo:kimi-chat" tile that uses the same
chat UI but proxies straight to OpenRouter (no escrow, no payment), mirroring
the existing Piper demo.

Shared:
- postChatEscrow.ts + chatSessionPromptHash() (session-init prompt_hash helper
  shared by the buyer builder and the supplier start gate).

Supplier:
- CAPABILITY_KIND="chat-session" (+ CHAT_IDLE_TIMEOUT_MS); callOpenAiStream()
  streaming client; ChatSessionStore + endChatSession(); /v1/chat/{start,
  message,end} handlers with idle + hard-cap watchdog timers.

Buyer:
- Marketplace.startChat()/endChat() with relaxed (session-init) receipt
  verification + crypto verifyReceipt; /v1/chat/{start,message,end} routes
  (SSE passthrough) and a free /v1/chat-demo/message OpenRouter proxy;
  OPENROUTER_API_KEY/OPENROUTER_BASE_URL config; ChatForm.tsx + Dashboard
  KIMI_DEMO_SUPPLIER tile and llm.chat.v1 dispatch.

Deploy:
- docker-compose.supplier-kimi-chat.yml + supplier/.env.kimi-chat.example.

Typecheck passes across all workspaces; full vitest suite green except 15
pre-existing time-bomb/RED failures (tx-claim/accept/post-advert live-CBOR
fixtures with hardcoded past deadlines + the M0-C golden-datum test), confirmed
identical on a clean tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
david
2026-06-08 16:18:54 +02:00
parent e01a03b7f2
commit 9acad5b2b8
17 changed files with 1997 additions and 13 deletions

View File

@@ -126,17 +126,22 @@ describe("<Dashboard /> smoke", () => {
]);
render(wrap(<Dashboard />, mp));
// Wait for async discover to complete and cards to render. Dashboard
// appends a synthetic Piper-TTS demo card to whatever the indexer
// returned, so the count is N+1 until the on-chain TTS supplier ships.
// appends two synthetic demo cards (Piper-TTS + Kimi K2.6 chat) to whatever
// the indexer returned, so the count is N+2 until on-chain suppliers ship.
await waitFor(() => {
const cards = document.querySelectorAll("[data-testid='supplier-card']");
expect(cards.length).toBe(3);
expect(cards.length).toBe(4);
});
// Sanity: one of the cards is the demo Piper supplier.
const piperBadges = Array.from(
document.querySelectorAll("[data-testid='supplier-card']"),
).filter((c) => c.textContent?.includes("audio.synthesize.piper.v1"));
expect(piperBadges.length).toBe(1);
// Sanity: one of the cards is the demo Kimi K2.6 chat supplier.
const kimiBadges = Array.from(
document.querySelectorAll("[data-testid='supplier-card']"),
).filter((c) => c.textContent?.includes("llm.chat.v1"));
expect(kimiBadges.length).toBe(1);
});
});