@xelauvas/xela-cli 0.3.0 → 0.4.0

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 (136) hide show
  1. package/local/README.md +112 -0
  2. package/local/start-qwen.sh +47 -0
  3. package/local/start-tunnel.sh +36 -0
  4. package/local/xela-local.sh +139 -0
  5. package/package.json +19 -5
  6. package/run.sh +33 -0
  7. package/src/Tool.ts +1 -1
  8. package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
  9. package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
  10. package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
  11. package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
  12. package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
  13. package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
  14. package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
  15. package/src/_shims/bun_bundle.js +79 -4
  16. package/src/_shims/cjs_stub.cjs +32 -10
  17. package/src/_shims/loader.js +104 -5
  18. package/src/_shims/openai_adapter.ts +446 -230
  19. package/src/_shims/register.js +16 -3
  20. package/src/assistant/gate.ts +15 -0
  21. package/src/assistant/index.ts +89 -0
  22. package/src/bootstrap/state.ts +6 -1
  23. package/src/bridge/peerSessions.ts +18 -0
  24. package/src/cli/print.ts +5 -0
  25. package/src/commands/assistant/assistant.ts +17 -0
  26. package/src/commands/assistant/index.ts +11 -0
  27. package/src/commands/brief.ts +3 -1
  28. package/src/commands/buddy/buddy.ts +88 -0
  29. package/src/commands/buddy/index.ts +12 -0
  30. package/src/commands/model/model.tsx +8 -23
  31. package/src/commands/peers/index.ts +11 -0
  32. package/src/commands/peers/peers.ts +24 -0
  33. package/src/commands/proactive/proactive.ts +64 -0
  34. package/src/commands/proactive.ts +12 -0
  35. package/src/commands/workflows/index.ts +12 -0
  36. package/src/commands/workflows/workflows.ts +36 -0
  37. package/src/commands.ts +4 -8
  38. package/src/components/Messages.tsx +1 -1
  39. package/src/components/ModelPicker.tsx +4 -34
  40. package/src/components/Spinner.tsx +54 -1
  41. package/src/components/StructuredDiff/colorDiff.ts +3 -1
  42. package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
  43. package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
  44. package/src/components/permissions/PermissionRequest.tsx +4 -2
  45. package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
  46. package/src/constants/prompts.ts +26 -0
  47. package/src/ink/terminal.ts +9 -1
  48. package/src/jobs/classifier.ts +103 -0
  49. package/src/memdir/paths.ts +13 -1
  50. package/src/proactive/index.ts +158 -0
  51. package/src/proactive/useProactive.ts +78 -0
  52. package/src/screens/REPL.tsx +28 -8
  53. package/src/services/api/claude.ts +6 -1
  54. package/src/services/compact/autoCompact.ts +18 -4
  55. package/src/services/compact/cachedMCConfig.ts +1 -0
  56. package/src/services/compact/cachedMicrocompact.ts +113 -0
  57. package/src/services/compact/compact.ts +22 -2
  58. package/src/services/compact/reactiveCompact.ts +251 -0
  59. package/src/services/compact/snipCompact.ts +282 -0
  60. package/src/services/compact/snipProjection.ts +50 -0
  61. package/src/services/contextCollapse/index.ts +322 -0
  62. package/src/services/contextCollapse/operations.ts +187 -0
  63. package/src/services/contextCollapse/persist.ts +40 -0
  64. package/src/services/extractMemories/extractMemories.ts +11 -2
  65. package/src/services/skillSearch/featureCheck.ts +12 -0
  66. package/src/services/skillSearch/localSearch.ts +98 -0
  67. package/src/services/skillSearch/prefetch.ts +125 -0
  68. package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
  69. package/src/services/skillSearch/remoteSkillState.ts +21 -0
  70. package/src/services/skillSearch/signals.ts +7 -0
  71. package/src/services/skillSearch/telemetry.ts +9 -0
  72. package/src/setup.ts +10 -5
  73. package/src/skills/bundled/dream.ts +31 -0
  74. package/src/skills/bundled/hunter.ts +45 -0
  75. package/src/skills/bundled/index.ts +10 -23
  76. package/src/skills/bundled/runSkillGenerator.ts +61 -0
  77. package/src/skills/mcpSkills.ts +101 -0
  78. package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
  79. package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
  80. package/src/tasks.ts +22 -8
  81. package/src/tools/AgentTool/runAgent.ts +11 -0
  82. package/src/tools/BriefTool/BriefTool.ts +2 -0
  83. package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
  84. package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
  85. package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
  86. package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
  87. package/src/tools/MCPTool/MCPTool.ts +6 -0
  88. package/src/tools/MonitorTool/MonitorTool.ts +273 -0
  89. package/src/tools/MonitorTool/UI.tsx +25 -0
  90. package/src/tools/MonitorTool/events.ts +84 -0
  91. package/src/tools/MonitorTool/prompt.ts +45 -0
  92. package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
  93. package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
  94. package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
  95. package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
  96. package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
  97. package/src/tools/SendUserFileTool/prompt.ts +7 -0
  98. package/src/tools/SleepTool/SleepTool.ts +126 -0
  99. package/src/tools/SnipTool/SnipTool.ts +90 -0
  100. package/src/tools/SnipTool/prompt.ts +5 -0
  101. package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
  102. package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
  103. package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
  104. package/src/tools/ToolSearchTool/prompt.ts +1 -1
  105. package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
  106. package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
  107. package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
  108. package/src/tools/WebBrowserTool/prompt.ts +16 -0
  109. package/src/tools/WorkflowTool/UI.tsx +27 -0
  110. package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
  111. package/src/tools/WorkflowTool/bundled/index.ts +88 -0
  112. package/src/tools/WorkflowTool/constants.ts +7 -0
  113. package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
  114. package/src/tools/WorkflowTool/prompt.ts +38 -0
  115. package/src/tools/WorkflowTool/runtime.ts +331 -0
  116. package/src/tools.ts +36 -31
  117. package/src/utils/betas.ts +5 -0
  118. package/src/utils/context.ts +9 -0
  119. package/src/utils/conversationRecovery.ts +1 -1
  120. package/src/utils/forkedAgent.ts +4 -0
  121. package/src/utils/logoV2Utils.ts +7 -3
  122. package/src/utils/messageQueueManager.ts +23 -9
  123. package/src/utils/model/model.ts +10 -9
  124. package/src/utils/model/modelOptions.ts +0 -21
  125. package/src/utils/model/validateModel.ts +0 -19
  126. package/src/utils/permissions/permissionSetup.ts +5 -0
  127. package/src/utils/permissions/permissions.ts +25 -0
  128. package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
  129. package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
  130. package/src/utils/permissions/yoloClassifier.ts +28 -6
  131. package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
  132. package/src/utils/systemThemeWatcher.ts +62 -0
  133. package/src/utils/udsClient.ts +115 -0
  134. package/src/utils/udsMessaging.ts +157 -0
  135. package/src/utils/yaml.ts +251 -5
  136. package/src/utils/model/providerModels.ts +0 -168
