thinkpool-pair 0.7.279 → 0.7.281

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/bridge.mjs CHANGED
@@ -46,7 +46,7 @@ import { createPermNotifier, shouldNotifyTurnDone, permissionSummary, clipSummar
46
46
  // resolveProviderEnv(id) → {ANTHROPIC_BASE_URL,ANTHROPIC_AUTH_TOKEN,ANTHROPIC_MODEL} for a
47
47
  // registered custom provider, or null for the built-in/unknown (leave the default env intact).
48
48
  // Multi-provider BYOK slice 1: a lane spawned with a `provider` id runs on that endpoint.
49
- import { resolveProviderEnv, providerNameMap, publicKeyB64, announceProviders, listProviders, addProvider, removeProvider, unseal, effectiveLaneModel, sameProviderEnv, providerModel } from './providers.mjs'
49
+ import { resolveProviderEnv, resolveProviderRef, providerNameMap, publicKeyB64, announceProviders, listProviders, addProvider, removeProvider, unseal, effectiveLaneModel, sameProviderEnv, providerModel } from './providers.mjs'
50
50
  import { validateProviderSwitch, providerSwitchPlan, BUILTIN_PROVIDER } from './switch-provider.mjs'
51
51
  import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'
52
52
  import { z } from 'zod'
@@ -2071,6 +2071,10 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
2071
2071
  const isIndependentMain = !entry.spawnedBy && !entry.sideParent && !entry.flowRole && (entry.spawnDepth || 0) === 0
2072
2072
  const canOpenMainConductor = isIndependentMain && entry.cascadeRole !== 'conductor'
2073
2073
  const canSpawnWorkers = isIndependentMain
2074
+ const registeredProviderChoices = () => announceProviders()
2075
+ .filter((provider) => provider.id !== 'anthropic')
2076
+ .map((provider) => `${provider.name}${provider.model ? ` (${provider.model})` : ''}`)
2077
+ .join(', ')
2074
2078
  const resolveAgentOpen = (args, { worker = false } = {}) => {
2075
2079
  const runtime = args?.runtime || entry.runtime || 'claude'
2076
2080
  if (runtime === 'codex' && args?.provider) return { error: 'A Codex terminal uses its Codex/OpenAI login in v1; custom bridge providers are not wired to Codex yet. Omit provider or open a Claude terminal for that provider.' }
@@ -2084,6 +2088,11 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
2084
2088
  if (runtime === 'hermes' && !modelCatalogValues(catalog).has(args.model)) {
2085
2089
  return { error: `Could not open a Hermes worker on ${JSON.stringify(args.model)} — that exact model is not in this parent session's ACP catalog.` }
2086
2090
  }
2091
+ const provider = runtime === 'claude' && args?.provider ? resolveProviderRef(args.provider) : args?.provider
2092
+ if (runtime === 'claude' && args?.provider && !provider) {
2093
+ const choices = registeredProviderChoices()
2094
+ return { error: `Unknown registered provider ${JSON.stringify(args.provider)}. No lane was opened and the built-in Claude login was not used.${choices ? ` Available provider names: ${choices}.` : ' No custom providers are registered on this host.'}` }
2095
+ }
2087
2096
  if (runtime === 'claude' && !args?.provider && args?.model && /^gpt-/i.test(args.model)) {
2088
2097
  return { error: `Could not open a Claude terminal on Codex model ${JSON.stringify(args.model)}. Choose runtime="codex" or a Claude model.` }
2089
2098
  }
@@ -2096,7 +2105,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
2096
2105
  // permission-card mode from the conductor and never honor a lower explicit
2097
2106
  // override: spawn_terminal always opens them in bypassPermissions.
2098
2107
  mode: worker ? 'bypassPermissions' : (args?.mode || (entry.mode === 'plan' ? 'default' : entry.mode)),
2099
- provider: args?.provider,
2108
+ provider,
2100
2109
  }
2101
2110
  }
2102
2111
  const dispatchContext = (now = Date.now()) => ({
@@ -2414,7 +2423,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
2414
2423
  model: z.string().optional().describe('model for the chosen runtime; Hermes workers require an exact ACP catalog ID such as nous:anthropic/claude-sonnet-4.6'),
2415
2424
  sliceType: z.enum(['scaffold', 'feature', 'fix', 'review']).optional().describe('optional cascade slice tier; omitted preserves normal inheritance/default, explicit model wins'),
2416
2425
  runtime: z.enum(['claude', 'codex', 'hermes']).optional().describe('agent runtime for the new lane; Hermes is allowed only from a top-level Hermes parent with an explicit catalog model'),
2417
- provider: z.string().optional().describe('optional registered LLM provider id to run this lane on (from the account\'s provider registry); omit for the default Claude/Anthropic path'),
2426
+ provider: z.string().optional().describe(`optional registered Claude-compatible provider name or id; omit for built-in Anthropic${registeredProviderChoices() ? `. Available now: ${registeredProviderChoices()}` : ''}`),
2418
2427
  mode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).optional().describe('accepted for compatibility; spawned workers always run autonomously in bypassPermissions'),
2419
2428
  },
