thinkpool-pair 0.7.25 → 0.7.26

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.
package/README.md CHANGED
@@ -98,5 +98,37 @@ npx thinkpool-pair@latest <ROOM> -- claude # structured Claude, no TTY
98
98
  - `resize` — web viewport size → headless PTYs only (the attached terminal
99
99
  follows your own TTY).
100
100
 
101
+ ## Alternative LLM Providers
102
+
103
+ The bridge supports using alternative LLM providers that are compatible with the Anthropic API:
104
+
105
+ ### OpenRouter
106
+ To use OpenRouter instead of Anthropic directly:
107
+ ```bash
108
+ npx thinkpool-pair provider openrouter --token sk-or-... [--model anthropic/claude-3.5-sonnet]
109
+ ```
110
+
111
+ This will configure the bridge to use OpenRouter's API endpoint with your provided token. You can optionally specify a model.
112
+
113
+ ### GLM (Z.ai)
114
+ To use GLM via Z.ai's Anthropic-compatible endpoint:
115
+ ```bash
116
+ npx thinkpool-pair provider glm --token zai_...
117
+ ```
118
+
119
+ ### Custom Provider
120
+ For any other Anthropic-compatible endpoint:
121
+ ```bash
122
+ npx thinkpool-pair provider custom --base https://your-endpoint.com --token your-token [--model model-name]
123
+ ```
124
+
125
+ ### Reset to Default
126
+ To reset back to using Anthropic directly:
127
+ ```bash
128
+ npx thinkpool-pair provider anthropic
129
+ ```
130
+
131
+ Provider configurations are stored in `~/.thinkpool-pair/provider.json` and apply to all bridges on the machine.
132
+
101
133
  Public anon creds are embedded (the same ones the web app ships). Override with
102
134
  `TP_SUPABASE_URL` / `TP_SUPABASE_ANON` if needed. Set `TP_NAME` to label yourself.
package/bridge.mjs CHANGED
@@ -597,9 +597,16 @@ function openStructured({ id, model, resume, log, commands, mode }) {
597
597
  // on the entry so the ANNOUNCE can hand it to clients that connect/reload
598
598
  // AFTER init (the one-time code-event would miss them), then re-announce.
599
599
  if (evt.kind === 'system' && Array.isArray(evt.commands) && evt.commands.length && !entry.commands) { entry.commands = evt.commands; announce(); persist() }
600
- // Chrome events (mode / usage / clear) are transient state, not transcript —
601
- // broadcast + print them, but keep them out of the persisted/replayed log.
602
- const chrome = evt.kind === 'mode' || evt.kind === 'usage' || evt.kind === 'clear'
600
+ // Chrome events (mode / usage / clear / compact) are transient state, not
601
+ // transcript — broadcast + print them but keep out of the persisted/replayed log.
602
+ const chrome = evt.kind === 'mode' || evt.kind === 'usage' || evt.kind === 'clear' || evt.kind === 'compact'
603
+ // compact turn finished (or errored) → clear the pulsing indicator.
604
+ // No "done" ctl line — the SDK's own output in the transcript is the
605
+ // signal (compact summary on success, AbortError text on abort/too-small).
606
+ if ((evt.kind === 'result' || evt.kind === 'error') && entry.compacting) {
607
+ entry.compacting = false
608
+ bcast('code-event', { term: id, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
609
+ }
603
610
  if (!chrome) {
604
611
  entry.log.push(evt)
605
612
  if (entry.log.length > STRUCTURED_LOG_MAX) entry.log.shift()
@@ -814,6 +821,16 @@ channel
814
821
  ctlLine('context cleared. You can continue with these answers in mind.')
815
822
  return
816
823
  }
824
+ // /compact → announce immediately, track so onEvent can emit a done signal when
825
+ // the SDK turn finishes. No echoYou — the ctl line IS the announcement.
826
+ if (/^\/compact\b/.test(text)) {
827
+ ctlLine('◈ compacting context…')
828
+ s.compacting = true
829
+ s.compactBy = payload.by
830
+ bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'start', ts: Date.now() } })
831
+ s.session.sendTurn(text)
832
+ return
833
+ }
817
834
  s.session.sendTurn(text)
818
835
  echoYou()
819
836
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.25",
3
+ "version": "0.7.26",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": {
package/provider.mjs CHANGED
@@ -19,6 +19,7 @@
19
19
  CLI (dispatched from bridge.mjs):
20
20
  npx thinkpool-pair provider # show current
21
21
  npx thinkpool-pair provider glm --token <zai_…> # use GLM (Z.ai)
22
+ npx thinkpool-pair provider openrouter --token <sk-or-…> [--model <model>]
22
23
  npx thinkpool-pair provider custom --base <url> --token <key>
