@kidsinai/kids-client 0.0.23 → 0.0.25

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@kidsinai/kids-client",
4
- "version": "0.0.23",
4
+ "version": "0.0.25",
5
5
  "type": "module",
6
6
  "description": "Own-client TUI for Kids OpenCode — talks to local `opencode serve` via @opencode-ai/sdk v2 with kid-warm rendering, mission progress, permission dialog, and stderr-tail audit pipeline.",
7
7
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "ink-spinner": "^5.0.0",
36
36
  "ink-text-input": "^6.0.0",
37
37
  "react": "^18.3.1",
38
- "@kidsinai/kids-opencode-plugin": "^0.0.22"
38
+ "@kidsinai/kids-opencode-plugin": "^0.0.24"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@opencode-ai/sdk": "^1.14.51",
@@ -69,3 +69,28 @@ function pickModels(models: unknown): unknown[] {
69
69
  if (models && typeof models === "object") return Object.values(models)
70
70
  return []
71
71
  }
72
+
73
+ /** True when an LLM error is about the *model itself* (not auth/network) — e.g.
74
+ * a model the current ChatGPT-account auth isn't allowed to use. The fix is to
75
+ * switch models, so callers should re-pick rather than retry or re-auth. */
76
+ export function isModelUnavailable(msg: string): boolean {
77
+ const m = msg.toLowerCase()
78
+ return m.includes("not supported")
79
+ || m.includes("model_not_found")
80
+ || m.includes("does not have access")
81
+ || m.includes("not available")
82
+ || m.includes("unsupported model")
83
+ }
84
+
85
+ /** Pick a kid-safe default model: prefer the small/standard tiers that work
86
+ * with ChatGPT-account auth; skip the `-pro` tiers (API-key only, rejected by
87
+ * the Codex/OAuth path). Returns null only if the server reports no models. */
88
+ export function pickDefaultModel(models: ModelChoice[]): ModelChoice | null {
89
+ const usable = models.filter((m) => !/-pro\b/i.test(m.id))
90
+ const prefer = ["gpt-5.4-mini", "gpt-5.4", "claude-3-5-sonnet", "sonnet", "gpt-5.5"]
91
+ for (const p of prefer) {
92
+ const hit = usable.find((m) => m.id.toLowerCase().includes(p))
93
+ if (hit) return hit
94
+ }
95
+ return usable[0] ?? models[0] ?? null
96
+ }
package/src/index.tsx CHANGED
@@ -31,7 +31,7 @@ import { listInstalledPacks, resolveContext } from "./core/course-pack.ts"
31
31
  import { readLastSession, writeLastSession } from "./core/last-session.ts"
32
32
  import { isCompletionTrigger, runCheck } from "./core/check-runner.ts"
33
33
  import { parseSlash, matchCommand } from "./core/commands.ts"
34
- import { listModels } from "./core/models.ts"
34
+ import { listModels, isModelUnavailable, pickDefaultModel } from "./core/models.ts"
35
35
  import { findFiles } from "./core/files.ts"
36
36
  import { App } from "./render/ink/App.tsx"
37
37
  import { FREE_PLAY_PACK_ID } from "./render/ink/screens/CoursePackPicker.tsx"
@@ -652,10 +652,32 @@ function makeFullHandlers(
652
652
 
653
653
  store.update({ thinking: true })
654
654
  updateLastSession()
655
+ // Resolve a usable model up front. With no explicit pick, the server
656
+ // falls back to whatever model it last used — which may be one the
657
+ // current auth can't use (a ChatGPT-account login can't use gpt-5.5-pro).
658
+ // Pinning a known-good default makes the kid's first message just work.
659
+ let model = snap.selectedModel
660
+ if (!model) {
661
+ const def = pickDefaultModel(await listModels(client))
662
+ if (def) {
663
+ model = def.id
664
+ store.update({ selectedModel: def.id, selectedModelLabel: def.label })
665
+ }
666
+ }
655
667
  try {
656
- await session.prompt(text, { model: snap.selectedModel ?? undefined })
668
+ await session.prompt(text, { model: model ?? undefined })
657
669
  } catch (err) {
658
670
  const detail = errMessage(err)
671
+ if (isModelUnavailable(detail)) {
672
+ // The model isn't usable on this account (e.g. a -pro model under a
673
+ // ChatGPT login). Clear it so the next message auto-picks a good
674
+ // default, and guide the kid to /model instead of a scary error.
675
+ store.update({ thinking: false, selectedModel: null, selectedModelLabel: null })
676
+ sysMessage(env.locale === "zh-Hans"
677
+ ? "这个 AI 模型在你的账号下用不了。直接再发一条消息会自动换成可用模型,或打 /model 自己选(推荐 gpt-5.4-mini)。"
678
+ : "That AI model isn't available on your account. Just send again to auto-switch to a usable one, or type /model to choose (try gpt-5.4-mini).")
679
+ return
680
+ }
659
681
  store.update({ thinking: false, screen: { kind: "error", variant: classifyLlmError(detail), detail } })
660
682
  }
661
683
  },