package/src/utils/yaml.ts CHANGED
@@ -1,15 +1,261 @@
1
1
  /**
2
2
  * YAML parsing wrapper.
3
3
  *
4
- * Uses Bun.YAML (built-in, zero-cost) when running under Bun, otherwise falls
5
- * back to the `yaml` npm package. The package is lazy-required inside the
6
- * non-Bun branch so native Bun builds never load the ~270KB yaml parser.
4
+ * Uses Bun.YAML when running under Bun, the `yaml` npm package when it is
5
+ * installed, and otherwise a built-in parser for the YAML subset that skill
6
+ * frontmatter and plugin manifests use (block mappings and sequences, flow
7
+ * arrays, quoted/plain scalars, `|` and `>` block scalars, comments). Before
8
+ * this fallback existed a missing package silently produced empty frontmatter,
9
+ * so every skill lost its description.
7
10
  */
8
11
 
9
12
  export function parseYaml(input: string): unknown {
10
13
  if (typeof Bun !== 'undefined') {
11
14
  return Bun.YAML.parse(input)
12
15
  }
13
- // eslint-disable-next-line @typescript-eslint/no-require-imports
14
- return (require('yaml') as typeof import('yaml')).parse(input)
16
+ try {
17
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
18
+ const mod = require('yaml') as { parse?: (s: string) => unknown }
19
+ if (mod && typeof mod.parse === 'function') {
20
+ const out = mod.parse(input)
21
+ // The missing-module stub returns its argument unchanged
22
+ if (out !== input) return out
23
+ }
24
+ } catch {
25
+ // fall through to the built-in parser
26
+ }
27
+ return parseYamlSubset(input)
28
+ }
29
+
30
+ type Line = { indent: number; text: string; raw: string }
31
+
32
+ function splitLines(input: string): Line[] {
33
+ const lines: Line[] = []
34
+ for (const raw of input.replace(/\r\n?/g, '\n').split('\n')) {
35
+ const text = stripComment(raw).replace(/\s+$/, '')
36
+ if (text.trim() === '') continue
37
+ const indent = raw.length - raw.replace(/^[ \t]*/, '').length
38
+ lines.push({ indent, text: text.trim(), raw })
39
+ }
40
+ return lines
41
+ }
42
+
43
+ function stripComment(line: string): string {
44
+ let inSingle = false
45
+ let inDouble = false
46
+ for (let i = 0; i < line.length; i++) {
47
+ const ch = line[i]
48
+ if (ch === "'" && !inDouble) inSingle = !inSingle
49
+ else if (ch === '"' && !inSingle) inDouble = !inDouble
50
+ else if (ch === '#' && !inSingle && !inDouble && (i === 0 || /\s/.test(line[i - 1]!))) {
51
+ return line.slice(0, i)
52
+ }
53
+ }
54
+ return line
55
+ }
56
+
57
+ function parseScalar(text: string): unknown {
58
+ const t = text.trim()
59
+ if (t === '' || t === '~' || t === 'null' || t === 'Null' || t === 'NULL') return null
60
+ if (t === 'true' || t === 'True' || t === 'TRUE') return true
61
+ if (t === 'false' || t === 'False' || t === 'FALSE') return false
62
+ if (/^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?$/.test(t)) return Number(t)
63
+ if (/^0x[0-9a-fA-F]+$/.test(t)) return parseInt(t, 16)
64
+ if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) {
65
+ try {
66
+ return JSON.parse(t)
67
+ } catch {
68
+ return t.slice(1, -1)
69
+ }
70
+ }
71
+ if (t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
72
+ return t.slice(1, -1).replace(/''/g, "'")
73
+ }
74
+ if (t.startsWith('[') && t.endsWith(']')) return parseFlowArray(t)
75
+ if (t.startsWith('{') && t.endsWith('}')) return parseFlowMap(t)
76
+ return t
77
+ }
78
+
79
+ function splitFlow(inner: string): string[] {
80
+ const parts: string[] = []
81
+ let depth = 0
82
+ let quote: string | null = null
83
+ let current = ''
84
+ for (const ch of inner) {
85
+ if (quote) {
86
+ current += ch
87
+ if (ch === quote) quote = null
88
+ continue
89
+ }
90
+ if (ch === '"' || ch === "'") {
91
+ quote = ch
92
+ current += ch
93
+ } else if (ch === '[' || ch === '{') {
94
+ depth++
95
+ current += ch
96
+ } else if (ch === ']' || ch === '}') {
97
+ depth--
98
+ current += ch
99
+ } else if (ch === ',' && depth === 0) {
100
+ parts.push(current)
101
+ current = ''
102
+ } else {
103
+ current += ch
104
+ }
105
+ }
106
+ if (current.trim() !== '') parts.push(current)
107
+ return parts
108
+ }
109
+
110
+ function parseFlowArray(text: string): unknown[] {
111
+ const inner = text.slice(1, -1).trim()
112
+ if (inner === '') return []
113
+ return splitFlow(inner).map(p => parseScalar(p))
114
+ }
115
+
116
+ function parseFlowMap(text: string): Record<string, unknown> {
117
+ const inner = text.slice(1, -1).trim()
118
+ const out: Record<string, unknown> = {}
119
+ if (inner === '') return out
120
+ for (const part of splitFlow(inner)) {
121
+ const idx = part.indexOf(':')
122
+ if (idx < 0) continue
123
+ out[parseScalar(part.slice(0, idx)) as string] = parseScalar(part.slice(idx + 1))
124
+ }
125
+ return out
126
+ }
127
+
128
+ /** Splits "key: value" at the first colon that is followed by a space or end. */
129
+ function splitKeyValue(text: string): { key: string; value: string } | null {
130
+ let quote: string | null = null
131
+ for (let i = 0; i < text.length; i++) {
132
+ const ch = text[i]
133
+ if (quote) {
134
+ if (ch === quote) quote = null
135
+ continue
136
+ }
137
+ if (ch === '"' || ch === "'") {
138
+ quote = ch
139
+ continue
140
+ }
141
+ if (ch === ':' && (i === text.length - 1 || text[i + 1] === ' ')) {
142
+ return { key: String(parseScalar(text.slice(0, i))), value: text.slice(i + 1).trim() }
143
+ }
144
+ }
145
+ return null
146
+ }
147
+
148
+ function parseBlock(lines: Line[], start: number, indent: number): { value: unknown; next: number } {
149
+ if (start >= lines.length) return { value: null, next: start }
150
+ if (lines[start]!.text.startsWith('- ') || lines[start]!.text === '-') {
151
+ return parseSequence(lines, start, indent)
152
+ }
153
+ return parseMapping(lines, start, indent)
154
+ }
155
+
156
+ function parseBlockScalar(lines: Line[], start: number, indent: number, style: '|' | '>'): { value: string; next: number } {
157
+ const collected: string[] = []
158
+ let i = start
159
+ let base = -1
160
+ while (i < lines.length && lines[i]!.indent > indent) {
161
+ const raw = lines[i]!.raw.replace(/\s+$/, '')
162
+ if (base < 0) base = lines[i]!.indent
163
+ collected.push(raw.slice(Math.min(base, raw.length - raw.trimStart().length)))
164
+ i++
165
+ }
166
+ const text = style === '|' ? collected.join('\n') : collected.join(' ').replace(/\s+/g, ' ')
167
+ return { value: text, next: i }
168
+ }
169
+
170
+ function parseMapping(lines: Line[], start: number, indent: number): { value: Record<string, unknown>; next: number } {
171
+ const out: Record<string, unknown> = {}
172
+ let i = start
173
+ while (i < lines.length) {
174
+ const line = lines[i]!
175
+ if (line.indent < indent) break
176
+ if (line.indent > indent) {
177
+ i++
178
+ continue
179
+ }
180
+ const kv = splitKeyValue(line.text)
181
+ if (!kv) {
182
+ i++
183
+ continue
184
+ }
185
+ if (kv.value === '' ) {
186
+ // nested block or empty
187
+ if (i + 1 < lines.length && lines[i + 1]!.indent > indent) {
188
+ const nested = parseBlock(lines, i + 1, lines[i + 1]!.indent)
189
+ out[kv.key] = nested.value
190
+ i = nested.next
191
+ } else if (i + 1 < lines.length && lines[i + 1]!.indent === indent && lines[i + 1]!.text.startsWith('- ')) {
192
+ // sequence at the same indentation as its key (common YAML style)
193
+ const nested = parseSequence(lines, i + 1, indent)
194
+ out[kv.key] = nested.value
195
+ i = nested.next
196
+ } else {
197
+ out[kv.key] = null
198
+ i++
199
+ }
200
+ } else if (kv.value === '|' || kv.value === '>' || kv.value === '|-' || kv.value === '>-') {
201
+ const block = parseBlockScalar(lines, i + 1, indent, kv.value[0] as '|' | '>')
202
+ out[kv.key] = kv.value.endsWith('-') ? block.value.replace(/\n+$/, '') : block.value
203
+ i = block.next
204
+ } else {
205
+ out[kv.key] = parseScalar(kv.value)
206
+ i++
207
+ }
208
+ }
209
+ return { value: out, next: i }
210
+ }
211
+
212
+ function parseSequence(lines: Line[], start: number, indent: number): { value: unknown[]; next: number } {
213
+ const out: unknown[] = []
214
+ let i = start
215
+ while (i < lines.length) {
216
+ const line = lines[i]!
217
+ if (line.indent < indent) break
218
+ if (line.indent > indent || !(line.text.startsWith('- ') || line.text === '-')) {
219
+ i++
220
+ continue
221
+ }
222
+ const rest = line.text === '-' ? '' : line.text.slice(2).trim()
223
+ if (rest === '') {
224
+ if (i + 1 < lines.length && lines[i + 1]!.indent > indent) {
225
+ const nested = parseBlock(lines, i + 1, lines[i + 1]!.indent)
226
+ out.push(nested.value)
227
+ i = nested.next
228
+ } else {
229
+ out.push(null)
230
+ i++
231
+ }
232
+ continue
233
+ }
234
+ const kv = splitKeyValue(rest)
235
+ if (kv && !rest.startsWith('"') && !rest.startsWith("'") && !rest.startsWith('[') && !rest.startsWith('{')) {
236
+ // "- key: value" starts an inline mapping whose other keys follow at indent + 2
237
+ const item: Record<string, unknown> = {}
238
+ item[kv.key] = kv.value === '' ? null : parseScalar(kv.value)
239
+ let j = i + 1
240
+ const childIndent = indent + 2
241
+ if (j < lines.length && lines[j]!.indent >= childIndent) {
242
+ const more = parseMapping(lines, j, lines[j]!.indent)
243
+ Object.assign(item, more.value)
244
+ j = more.next
245
+ }
246
+ out.push(item)
247
+ i = j
248
+ } else {
249
+ out.push(parseScalar(rest))
250
+ i++
251
+ }
252
+ }
253
+ return { value: out, next: i }
254
+ }
255
+
256
+ export function parseYamlSubset(input: string): unknown {
257
+ const lines = splitLines(input)
258
+ if (lines.length === 0) return null
259
+ const first = lines[0]!
260
+ return parseBlock(lines, 0, first.indent).value
15
261
  }