2420
2429
  async (args) => {
@@ -165,7 +165,7 @@ function makeInputStream() {
165
165
  // Simplify SDK assistant content blocks to a stable wire shape.
166
166
  const simplifyBlocks = (blocks = []) => blocks.map((b) => {
167
167
  if (b.type === 'text') return { type: 'text', text: b.text }
168
- if (b.type === 'thinking') return { type: 'thinking', text: b.thinking || '' }
168
+ if (b.type === 'thinking') return { type: 'thinking', text: b.thinking || '', reasoningKind: 'thought' }
169
169
  if (b.type === 'tool_use') return { type: 'tool_use', id: b.id, name: b.name, input: b.input }
170
170
  return { type: b.type }
171
171
  }).filter(Boolean)
@@ -125,7 +125,7 @@ export class CodexEventMapper {
125
125
  if (it.type === 'agent_message') {
126
126
  this._emit({ kind: 'assistant', blocks: [{ type: 'text', text: it.text || '' }], parentToolUseId: null })
127
127
  } else if (it.type === 'reasoning') {
128
- this._emit({ kind: 'assistant', blocks: [{ type: 'thinking', text: it.text || '' }], parentToolUseId: null })
128
+ this._emit({ kind: 'assistant', blocks: [{ type: 'thinking', text: it.text || '', reasoningKind: 'summary' }], parentToolUseId: null })
129
129
  } else if (it.type === 'command_execution') {
130
130
  const bad = it.exit_code != null ? it.exit_code !== 0 : false
131
131
  this._emit({ kind: 'tool_result', toolUseId: it.id, content: [{ type: 'text', text: it.aggregated_output || '' }], isError: bad, durationMs, parentToolUseId: null })
@@ -153,7 +153,7 @@ export class HermesEventMapper {
153
153
  }
154
154
  this.tools.clear()
155
155
  const blocks = []
156
- if (this.thought) blocks.push({ type: 'thinking', text: this.thought })
156
+ if (this.thought) blocks.push({ type: 'thinking', text: this.thought, reasoningKind: 'thought' })
157
157
  if (this.text) blocks.push({ type: 'text', text: this.text })
158
158
  if (blocks.length) this._emit({ kind: 'assistant', blocks, parentToolUseId: null, ...(this.textCid ? { replacesCid: this.textCid } : {}) })
159
159
  this._emit({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.279",
3
+ "version": "0.7.281",
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/providers.mjs CHANGED
@@ -333,6 +333,27 @@ export function announceProviders() {
333
333
  return [{ id: BUILTIN_ID, name: 'Anthropic (Claude)', model: null, group: BUILTIN_ID }, ...custom]
334
334
  }
335
335
 
336
+ /**
337
+ * Resolve an agent-facing provider reference without ever falling through to the
338
+ * built-in Claude login. Accept an exact id, a unique case-insensitive display
339
+ * name, or a unique configured model. Unknown/ambiguous references return null
340
+ * so callers can fail closed and show the current safe choices.
341
+ */
342
+ export function resolveProviderRef(ref) {
343
+ if (!ref || ref === BUILTIN_ID) return BUILTIN_ID
344
+ const raw = String(ref).trim()
345
+ if (!raw) return BUILTIN_ID
346
+ const providers = loadProviders()
347
+ const exact = providers.find((provider) => provider.id === raw)
348
+ if (exact) return exact.id
349
+ const needle = raw.toLocaleLowerCase()
350
+ const matches = providers.filter((provider) => (
351
+ String(provider.name || '').trim().toLocaleLowerCase() === needle
352
+ || String(provider.model || '').trim().toLocaleLowerCase() === needle
353
+ ))
354
+ return matches.length === 1 ? matches[0].id : null
355
+ }
356
+
336
357
  /**
337
358
  * id → name map for the ROOM announce's per-lane provider projection (name-only,
338
359
  * NEVER key or baseUrl). Built once per announce so a cross-device viewer resolves
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": 6,
3
+ "bundleVersion": 7,
4
4
  "contracts": [
5
5
  {
6
6
  "id": "room-coordination",
@@ -41,7 +41,8 @@
41
41
  },
42
42
  {
43
43
  "id": "work-routing",
44
- "version": 2,
44
+ "version": 3,
45
+ "providerRoutingContract": "A Claude worker may select a connected Anthropic-compatible provider by durable id, unique display name, or unique configured model. Unknown or ambiguous references fail closed and must never fall through to built-in Claude.",
45
46
  "routes": [
46
47
  {
47
48
  "id": "work-routing",