gsd-pi 2.76.0-dev.4100bd590 → 2.76.0-dev.76f9a2dc5

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.
Files changed (112) hide show
  1. package/dist/resource-loader.d.ts +1 -1
  2. package/dist/resource-loader.js +2 -8
  3. package/dist/resources/extensions/gsd/auto/phases.js +4 -1
  4. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +13 -2
  6. package/dist/resources/extensions/gsd/auto-start.js +12 -7
  7. package/dist/resources/extensions/gsd/auto.js +4 -1
  8. package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
  9. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  10. package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
  11. package/dist/resources/extensions/gsd/safety/file-change-validator.js +1 -1
  12. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  13. package/dist/web/standalone/.next/BUILD_ID +1 -1
  14. package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
  15. package/dist/web/standalone/.next/build-manifest.json +2 -2
  16. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  17. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/index.html +1 -1
  34. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
  41. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  43. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  44. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  45. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  46. package/package.json +1 -1
  47. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  48. package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
  49. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  50. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
  51. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
  52. package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
  53. package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
  54. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
  55. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
  56. package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
  57. package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
  58. package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
  59. package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
  60. package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
  61. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  62. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
  63. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
  65. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
  67. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
  68. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
  69. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
  71. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/model-registry.js +76 -10
  73. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  75. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  77. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  78. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
  80. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  81. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  82. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  85. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
  86. package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
  87. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
  88. package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
  89. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  90. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
  91. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  92. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  93. package/scripts/link-workspace-packages.cjs +1 -0
  94. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  95. package/src/resources/extensions/gsd/auto/phases.ts +4 -0
  96. package/src/resources/extensions/gsd/auto/session.ts +7 -1
  97. package/src/resources/extensions/gsd/auto-model-selection.ts +16 -1
  98. package/src/resources/extensions/gsd/auto-start.ts +12 -7
  99. package/src/resources/extensions/gsd/auto.ts +4 -1
  100. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
  101. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  102. package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
  103. package/src/resources/extensions/gsd/safety/file-change-validator.ts +1 -1
  104. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
  105. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
  106. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
  107. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
  108. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +20 -0
  109. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  110. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
  111. /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → UMCfv_sVnLXawpUAjvArc}/_buildManifest.js +0 -0
  112. /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → UMCfv_sVnLXawpUAjvArc}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"provider-manager.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/provider-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TUI component for managing provider configurations.\n * Shows providers with auth status, discovery support, and model counts.\n */\n\nimport {\n\tContainer,\n\ttype Focusable,\n\tgetEditorKeybindings,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@gsd/pi-tui\";\nimport type { AuthStorage } from \"../../../core/auth-storage.js\";\nimport { getDiscoverableProviders } from \"../../../core/model-discovery.js\";\nimport { providerDisplayName } from \"./model-selector.js\";\nimport type { ModelRegistry } from \"../../../core/model-registry.js\";\nimport { ModelsJsonWriter } from \"../../../core/models-json-writer.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { rawKeyHint } from \"./keybinding-hints.js\";\n\ninterface ProviderInfo {\n\tname: string;\n\thasAuth: boolean;\n\tsupportsDiscovery: boolean;\n\tmodelCount: number;\n}\n\nexport class ProviderManagerComponent extends Container implements Focusable {\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t}\n\n\tprivate providers: ProviderInfo[] = [];\n\tprivate selectedIndex = 0;\n\tprivate listContainer: Container;\n\tprivate tui: TUI;\n\tprivate authStorage: AuthStorage;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate modelsJsonWriter: ModelsJsonWriter;\n\tprivate onDone: () => void;\n\tprivate onDiscover: (provider: string) => void;\n\tprivate onSetupAuth: (provider: string) => void;\n\tprivate confirmingRemove = false;\n\tprivate hintsContainer: Container;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tauthStorage: AuthStorage,\n\t\tmodelRegistry: ModelRegistry,\n\t\tonDone: () => void,\n\t\tonDiscover: (provider: string) => void,\n\t\tonSetupAuth?: (provider: string) => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.authStorage = authStorage;\n\t\tthis.modelRegistry = modelRegistry;\n\t\tthis.modelsJsonWriter = new ModelsJsonWriter(this.modelRegistry.modelsJsonPath);\n\t\tthis.onDone = onDone;\n\t\tthis.onDiscover = onDiscover;\n\t\tthis.onSetupAuth = onSetupAuth ?? (() => {});\n\n\t\t// Header\n\t\tthis.addChild(new Text(theme.fg(\"accent\", \"Provider Manager\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Hints\n\t\tthis.hintsContainer = new Container();\n\t\tthis.addChild(this.hintsContainer);\n\t\tthis.updateHints();\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// List\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.loadProviders();\n\t\tthis.updateList();\n\t}\n\n\tprivate loadProviders(): void {\n\t\tconst discoverableSet = new Set(getDiscoverableProviders());\n\t\tconst allModels = this.modelRegistry.getAll();\n\n\t\t// Group models by provider\n\t\tconst providerModelCounts = new Map<string, number>();\n\t\tfor (const model of allModels) {\n\t\t\tproviderModelCounts.set(model.provider, (providerModelCounts.get(model.provider) ?? 0) + 1);\n\t\t}\n\n\t\t// Build provider list from all known providers\n\t\tconst providerNames = new Set([\n\t\t\t...providerModelCounts.keys(),\n\t\t\t...discoverableSet,\n\t\t]);\n\n\t\tthis.providers = Array.from(providerNames)\n\t\t\t.sort()\n\t\t\t.map((name) => ({\n\t\t\t\tname,\n\t\t\t\thasAuth: this.authStorage.hasAuth(name),\n\t\t\t\tsupportsDiscovery: discoverableSet.has(name),\n\t\t\t\tmodelCount: providerModelCounts.get(name) ?? 0,\n\t\t\t}));\n\t\tthis.clampSelectedIndex();\n\t}\n\n\tprivate clampSelectedIndex(): void {\n\t\tif (this.providers.length === 0) {\n\t\t\tthis.selectedIndex = 0;\n\t\t\treturn;\n\t\t}\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, this.providers.length - 1);\n\t}\n\n\tprivate updateHints(): void {\n\t\tthis.hintsContainer.clear();\n\t\tif (this.confirmingRemove) {\n\t\t\tconst hints = [\n\t\t\t\trawKeyHint(\"r\", \"confirm removal\"),\n\t\t\t\trawKeyHint(\"esc\", \"cancel\"),\n\t\t\t].join(\" \");\n\t\t\tthis.hintsContainer.addChild(new Text(hints, 0, 0));\n\t\t} else {\n\t\t\tconst hints = [\n\t\t\t\trawKeyHint(\"enter\", \"setup auth\"),\n\t\t\t\trawKeyHint(\"d\", \"discover\"),\n\t\t\t\trawKeyHint(\"r\", \"remove auth\"),\n\t\t\t\trawKeyHint(\"esc\", \"close\"),\n\t\t\t].join(\" \");\n\t\t\tthis.hintsContainer.addChild(new Text(hints, 0, 0));\n\t\t}\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tfor (let i = 0; i < this.providers.length; i++) {\n\t\t\tconst p = this.providers[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\tconst authBadge = p.hasAuth ? theme.fg(\"success\", \"[auth]\") : theme.fg(\"muted\", \"[no auth]\");\n\t\t\tconst discoveryBadge = p.supportsDiscovery ? theme.fg(\"accent\", \"[discovery]\") : \"\";\n\t\t\tconst countBadge = theme.fg(\"muted\", `(${p.modelCount} models)`);\n\n\t\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"> \") : \" \";\n\t\t\tconst nameText = isSelected ? theme.fg(\"accent\", providerDisplayName(p.name)) : providerDisplayName(p.name);\n\n\t\t\tconst parts = [prefix, nameText, \" \", authBadge];\n\t\t\tif (discoveryBadge) parts.push(\" \", discoveryBadge);\n\t\t\tparts.push(\" \", countBadge);\n\n\t\t\tthis.listContainer.addChild(new Text(parts.join(\"\"), 0, 0));\n\t\t}\n\n\t\tif (this.providers.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No providers configured\"), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tif (this.providers.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.providers.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} else if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tif (this.providers.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.providers.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} else if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.confirmingRemove) {\n\t\t\t\tthis.confirmingRemove = false;\n\t\t\t\tthis.updateHints();\n\t\t\t\tthis.tui.requestRender();\n\t\t\t} else {\n\t\t\t\tthis.onDone();\n\t\t\t}\n\t\t} else if (keyData === \"d\" || keyData === \"D\") {\n\t\t\tconst provider = this.providers[this.selectedIndex];\n\t\t\tif (provider?.supportsDiscovery) {\n\t\t\t\tthis.onDiscover(provider.name);\n\t\t\t}\n\t\t} else if (keyData === \"r\" || keyData === \"R\") {\n\t\t\tconst provider = this.providers[this.selectedIndex];\n\t\t\tif (provider?.hasAuth) {\n\t\t\t\tif (this.confirmingRemove) {\n\t\t\t\t\tthis.confirmingRemove = false;\n\t\t\t\t\tthis.authStorage.remove(provider.name);\n\t\t\t\t\tthis.modelsJsonWriter.removeProvider(provider.name);\n\t\t\t\t\tthis.modelRegistry.refresh();\n\t\t\t\t\tthis.loadProviders();\n\t\t\t\t\tthis.updateHints();\n\t\t\t\t\tthis.updateList();\n\t\t\t\t\tthis.tui.requestRender();\n\t\t\t\t} else {\n\t\t\t\t\tthis.confirmingRemove = true;\n\t\t\t\t\tthis.updateHints();\n\t\t\t\t\tthis.tui.requestRender();\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\t// Enter key → initiate auth setup for the selected provider (#3579)\n\t\t\tconst provider = this.providers[this.selectedIndex];\n\t\t\tif (provider) {\n\t\t\t\tthis.onSetupAuth(provider.name);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"provider-manager.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/provider-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TUI component for managing provider configurations.\n * Shows providers with auth status, discovery support, and model counts.\n */\n\nimport {\n\tContainer,\n\ttype Focusable,\n\tgetEditorKeybindings,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@gsd/pi-tui\";\nimport type { AuthStorage } from \"../../../core/auth-storage.js\";\nimport { getDiscoverableProviders, getDiscoveryAdapter } from \"../../../core/model-discovery.js\";\nimport { providerDisplayName } from \"./model-selector.js\";\nimport type { ModelRegistry } from \"../../../core/model-registry.js\";\nimport { ModelsJsonWriter } from \"../../../core/models-json-writer.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { rawKeyHint } from \"./keybinding-hints.js\";\n\ninterface ProviderInfo {\n\tname: string;\n\thasAuth: boolean;\n\tsupportsDiscovery: boolean;\n\tmodelCount: number;\n}\n\nexport class ProviderManagerComponent extends Container implements Focusable {\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t}\n\n\tprivate providers: ProviderInfo[] = [];\n\tprivate selectedIndex = 0;\n\tprivate listContainer: Container;\n\tprivate tui: TUI;\n\tprivate authStorage: AuthStorage;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate modelsJsonWriter: ModelsJsonWriter;\n\tprivate onDone: () => void;\n\tprivate onDiscover: (provider: string) => void;\n\tprivate onSetupAuth: (provider: string) => void;\n\tprivate confirmingRemove = false;\n\tprivate hintsContainer: Container;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tauthStorage: AuthStorage,\n\t\tmodelRegistry: ModelRegistry,\n\t\tonDone: () => void,\n\t\tonDiscover: (provider: string) => void,\n\t\tonSetupAuth?: (provider: string) => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.authStorage = authStorage;\n\t\tthis.modelRegistry = modelRegistry;\n\t\tthis.modelsJsonWriter = new ModelsJsonWriter(this.modelRegistry.modelsJsonPath);\n\t\tthis.onDone = onDone;\n\t\tthis.onDiscover = onDiscover;\n\t\tthis.onSetupAuth = onSetupAuth ?? (() => {});\n\n\t\t// Header\n\t\tthis.addChild(new Text(theme.fg(\"accent\", \"Provider Manager\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Hints\n\t\tthis.hintsContainer = new Container();\n\t\tthis.addChild(this.hintsContainer);\n\t\tthis.updateHints();\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// List\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\tthis.loadProviders();\n\t\tthis.updateList();\n\t}\n\n\tprivate loadProviders(): void {\n\t\tconst discoverableSet = new Set(getDiscoverableProviders());\n\t\tconst allModels = this.modelRegistry.getAll();\n\n\t\t// Group models by provider\n\t\tconst providerModelCounts = new Map<string, number>();\n\t\tfor (const model of allModels) {\n\t\t\tproviderModelCounts.set(model.provider, (providerModelCounts.get(model.provider) ?? 0) + 1);\n\t\t}\n\n\t\t// Build provider list from all known providers\n\t\tconst providerNames = new Set([\n\t\t\t...providerModelCounts.keys(),\n\t\t\t...discoverableSet,\n\t\t]);\n\n\t\tthis.providers = Array.from(providerNames)\n\t\t\t.sort()\n\t\t\t.map((name) => {\n\t\t\t\tconst providerApis = new Set(\n\t\t\t\t\tallModels\n\t\t\t\t\t\t.filter((m) => m.provider === name)\n\t\t\t\t\t\t.map((m) => m.api)\n\t\t\t\t\t\t.filter((api): api is string => typeof api === \"string\" && api.length > 0),\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tname,\n\t\t\t\t\thasAuth: this.authStorage.hasAuth(name),\n\t\t\t\t\tsupportsDiscovery:\n\t\t\t\t\t\tdiscoverableSet.has(name) || getDiscoveryAdapter(name, providerApis).supportsDiscovery,\n\t\t\t\t\tmodelCount: providerModelCounts.get(name) ?? 0,\n\t\t\t\t};\n\t\t\t});\n\t\tthis.clampSelectedIndex();\n\t}\n\n\tprivate clampSelectedIndex(): void {\n\t\tif (this.providers.length === 0) {\n\t\t\tthis.selectedIndex = 0;\n\t\t\treturn;\n\t\t}\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, this.providers.length - 1);\n\t}\n\n\tprivate updateHints(): void {\n\t\tthis.hintsContainer.clear();\n\t\tif (this.confirmingRemove) {\n\t\t\tconst hints = [\n\t\t\t\trawKeyHint(\"r\", \"confirm removal\"),\n\t\t\t\trawKeyHint(\"esc\", \"cancel\"),\n\t\t\t].join(\" \");\n\t\t\tthis.hintsContainer.addChild(new Text(hints, 0, 0));\n\t\t} else {\n\t\t\tconst hints = [\n\t\t\t\trawKeyHint(\"enter\", \"setup auth\"),\n\t\t\t\trawKeyHint(\"d\", \"discover\"),\n\t\t\t\trawKeyHint(\"r\", \"remove auth\"),\n\t\t\t\trawKeyHint(\"esc\", \"close\"),\n\t\t\t].join(\" \");\n\t\t\tthis.hintsContainer.addChild(new Text(hints, 0, 0));\n\t\t}\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tfor (let i = 0; i < this.providers.length; i++) {\n\t\t\tconst p = this.providers[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\tconst authBadge = p.hasAuth ? theme.fg(\"success\", \"[auth]\") : theme.fg(\"muted\", \"[no auth]\");\n\t\t\tconst discoveryBadge = p.supportsDiscovery ? theme.fg(\"accent\", \"[discovery]\") : \"\";\n\t\t\tconst countBadge = theme.fg(\"muted\", `(${p.modelCount} models)`);\n\n\t\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"> \") : \" \";\n\t\t\tconst nameText = isSelected ? theme.fg(\"accent\", providerDisplayName(p.name)) : providerDisplayName(p.name);\n\n\t\t\tconst parts = [prefix, nameText, \" \", authBadge];\n\t\t\tif (discoveryBadge) parts.push(\" \", discoveryBadge);\n\t\t\tparts.push(\" \", countBadge);\n\n\t\t\tthis.listContainer.addChild(new Text(parts.join(\"\"), 0, 0));\n\t\t}\n\n\t\tif (this.providers.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No providers configured\"), 0, 0));\n\t\t}\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tif (this.providers.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.providers.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} else if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tif (this.providers.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.providers.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t\tthis.tui.requestRender();\n\t\t} else if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.confirmingRemove) {\n\t\t\t\tthis.confirmingRemove = false;\n\t\t\t\tthis.updateHints();\n\t\t\t\tthis.tui.requestRender();\n\t\t\t} else {\n\t\t\t\tthis.onDone();\n\t\t\t}\n\t\t} else if (keyData === \"d\" || keyData === \"D\") {\n\t\t\tconst provider = this.providers[this.selectedIndex];\n\t\t\tif (provider?.supportsDiscovery) {\n\t\t\t\tthis.onDiscover(provider.name);\n\t\t\t}\n\t\t} else if (keyData === \"r\" || keyData === \"R\") {\n\t\t\tconst provider = this.providers[this.selectedIndex];\n\t\t\tif (provider?.hasAuth) {\n\t\t\t\tif (this.confirmingRemove) {\n\t\t\t\t\tthis.confirmingRemove = false;\n\t\t\t\t\tthis.authStorage.remove(provider.name);\n\t\t\t\t\tthis.modelsJsonWriter.removeProvider(provider.name);\n\t\t\t\t\tthis.modelRegistry.refresh();\n\t\t\t\t\tthis.loadProviders();\n\t\t\t\t\tthis.updateHints();\n\t\t\t\t\tthis.updateList();\n\t\t\t\t\tthis.tui.requestRender();\n\t\t\t\t} else {\n\t\t\t\t\tthis.confirmingRemove = true;\n\t\t\t\t\tthis.updateHints();\n\t\t\t\t\tthis.tui.requestRender();\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\t// Enter key → initiate auth setup for the selected provider (#3579)\n\t\t\tconst provider = this.providers[this.selectedIndex];\n\t\t\tif (provider) {\n\t\t\t\tthis.onSetupAuth(provider.name);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -1,17 +1,18 @@
1
- import { Box, type MarkdownTheme } from "@gsd/pi-tui";
1
+ import { Container, type MarkdownTheme } from "@gsd/pi-tui";
2
2
  import type { ParsedSkillBlock } from "../../../core/agent-session.js";
3
3
  /**
4
- * Component that renders a skill invocation message with collapsed/expanded state.
5
- * Uses same background color as custom messages for visual consistency.
6
- * Only renders the skill block itself - user message is rendered separately.
4
+ * Renders a skill invocation in the shared chat-frame style (top rule,
5
+ * `• skill - <name>` header, `│ ` body margin) with purple border/label
6
+ * matching compaction so it visually aligns with user/assistant messages.
7
7
  */
8
- export declare class SkillInvocationMessageComponent extends Box {
8
+ export declare class SkillInvocationMessageComponent extends Container {
9
9
  private expanded;
10
10
  private skillBlock;
11
11
  private markdownTheme;
12
12
  constructor(skillBlock: ParsedSkillBlock, markdownTheme?: MarkdownTheme);
13
13
  setExpanded(expanded: boolean): void;
14
14
  invalidate(): void;
15
- private updateDisplay;
15
+ private rebuild;
16
+ render(width: number): string[];
16
17
  }
17
18
  //# sourceMappingURL=skill-invocation-message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"skill-invocation-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAQ,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAIvE;;;;GAIG;AACH,qBAAa,+BAAgC,SAAQ,GAAG;IACvD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,UAAU,EAAE,gBAAgB,EAAE,aAAa,GAAE,aAAkC;IAO3F,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAK3B,UAAU,IAAI,IAAI;IAK3B,OAAO,CAAC,aAAa;CAsBrB"}
1
+ {"version":3,"file":"skill-invocation-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAY,KAAK,aAAa,EAAQ,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAKvE;;;;GAIG;AACH,qBAAa,+BAAgC,SAAQ,SAAS;IAC7D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,UAAU,EAAE,gBAAgB,EAAE,aAAa,GAAE,aAAkC;IAO3F,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAO3B,UAAU,IAAI,IAAI;IAK3B,OAAO,CAAC,OAAO;IAoBN,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAYxC"}
@@ -1,45 +1,53 @@
1
- import { Box, Markdown, Text } from "@gsd/pi-tui";
1
+ // GSD / pi-coding-agent Skill invocation message component
2
+ import { Container, Markdown, Text } from "@gsd/pi-tui";
2
3
  import { getMarkdownTheme, theme } from "../theme/theme.js";
4
+ import { renderChatFrame } from "./chat-frame.js";
3
5
  import { editorKey } from "./keybinding-hints.js";
4
6
  /**
5
- * Component that renders a skill invocation message with collapsed/expanded state.
6
- * Uses same background color as custom messages for visual consistency.
7
- * Only renders the skill block itself - user message is rendered separately.
7
+ * Renders a skill invocation in the shared chat-frame style (top rule,
8
+ * `• skill - <name>` header, `│ ` body margin) with purple border/label
9
+ * matching compaction so it visually aligns with user/assistant messages.
8
10
  */
9
- export class SkillInvocationMessageComponent extends Box {
11
+ export class SkillInvocationMessageComponent extends Container {
10
12
  constructor(skillBlock, markdownTheme = getMarkdownTheme()) {
11
- super(1, 1, (t) => theme.bg("customMessageBg", t));
13
+ super();
12
14
  this.expanded = false;
13
15
  this.skillBlock = skillBlock;
14
16
  this.markdownTheme = markdownTheme;
15
- this.updateDisplay();
17
+ this.rebuild();
16
18
  }
17
19
  setExpanded(expanded) {
18
- this.expanded = expanded;
19
- this.updateDisplay();
20
+ if (this.expanded !== expanded) {
21
+ this.expanded = expanded;
22
+ this.rebuild();
23
+ }
20
24
  }
21
25
  invalidate() {
22
26
  super.invalidate();
23
- this.updateDisplay();
27
+ this.rebuild();
24
28
  }
25
- updateDisplay() {
29
+ rebuild() {
26
30
  this.clear();
27
31
  if (this.expanded) {
28
- // Expanded: label + skill name header + full content
29
- const label = theme.fg("customMessageLabel", theme.bold("[skill]"));
30
- this.addChild(new Text(label, 0, 0));
31
- const header = `**${this.skillBlock.name}**\n\n`;
32
- this.addChild(new Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {
32
+ this.addChild(new Markdown(this.skillBlock.content, 0, 0, this.markdownTheme, {
33
33
  color: (text) => theme.fg("customMessageText", text),
34
34
  }));
35
35
  }
36
36
  else {
37
- // Collapsed: single line - [skill] name (hint to expand)
38
- const line = theme.fg("customMessageLabel", theme.bold("[skill]") + " ") +
39
- theme.fg("customMessageText", this.skillBlock.name) +
40
- theme.fg("dim", ` (${editorKey("expandTools")} to expand)`);
41
- this.addChild(new Text(line, 0, 0));
37
+ this.addChild(new Text(theme.fg("dim", `(${editorKey("expandTools")} to expand)`), 0, 0));
42
38
  }
43
39
  }
40
+ render(width) {
41
+ const frameWidth = Math.max(20, width);
42
+ const contentWidth = Math.max(1, frameWidth - 4);
43
+ const lines = super.render(contentWidth);
44
+ const framed = renderChatFrame(lines, frameWidth, {
45
+ label: `skill - ${this.skillBlock.name}`,
46
+ tone: "skill",
47
+ timestampFormat: "date-time-iso",
48
+ showTimestamp: false,
49
+ });
50
+ return framed.length > 0 ? ["", ...framed] : framed;
51
+ }
44
52
  }
45
53
  //# sourceMappingURL=skill-invocation-message.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"skill-invocation-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,IAAI,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;GAIG;AACH,MAAM,OAAO,+BAAgC,SAAQ,GAAG;IAKvD,YAAY,UAA4B,EAAE,gBAA+B,gBAAgB,EAAE;QAC1F,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QAL5C,aAAQ,GAAG,KAAK,CAAC;QAMxB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAED,WAAW,CAAC,QAAiB;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEQ,UAAU;QAClB,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEO,aAAa;QACpB,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,qDAAqD;YACrD,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;YACpE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,MAAM,GAAG,KAAK,IAAI,CAAC,UAAU,CAAC,IAAI,QAAQ,CAAC;YACjD,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACxE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,yDAAyD;YACzD,MAAM,IAAI,GACT,KAAK,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;gBAC3D,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBACnD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,SAAS,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;YAC7D,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;IACF,CAAC;CACD","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Text } from \"@gsd/pi-tui\";\nimport type { ParsedSkillBlock } from \"../../../core/agent-session.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { editorKey } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a skill invocation message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n * Only renders the skill block itself - user message is rendered separately.\n */\nexport class SkillInvocationMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate skillBlock: ParsedSkillBlock;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.skillBlock = skillBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tif (this.expanded) {\n\t\t\t// Expanded: label + skill name header + full content\n\t\t\tconst label = theme.fg(\"customMessageLabel\", theme.bold(\"[skill]\"));\n\t\t\tthis.addChild(new Text(label, 0, 0));\n\t\t\tconst header = `**${this.skillBlock.name}**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\t// Collapsed: single line - [skill] name (hint to expand)\n\t\t\tconst line =\n\t\t\t\ttheme.fg(\"customMessageLabel\", theme.bold(\"[skill]\") + \" \") +\n\t\t\t\ttheme.fg(\"customMessageText\", this.skillBlock.name) +\n\t\t\t\ttheme.fg(\"dim\", ` (${editorKey(\"expandTools\")} to expand)`);\n\t\t\tthis.addChild(new Text(line, 0, 0));\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"skill-invocation-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/skill-invocation-message.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAsB,IAAI,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;GAIG;AACH,MAAM,OAAO,+BAAgC,SAAQ,SAAS;IAK7D,YAAY,UAA4B,EAAE,gBAA+B,gBAAgB,EAAE;QAC1F,KAAK,EAAE,CAAC;QALD,aAAQ,GAAG,KAAK,CAAC;QAMxB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAED,WAAW,CAAC,QAAiB;QAC5B,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACzB,IAAI,CAAC,OAAO,EAAE,CAAC;QAChB,CAAC;IACF,CAAC;IAEQ,UAAU;QAClB,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,OAAO;QACd,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBAC/D,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,SAAS,CAAC,aAAa,CAAC,aAAa,CAAC,EAC1D,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IACF,CAAC;IAEQ,MAAM,CAAC,KAAa;QAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE;YACjD,KAAK,EAAE,WAAW,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;YACxC,IAAI,EAAE,OAAO;YACb,eAAe,EAAE,eAAe;YAChC,aAAa,EAAE,KAAK;SACpB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACrD,CAAC;CACD","sourcesContent":["// GSD / pi-coding-agent — Skill invocation message component\nimport { Container, Markdown, type MarkdownTheme, Text } from \"@gsd/pi-tui\";\nimport type { ParsedSkillBlock } from \"../../../core/agent-session.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { renderChatFrame } from \"./chat-frame.js\";\nimport { editorKey } from \"./keybinding-hints.js\";\n\n/**\n * Renders a skill invocation in the shared chat-frame style (top rule,\n * `• skill - <name>` header, `│ ` body margin) with purple border/label\n * matching compaction so it visually aligns with user/assistant messages.\n */\nexport class SkillInvocationMessageComponent extends Container {\n\tprivate expanded = false;\n\tprivate skillBlock: ParsedSkillBlock;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper();\n\t\tthis.skillBlock = skillBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.rebuild();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tif (this.expanded !== expanded) {\n\t\t\tthis.expanded = expanded;\n\t\t\tthis.rebuild();\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.rebuild();\n\t}\n\n\tprivate rebuild(): void {\n\t\tthis.clear();\n\n\t\tif (this.expanded) {\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(this.skillBlock.content, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"dim\", `(${editorKey(\"expandTools\")} to expand)`),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst frameWidth = Math.max(20, width);\n\t\tconst contentWidth = Math.max(1, frameWidth - 4);\n\t\tconst lines = super.render(contentWidth);\n\t\tconst framed = renderChatFrame(lines, frameWidth, {\n\t\t\tlabel: `skill - ${this.skillBlock.name}`,\n\t\t\ttone: \"skill\",\n\t\t\ttimestampFormat: \"date-time-iso\",\n\t\t\tshowTimestamp: false,\n\t\t});\n\t\treturn framed.length > 0 ? [\"\", ...framed] : framed;\n\t}\n}\n"]}
@@ -5,6 +5,7 @@ import {
5
5
  getDefaultTTL,
6
6
  getDiscoverableProviders,
7
7
  getDiscoveryAdapter,
8
+ supportsDiscoveryForApi,
8
9
  } from "./model-discovery.js";
9
10
 
10
11
  // ─── getDiscoveryAdapter ─────────────────────────────────────────────────────
@@ -52,6 +53,12 @@ describe("getDiscoveryAdapter", () => {
52
53
  assert.equal(adapter.supportsDiscovery, false);
53
54
  });
54
55
 
56
+ it("returns OpenAI-style adapter for unknown provider with OpenAI-compatible API", () => {
57
+ const adapter = getDiscoveryAdapter("my-proxy", ["openai-completions"]);
58
+ assert.equal(adapter.provider, "my-proxy");
59
+ assert.equal(adapter.supportsDiscovery, true);
60
+ });
61
+
55
62
  it("static adapter fetchModels returns empty array", async () => {
56
63
  const adapter = getDiscoveryAdapter("anthropic");
57
64
  const models = await adapter.fetchModels("key");
@@ -123,3 +130,15 @@ describe("DISCOVERY_TTLS", () => {
123
130
  }
124
131
  });
125
132
  });
133
+
134
+ describe("supportsDiscoveryForApi", () => {
135
+ it("returns true for OpenAI-compatible APIs", () => {
136
+ assert.equal(supportsDiscoveryForApi("openai-completions"), true);
137
+ assert.equal(supportsDiscoveryForApi("openai-responses"), true);
138
+ });
139
+
140
+ it("returns false for non-discoverable APIs", () => {
141
+ assert.equal(supportsDiscoveryForApi("anthropic-messages"), false);
142
+ assert.equal(supportsDiscoveryForApi(undefined), false);
143
+ });
144
+ });
@@ -26,6 +26,14 @@ export interface ProviderDiscoveryAdapter {
26
26
  fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]>;
27
27
  }
28
28
 
29
+ export const OPENAI_COMPAT_DISCOVERY_APIS = new Set([
30
+ "openai",
31
+ "openai-completions",
32
+ "openai-responses",
33
+ "openai-codex-responses",
34
+ "azure-openai-responses",
35
+ ]);
36
+
29
37
  /** Per-provider TTLs in milliseconds */
30
38
  export const DISCOVERY_TTLS: Record<string, number> = {
31
39
  ollama: 5 * 60 * 1000, // 5 minutes (local, models change often)
@@ -53,10 +61,77 @@ async function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutM
53
61
 
54
62
  const OPENAI_EXCLUDED_PREFIXES = ["embedding", "tts", "dall-e", "whisper", "text-embedding", "davinci", "babbage"];
55
63
 
64
+ function asPositiveNumber(value: unknown): number | undefined {
65
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
66
+ if (typeof value === "string") {
67
+ const n = Number.parseFloat(value);
68
+ if (Number.isFinite(n) && n > 0) return n;
69
+ }
70
+ return undefined;
71
+ }
72
+
73
+ function pickFirstPositiveNumber(record: Record<string, unknown>, keys: string[]): number | undefined {
74
+ for (const key of keys) {
75
+ const value = asPositiveNumber(record[key]);
76
+ if (value !== undefined) return value;
77
+ }
78
+ return undefined;
79
+ }
80
+
81
+ function discoverInputModalities(rawModel: Record<string, unknown>, id: string): Array<"text" | "image"> {
82
+ const directModalities = rawModel.input_modalities;
83
+ const capabilitiesModalities = (rawModel.capabilities as Record<string, unknown> | undefined)?.input_modalities;
84
+ const source = Array.isArray(directModalities)
85
+ ? directModalities
86
+ : Array.isArray(capabilitiesModalities)
87
+ ? capabilitiesModalities
88
+ : [];
89
+ const supportsImage = source.some((m) => typeof m === "string" && /image|vision/i.test(m))
90
+ || /vision|image|omni|multimodal/i.test(id);
91
+ return supportsImage ? ["text", "image"] : ["text"];
92
+ }
93
+
94
+ function parseOpenAICompatibleModel(rawModel: Record<string, unknown>): DiscoveredModel | undefined {
95
+ const id = typeof rawModel.id === "string" ? rawModel.id : "";
96
+ if (!id) return undefined;
97
+ if (OPENAI_EXCLUDED_PREFIXES.some((prefix) => id.startsWith(prefix))) return undefined;
98
+
99
+ const contextWindow = pickFirstPositiveNumber(rawModel, [
100
+ "context_window",
101
+ "context_length",
102
+ "max_context_length",
103
+ "max_input_tokens",
104
+ "input_token_limit",
105
+ "max_model_len",
106
+ ]);
107
+ const maxTokens = pickFirstPositiveNumber(rawModel, [
108
+ "max_output_tokens",
109
+ "output_token_limit",
110
+ "max_completion_tokens",
111
+ "max_tokens",
112
+ ]);
113
+ const reasoning = rawModel.reasoning === true
114
+ || rawModel.supports_reasoning === true
115
+ || ((rawModel.capabilities as Record<string, unknown> | undefined)?.reasoning === true);
116
+
117
+ return {
118
+ id,
119
+ name: typeof rawModel.name === "string" && rawModel.name.length > 0 ? rawModel.name : id,
120
+ contextWindow,
121
+ maxTokens,
122
+ reasoning,
123
+ input: discoverInputModalities(rawModel, id),
124
+ };
125
+ }
126
+
56
127
  class OpenAIDiscoveryAdapter implements ProviderDiscoveryAdapter {
57
- provider = "openai";
128
+ provider: string;
58
129
  supportsDiscovery = true;
59
130
 
131
+ constructor(provider: string) {
132
+ this.provider = provider;
133
+ }
134
+
60
135
  async fetchModels(apiKey: string, baseUrl?: string): Promise<DiscoveredModel[]> {
61
136
  const url = `${baseUrl ?? "https://api.openai.com"}/v1/models`;
62
137
  const response = await fetchWithTimeout(url, {
@@ -67,14 +142,10 @@ class OpenAIDiscoveryAdapter implements ProviderDiscoveryAdapter {
67
142
  throw new Error(`OpenAI models API returned ${response.status}: ${response.statusText}`);
68
143
  }
69
144
 
70
- const data = (await response.json()) as { data: Array<{ id: string; owned_by?: string }> };
71
- return data.data
72
- .filter((m) => !OPENAI_EXCLUDED_PREFIXES.some((prefix) => m.id.startsWith(prefix)))
73
- .map((m) => ({
74
- id: m.id,
75
- name: m.id,
76
- input: ["text" as const, "image" as const],
77
- }));
145
+ const data = (await response.json()) as { data?: Array<Record<string, unknown>> };
146
+ return (data.data ?? [])
147
+ .map((m) => parseOpenAICompatibleModel(m))
148
+ .filter((m): m is DiscoveredModel => !!m);
78
149
  }
79
150
  }
80
151
 
@@ -207,7 +278,7 @@ class StaticDiscoveryAdapter implements ProviderDiscoveryAdapter {
207
278
  // ─── Registry ────────────────────────────────────────────────────────────────
208
279
 
209
280
  const adapters: Record<string, ProviderDiscoveryAdapter> = {
210
- openai: new OpenAIDiscoveryAdapter(),
281
+ openai: new OpenAIDiscoveryAdapter("openai"),
211
282
  ollama: new OllamaDiscoveryAdapter(),
212
283
  openrouter: new OpenRouterDiscoveryAdapter(),
213
284
  google: new GoogleDiscoveryAdapter(),
@@ -220,8 +291,24 @@ const adapters: Record<string, ProviderDiscoveryAdapter> = {
220
291
  mistral: new StaticDiscoveryAdapter("mistral"),
221
292
  };
222
293
 
223
- export function getDiscoveryAdapter(provider: string): ProviderDiscoveryAdapter {
224
- return adapters[provider] ?? new StaticDiscoveryAdapter(provider);
294
+ export function supportsDiscoveryForApi(api: string | undefined): boolean {
295
+ if (!api) return false;
296
+ return OPENAI_COMPAT_DISCOVERY_APIS.has(api);
297
+ }
298
+
299
+ export function getDiscoveryAdapter(provider: string, providerApis?: Iterable<string>): ProviderDiscoveryAdapter {
300
+ const known = adapters[provider];
301
+ if (known) return known;
302
+
303
+ if (providerApis) {
304
+ for (const api of providerApis) {
305
+ if (supportsDiscoveryForApi(api)) {
306
+ return new OpenAIDiscoveryAdapter(provider);
307
+ }
308
+ }
309
+ }
310
+
311
+ return new StaticDiscoveryAdapter(provider);
225
312
  }
226
313
 
227
314
  export function getDiscoverableProviders(): string[] {
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, it } from "node:test";
6
6
  import { AuthStorage } from "./auth-storage.js";
7
7
  import { ModelDiscoveryCache } from "./discovery-cache.js";
8
8
  import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter } from "./model-discovery.js";
9
+ import { ModelRegistry } from "./model-registry.js";
9
10
 
10
11
  let testDir: string;
11
12
 
@@ -133,3 +134,77 @@ describe("Discovery TTL configuration", () => {
133
134
  assert.equal(customTTL, defaultTTL);
134
135
  });
135
136
  });
137
+
138
+ describe("ModelRegistry discovery — OpenAI-compatible custom providers", () => {
139
+ it("discovers custom OpenAI-compatible providers and maps capability metadata", async () => {
140
+ const providerName = `minimax-openai-${Date.now()}-${Math.random().toString(36).slice(2)}`;
141
+ const modelsPath = join(testDir, "models.json");
142
+ writeFileSync(
143
+ modelsPath,
144
+ JSON.stringify(
145
+ {
146
+ providers: {
147
+ [providerName]: {
148
+ baseUrl: "https://api.minimax.example",
149
+ apiKey: "minimax-test-key",
150
+ api: "openai-completions",
151
+ models: [{ id: "bootstrap-model" }],
152
+ },
153
+ },
154
+ },
155
+ null,
156
+ 2,
157
+ ),
158
+ "utf-8",
159
+ );
160
+
161
+ const prevFetch = globalThis.fetch;
162
+ let requestedUrl = "";
163
+ globalThis.fetch = (async (input: string | URL | Request) => {
164
+ requestedUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
165
+ return new Response(
166
+ JSON.stringify({
167
+ data: [
168
+ {
169
+ id: "MiniMax-M2.7-highspeed",
170
+ name: "MiniMax M2.7 Highspeed",
171
+ context_window: 165000,
172
+ max_output_tokens: 32768,
173
+ supports_reasoning: true,
174
+ input_modalities: ["text", "image"],
175
+ },
176
+ ],
177
+ }),
178
+ {
179
+ status: 200,
180
+ headers: { "content-type": "application/json" },
181
+ },
182
+ );
183
+ }) as typeof globalThis.fetch;
184
+
185
+ try {
186
+ const registry = new ModelRegistry(AuthStorage.inMemory({}), modelsPath);
187
+ // Guard against global cache leakage from prior test runs.
188
+ registry.getDiscoveryCache().clear(providerName);
189
+ const results = await registry.discoverModels([providerName]);
190
+
191
+ const discovery = results.find((r) => r.provider === providerName);
192
+ assert.ok(discovery, "discovery result should include custom provider");
193
+ assert.equal(discovery?.error, undefined, "custom provider discovery should succeed");
194
+ assert.equal(requestedUrl, "https://api.minimax.example/v1/models");
195
+
196
+ const discovered = registry
197
+ .getAllWithDiscovered()
198
+ .find((m) => m.provider === providerName && m.id === "MiniMax-M2.7-highspeed");
199
+ assert.ok(discovered, "discovered model should be merged into model list");
200
+ assert.equal(discovered?.api, "openai-completions");
201
+ assert.equal(discovered?.baseUrl, "https://api.minimax.example");
202
+ assert.equal(discovered?.contextWindow, 165000);
203
+ assert.equal(discovered?.maxTokens, 32768);
204
+ assert.equal(discovered?.reasoning, true);
205
+ assert.deepEqual(discovered?.input, ["text", "image"]);
206
+ } finally {
207
+ globalThis.fetch = prevFetch;
208
+ }
209
+ });
210
+ });
@@ -29,7 +29,7 @@ import { getAgentDir } from "../config.js";
29
29
  import type { AuthStorage } from "./auth-storage.js";
30
30
  import { ModelDiscoveryCache } from "./discovery-cache.js";
31
31
  import type { DiscoveredModel, DiscoveryResult } from "./model-discovery.js";
32
- import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter } from "./model-discovery.js";
32
+ import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter, supportsDiscoveryForApi } from "./model-discovery.js";
33
33
  import { clearConfigValueCache, resolveConfigValue, resolveHeaders } from "./resolve-config-value.js";
34
34
  import { isLocalModel } from "./local-model-check.js";
35
35
 
@@ -788,11 +788,12 @@ export class ModelRegistry {
788
788
  * Results are cached and merged into the registry (never overrides existing models).
789
789
  */
790
790
  async discoverModels(providers?: string[]): Promise<DiscoveryResult[]> {
791
- const targetProviders = providers ?? getDiscoverableProviders();
791
+ const targetProviders = providers ?? this.getAutoDiscoverableProviders();
792
792
  const results: DiscoveryResult[] = [];
793
793
 
794
794
  for (const providerName of targetProviders) {
795
- const adapter = getDiscoveryAdapter(providerName);
795
+ const providerApis = this.getProviderApis(providerName);
796
+ const adapter = getDiscoveryAdapter(providerName, providerApis);
796
797
  if (!adapter.supportsDiscovery) continue;
797
798
 
798
799
  // Skip if cache is still fresh
@@ -812,8 +813,10 @@ export class ModelRegistry {
812
813
  const apiKey = await this.authStorage.getApiKey(providerName);
813
814
  if (!apiKey && !this.isProviderRequestReady(providerName)) continue;
814
815
 
815
- const models = await adapter.fetchModels(apiKey ?? "", undefined);
816
- this.discoveryCache.set(providerName, models);
816
+ const baseUrl = this.getProviderBaseUrl(providerName);
817
+ const models = await adapter.fetchModels(apiKey ?? "", baseUrl);
818
+ const ttlMs = this.getDiscoveryTtl(providerName, providerApis);
819
+ this.discoveryCache.set(providerName, models, ttlMs);
817
820
  results.push({
818
821
  provider: providerName,
819
822
  models,
@@ -865,24 +868,97 @@ export class ModelRegistry {
865
868
  const converted: Model<Api>[] = [];
866
869
  for (const result of results) {
867
870
  if (result.error) continue;
871
+ const providerDefaults = this.getDiscoveryProviderDefaults(result.provider);
868
872
  for (const dm of result.models) {
869
873
  converted.push({
870
874
  id: dm.id,
871
875
  name: dm.name ?? dm.id,
872
- api: "openai" as Api,
876
+ api: providerDefaults.api,
873
877
  provider: result.provider,
874
- baseUrl: "",
878
+ baseUrl: providerDefaults.baseUrl,
875
879
  reasoning: dm.reasoning ?? false,
876
- input: dm.input ?? ["text"],
880
+ input: dm.input ?? providerDefaults.input,
877
881
  cost: dm.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
878
- contextWindow: dm.contextWindow ?? 128000,
879
- maxTokens: dm.maxTokens ?? 16384,
882
+ contextWindow: dm.contextWindow ?? providerDefaults.contextWindow,
883
+ maxTokens: dm.maxTokens ?? providerDefaults.maxTokens,
880
884
  } as Model<Api>);
881
885
  }
882
886
  }
883
887
  return converted;
884
888
  }
885
889
 
890
+ private getProviderApis(provider: string): Set<string> {
891
+ const apis = new Set<string>();
892
+ for (const model of this.models) {
893
+ if (model.provider === provider && typeof model.api === "string" && model.api.length > 0) {
894
+ apis.add(model.api);
895
+ }
896
+ }
897
+
898
+ const providerConfig = this.registeredProviders.get(provider);
899
+ if (providerConfig?.api) apis.add(providerConfig.api);
900
+ for (const modelDef of providerConfig?.models ?? []) {
901
+ if (modelDef.api) apis.add(modelDef.api);
902
+ }
903
+ return apis;
904
+ }
905
+
906
+ private getAutoDiscoverableProviders(): string[] {
907
+ const discoverable = new Set<string>(getDiscoverableProviders());
908
+ for (const provider of new Set(this.models.map((m) => m.provider))) {
909
+ const apis = this.getProviderApis(provider);
910
+ for (const api of apis) {
911
+ if (supportsDiscoveryForApi(api)) {
912
+ discoverable.add(provider);
913
+ break;
914
+ }
915
+ }
916
+ }
917
+ return [...discoverable];
918
+ }
919
+
920
+ private getProviderBaseUrl(provider: string): string | undefined {
921
+ const fromModels = this.models.find((m) => m.provider === provider && typeof m.baseUrl === "string" && m.baseUrl.length > 0);
922
+ if (fromModels?.baseUrl) return fromModels.baseUrl;
923
+ return this.registeredProviders.get(provider)?.baseUrl;
924
+ }
925
+
926
+ private getDiscoveryProviderDefaults(provider: string): {
927
+ api: Api;
928
+ baseUrl: string;
929
+ input: ("text" | "image")[];
930
+ contextWindow: number;
931
+ maxTokens: number;
932
+ } {
933
+ const first = this.models.find((m) => m.provider === provider);
934
+ if (first) {
935
+ return {
936
+ api: first.api,
937
+ baseUrl: first.baseUrl,
938
+ input: first.input,
939
+ contextWindow: first.contextWindow,
940
+ maxTokens: first.maxTokens,
941
+ };
942
+ }
943
+
944
+ return {
945
+ api: "openai-completions",
946
+ baseUrl: this.registeredProviders.get(provider)?.baseUrl ?? "",
947
+ input: ["text"],
948
+ contextWindow: 128000,
949
+ maxTokens: 16384,
950
+ };
951
+ }
952
+
953
+ private getDiscoveryTtl(provider: string, providerApis: Set<string>): number {
954
+ for (const api of providerApis) {
955
+ if (supportsDiscoveryForApi(api)) {
956
+ return getDefaultTTL("openai");
957
+ }
958
+ }
959
+ return getDefaultTTL(provider);
960
+ }
961
+
886
962
  /**
887
963
  * Check if a model's baseUrl points to a local endpoint.
888
964
  * Delegates to standalone isLocalModel() function.