@@ -1,168 +0,0 @@
1
- import OpenAI from 'openai'
2
- import { MODEL_ALIASES } from './aliases.js'
3
-
4
- export type ProviderModel = {
5
- id: string
6
- name: string
7
- }
8
-
9
- export type ProviderName =
10
- | 'openai'
11
- | 'groq'
12
- | 'openrouter'
13
- | 'deepseek'
14
- | 'mistral'
15
- | 'together'
16
- | 'ollama'
17
- | 'fireworks'
18
- | 'moonshot'
19
- | 'unknown'
20
-
21
- /**
22
- * Returns true if the model name is a Claude model name or alias.
23
- * These don't exist on OpenAI-compatible providers, so the adapter
24
- * should fall back to the provider default.
25
- */
26
- export function isClaudeModelName(model: string): boolean {
27
- const lower = model.toLowerCase().trim()
28
- if (lower.includes('claude')) return true
29
- return (MODEL_ALIASES as readonly string[]).includes(lower)
30
- }
31
-
32
- /**
33
- * Detect which OpenAI-compatible provider is configured.
34
- */
35
- export function detectOpenAIProvider(): ProviderName {
36
- const key = process.env.OPENAI_API_KEY || ''
37
- const baseURL = process.env.OPENAI_BASE_URL || ''
38
-
39
- if (baseURL.includes('groq.com') || key.startsWith('gsk_')) return 'groq'
40
- if (baseURL.includes('openrouter.ai') || key.startsWith('sk-or-')) return 'openrouter'
41
- if (baseURL.includes('deepseek.com')) return 'deepseek'
42
- if (baseURL.includes('mistral.ai')) return 'mistral'
43
- if (baseURL.includes('together.xyz')) return 'together'
44
- if (baseURL.includes('localhost') || baseURL.includes('127.0.0.1')) return 'ollama'
45
- if (baseURL.includes('fireworks.ai')) return 'fireworks'
46
- if (baseURL.includes('moonshot.cn') || baseURL.includes('moonshot.ai')) return 'moonshot'
47
- if (!baseURL || baseURL.includes('openai.com')) return 'openai'
48
- return 'unknown'
49
- }
50
-
51
- const CURATED_MODELS: Record<ProviderName, ProviderModel[]> = {
52
- openai: [
53
- { id: 'gpt-4o', name: 'GPT-4o' },
54
- { id: 'gpt-4o-mini', name: 'GPT-4o Mini' },
55
- { id: 'gpt-4.1', name: 'GPT-4.1' },
56
- { id: 'gpt-4.1-mini', name: 'GPT-4.1 Mini' },
57
- { id: 'o3', name: 'o3' },
58
- { id: 'o4-mini', name: 'o4 Mini' },
59
- ],
60
- groq: [
61
- { id: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B' },
62
- { id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B Instant' },
63
- { id: 'gemma2-9b-it', name: 'Gemma 2 9B' },
64
- { id: 'mixtral-8x7b-32768', name: 'Mixtral 8x7B' },
65
- ],
66
- openrouter: [
67
- { id: 'google/gemini-2.0-flash-001', name: 'Gemini 2.0 Flash' },
68
- { id: 'openai/gpt-4o', name: 'GPT-4o' },
69
- { id: 'meta-llama/llama-3.3-70b', name: 'Llama 3.3 70B' },
70
- ],
71
- deepseek: [
72
- { id: 'deepseek-chat', name: 'DeepSeek Chat' },
73
- { id: 'deepseek-reasoner', name: 'DeepSeek Reasoner' },
74
- ],
75
- mistral: [
76
- { id: 'mistral-large-latest', name: 'Mistral Large' },
77
- { id: 'mistral-small-latest', name: 'Mistral Small' },
78
- { id: 'codestral-latest', name: 'Codestral' },
79
- ],
80
- together: [
81
- { id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', name: 'Llama 3.3 70B Turbo' },
82
- { id: 'Qwen/Qwen2.5-72B-Instruct-Turbo', name: 'Qwen 2.5 72B Turbo' },
83
- ],
84
- ollama: [
85
- { id: 'llama3.2', name: 'Llama 3.2' },
86
- { id: 'qwen2.5-coder', name: 'Qwen 2.5 Coder' },
87
- { id: 'deepseek-r1', name: 'DeepSeek R1' },
88
- { id: 'mistral', name: 'Mistral' },
89
- ],
90
- fireworks: [
91
- { id: 'accounts/fireworks/models/llama-v3p3-70b-instruct', name: 'Llama 3.3 70B' },
92
- ],
93
- moonshot: [
94
- { id: 'moonshot-v1-32k', name: 'Moonshot V1 32K' },
95
- { id: 'moonshot-v1-128k', name: 'Moonshot V1 128K' },
96
- ],
97
- unknown: [],
98
- }
99
-
100
- export function getCuratedModels(provider: ProviderName): ProviderModel[] {
101
- return CURATED_MODELS[provider] ?? []
102
- }
103
-
104
- let cachedModels: ProviderModel[] | null = null
105
-
106
- /**
107
- * Fetch all available models from the provider via GET /v1/models.
108
- * Results are cached in memory for the session.
109
- * Returns curated fallback list on failure.
110
- */
111
- export async function fetchProviderModels(): Promise<ProviderModel[]> {
112
- if (cachedModels !== null) {
113
- return cachedModels
114
- }
115
-
116
- const provider = detectOpenAIProvider()
117
-
118
- try {
119
- const client = new OpenAI({
120
- apiKey: process.env.OPENAI_API_KEY,
121
- ...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}),
122
- ...(provider === 'groq' && !process.env.OPENAI_BASE_URL
123
- ? { baseURL: 'https://api.groq.com/openai/v1' }
124
- : {}),
125
- ...(provider === 'openrouter' && !process.env.OPENAI_BASE_URL
126
- ? { baseURL: 'https://openrouter.ai/api/v1' }
127
- : {}),
128
- })
129
-
130
- const response = await client.models.list({}, { timeout: 5000 })
131
- const models: ProviderModel[] = []
132
-
133
- for await (const model of response) {
134
- models.push({ id: model.id, name: model.id })
135
- }
136
-
137
- models.sort((a, b) => a.id.localeCompare(b.id))
138
- cachedModels = models
139
- return models
140
- } catch {
141
- const fallback = getCuratedModels(provider)
142
- cachedModels = fallback
143
- return fallback
144
- }
145
- }
146
-
147
- /**
148
- * Check if a model ID exists in the cached provider model list.
149
- * Returns false if the cache hasn't been populated yet.
150
- */
151
- export function isKnownProviderModel(modelId: string): boolean {
152
- if (!cachedModels) return false
153
- return cachedModels.some(m => m.id === modelId)
154
- }
155
-
156
- /**
157
- * Returns true if the app is using an OpenAI-compatible backend.
158
- */
159
- export function isOpenAIBackend(): boolean {
160
- return !!process.env.OPENAI_API_KEY
161
- }
162
-
163
- /**
164
- * Get the startup default model for the current OpenAI provider.
165
- */
166
- export function getOpenAIDefaultModel(): string {
167
- return process.env.OPENAI_MODEL || 'gpt-4o'
168
- }