23
24
  npx thinkpool-pair provider anthropic # reset to Claude
24
25
  ───────────────────────────────────────────────────────────── */
@@ -33,6 +34,9 @@ const FILE = path.join(DIR, 'provider.json')
33
34
  // into source); --base / --model override these if the endpoint ever moves.
34
35
  const GLM = { baseUrl: 'https://api.z.ai/api/anthropic', model: 'glm-5.2' }
35
36
 
37
+ // The pre-baked OpenRouter preset. --token is always required.
38
+ const OPENROUTER = { baseUrl: 'https://openrouter.ai/api/v1', model: 'anthropic/claude-3.5-sonnet' }
39
+
36
40
  function ensureDir() { try { fs.mkdirSync(DIR, { recursive: true }) } catch { /* noop */ } }
37
41
  function opt(args, flag) { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined }
38
42
 
@@ -58,6 +62,23 @@ export function applyProviderEnv() {
58
62
  const provider = (process.env.TP_PROVIDER || cfg.provider || 'anthropic').toLowerCase()
59
63
  if (provider === 'anthropic' || !provider) return { provider: 'anthropic', source: 'default' }
60
64
 
65
+ // Handle OpenRouter specifically
66
+ if (provider === 'openrouter') {
67
+ const baseUrl = process.env.TP_ANTHROPIC_BASE_URL || cfg.baseUrl || OPENROUTER.baseUrl
68
+ const authToken = process.env.TP_ANTHROPIC_AUTH_TOKEN || cfg.authToken
69
+ const model = process.env.TP_ANTHROPIC_MODEL || cfg.model || OPENROUTER.model
70
+
71
+ if (!authToken) {
72
+ process.stderr.write(`\n ◇ provider "${provider}" is set but is missing authToken.\n Finish setup: npx thinkpool-pair provider ${provider} --token <key>\n Falling back to regular Claude for now.\n`)
73
+ return { provider: 'anthropic', source: 'incomplete' }
74
+ }
75
+
76
+ process.env.ANTHROPIC_BASE_URL = baseUrl
77
+ process.env.ANTHROPIC_AUTH_TOKEN = authToken
78
+ if (model) process.env.ANTHROPIC_MODEL = model
79
+ return { provider, baseUrl, model, source: cfg.provider ? 'file' : 'env' }
80
+ }
81
+
61
82
  const baseUrl = process.env.TP_ANTHROPIC_BASE_URL || cfg.baseUrl
62
83
  const authToken = process.env.TP_ANTHROPIC_AUTH_TOKEN || cfg.authToken
63
84
  const model = process.env.TP_ANTHROPIC_MODEL || cfg.model
@@ -104,6 +125,17 @@ export async function runProvider(args) {
104
125
  return
105
126
  }
106
127
 
128
+ // ── OpenRouter preset ──
129
+ if (sub === 'openrouter') {
130
+ const token = opt(args, '--token') || opt(args, '--key')
131
+ const baseUrl = opt(args, '--base') || opt(args, '--base-url') || OPENROUTER.baseUrl
132
+ const model = opt(args, '--model') || OPENROUTER.model
133
+ if (!token) { console.error('\n ✗ openrouter needs --token <sk-or-…>. Get one at https://openrouter.ai/\n'); process.exit(1) }
134
+ saveProvider({ provider: 'openrouter', baseUrl, authToken: token, model, savedAt: Date.now() })
135
+ console.error(`\n ✓ provider set to openrouter.\n base url: ${baseUrl}\n model: ${model}\n token: ${token.slice(0, 8)}…\n Restart the bridge (or its launchd service) to apply.\n`)
136
+ return
137
+ }
138
+
107
139
  // ── arbitrary Anthropic-compatible endpoint ──
108
140
  if (sub === 'custom') {
109
141
  const token = opt(args, '--token') || opt(args, '--key')
@@ -115,6 +147,6 @@ export async function runProvider(args) {
115
147
  return
116
148
  }
117
149
 
118
- console.error('\n usage:\n npx thinkpool-pair provider # show current\n npx thinkpool-pair provider glm --token <zai_…> [--model glm-5.2]\n npx thinkpool-pair provider custom --base <url> --token <key> [--model <m>]\n npx thinkpool-pair provider anthropic # reset to Claude\n')
150
+ console.error('\n usage:\n npx thinkpool-pair provider # show current\n npx thinkpool-pair provider glm --token <zai_…> [--model glm-5.2]\n npx thinkpool-pair provider openrouter --token <sk-or-…> [--model <model>]\n npx thinkpool-pair provider custom --base <url> --token <key> [--model <m>]\n npx thinkpool-pair provider anthropic # reset to Claude\n')
119
151
  process.exit(1)
120
152
  }