@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.
- package/local/README.md +112 -0
- package/local/start-qwen.sh +47 -0
- package/local/start-tunnel.sh +36 -0
- package/local/xela-local.sh +139 -0
- package/package.json +19 -5
- package/run.sh +33 -0
- package/src/Tool.ts +1 -1
- package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
- package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
- package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
- package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
- package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
- package/src/_shims/bun_bundle.js +79 -4
- package/src/_shims/cjs_stub.cjs +32 -10
- package/src/_shims/loader.js +104 -5
- package/src/_shims/openai_adapter.ts +446 -230
- package/src/_shims/register.js +16 -3
- package/src/assistant/gate.ts +15 -0
- package/src/assistant/index.ts +89 -0
- package/src/bootstrap/state.ts +6 -1
- package/src/bridge/peerSessions.ts +18 -0
- package/src/cli/print.ts +5 -0
- package/src/commands/assistant/assistant.ts +17 -0
- package/src/commands/assistant/index.ts +11 -0
- package/src/commands/brief.ts +3 -1
- package/src/commands/buddy/buddy.ts +88 -0
- package/src/commands/buddy/index.ts +12 -0
- package/src/commands/model/model.tsx +8 -23
- package/src/commands/peers/index.ts +11 -0
- package/src/commands/peers/peers.ts +24 -0
- package/src/commands/proactive/proactive.ts +64 -0
- package/src/commands/proactive.ts +12 -0
- package/src/commands/workflows/index.ts +12 -0
- package/src/commands/workflows/workflows.ts +36 -0
- package/src/commands.ts +4 -8
- package/src/components/Messages.tsx +1 -1
- package/src/components/ModelPicker.tsx +4 -34
- package/src/components/Spinner.tsx +54 -1
- package/src/components/StructuredDiff/colorDiff.ts +3 -1
- package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
- package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
- package/src/components/permissions/PermissionRequest.tsx +4 -2
- package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
- package/src/constants/prompts.ts +26 -0
- package/src/ink/terminal.ts +9 -1
- package/src/jobs/classifier.ts +103 -0
- package/src/memdir/paths.ts +13 -1
- package/src/proactive/index.ts +158 -0
- package/src/proactive/useProactive.ts +78 -0
- package/src/screens/REPL.tsx +28 -8
- package/src/services/api/claude.ts +6 -1
- package/src/services/compact/autoCompact.ts +18 -4
- package/src/services/compact/cachedMCConfig.ts +1 -0
- package/src/services/compact/cachedMicrocompact.ts +113 -0
- package/src/services/compact/compact.ts +22 -2
- package/src/services/compact/reactiveCompact.ts +251 -0
- package/src/services/compact/snipCompact.ts +282 -0
- package/src/services/compact/snipProjection.ts +50 -0
- package/src/services/contextCollapse/index.ts +322 -0
- package/src/services/contextCollapse/operations.ts +187 -0
- package/src/services/contextCollapse/persist.ts +40 -0
- package/src/services/extractMemories/extractMemories.ts +11 -2
- package/src/services/skillSearch/featureCheck.ts +12 -0
- package/src/services/skillSearch/localSearch.ts +98 -0
- package/src/services/skillSearch/prefetch.ts +125 -0
- package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
- package/src/services/skillSearch/remoteSkillState.ts +21 -0
- package/src/services/skillSearch/signals.ts +7 -0
- package/src/services/skillSearch/telemetry.ts +9 -0
- package/src/setup.ts +10 -5
- package/src/skills/bundled/dream.ts +31 -0
- package/src/skills/bundled/hunter.ts +45 -0
- package/src/skills/bundled/index.ts +10 -23
- package/src/skills/bundled/runSkillGenerator.ts +61 -0
- package/src/skills/mcpSkills.ts +101 -0
- package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
- package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
- package/src/tasks.ts +22 -8
- package/src/tools/AgentTool/runAgent.ts +11 -0
- package/src/tools/BriefTool/BriefTool.ts +2 -0
- package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
- package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
- package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
- package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
- package/src/tools/MCPTool/MCPTool.ts +6 -0
- package/src/tools/MonitorTool/MonitorTool.ts +273 -0
- package/src/tools/MonitorTool/UI.tsx +25 -0
- package/src/tools/MonitorTool/events.ts +84 -0
- package/src/tools/MonitorTool/prompt.ts +45 -0
- package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
- package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
- package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
- package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
- package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
- package/src/tools/SendUserFileTool/prompt.ts +7 -0
- package/src/tools/SleepTool/SleepTool.ts +126 -0
- package/src/tools/SnipTool/SnipTool.ts +90 -0
- package/src/tools/SnipTool/prompt.ts +5 -0
- package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
- package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
- package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
- package/src/tools/ToolSearchTool/prompt.ts +1 -1
- package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
- package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
- package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
- package/src/tools/WebBrowserTool/prompt.ts +16 -0
- package/src/tools/WorkflowTool/UI.tsx +27 -0
- package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
- package/src/tools/WorkflowTool/bundled/index.ts +88 -0
- package/src/tools/WorkflowTool/constants.ts +7 -0
- package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
- package/src/tools/WorkflowTool/prompt.ts +38 -0
- package/src/tools/WorkflowTool/runtime.ts +331 -0
- package/src/tools.ts +36 -31
- package/src/utils/betas.ts +5 -0
- package/src/utils/context.ts +9 -0
- package/src/utils/conversationRecovery.ts +1 -1
- package/src/utils/forkedAgent.ts +4 -0
- package/src/utils/logoV2Utils.ts +7 -3
- package/src/utils/messageQueueManager.ts +23 -9
- package/src/utils/model/model.ts +10 -9
- package/src/utils/model/modelOptions.ts +0 -21
- package/src/utils/model/validateModel.ts +0 -19
- package/src/utils/permissions/permissionSetup.ts +5 -0
- package/src/utils/permissions/permissions.ts +25 -0
- package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
- package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
- package/src/utils/permissions/yoloClassifier.ts +28 -6
- package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
- package/src/utils/systemThemeWatcher.ts +62 -0
- package/src/utils/udsClient.ts +115 -0
- package/src/utils/udsMessaging.ts +157 -0
- package/src/utils/yaml.ts +251 -5
- 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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
}
|