mixdog 0.9.4 → 0.9.6
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/README.md +112 -38
- package/package.json +4 -1
- package/scripts/bench/r5-orchestrated-task.json +7 -0
- package/scripts/bench/round-r13-clientmeta.json +464 -0
- package/scripts/bench/round-r14-betafeatures.json +466 -0
- package/scripts/bench/round-r15-fulldefault.json +462 -0
- package/scripts/bench/round-r16-sessionid.json +466 -0
- package/scripts/bench/round-r17-wirebytes.json +456 -0
- package/scripts/bench/round-r18-prewarm.json +468 -0
- package/scripts/bench/round-r19-clean.json +472 -0
- package/scripts/bench/round-r20-prewarm-clean.json +475 -0
- package/scripts/bench/round-r21-delta-retry.json +473 -0
- package/scripts/bench/round-r22-full-probe.json +693 -0
- package/scripts/bench/round-r23-itemprobe.json +701 -0
- package/scripts/bench/round-r24-shapefix.json +677 -0
- package/scripts/bench/round-r25-serial.json +464 -0
- package/scripts/bench/round-r26-parallel3.json +671 -0
- package/scripts/bench/round-r27-parallel10.json +894 -0
- package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
- package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
- package/scripts/bench/round-r30-instid.json +253 -0
- package/scripts/bench/round-r31-upgradeprobe.json +256 -0
- package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
- package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
- package/scripts/bench/round-r34-orchestrated.json +120 -0
- package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
- package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
- package/scripts/bench-run.mjs +40 -7
- package/scripts/internal-comms-bench.mjs +2 -4
- package/scripts/model-catalog-audit.mjs +209 -0
- package/scripts/model-list-sanitize-test.mjs +37 -0
- package/scripts/recall-bench.mjs +76 -13
- package/scripts/recall-quality-cases.json +12 -0
- package/scripts/tool-smoke.mjs +2 -2
- package/src/agents/heavy-worker/AGENT.md +7 -7
- package/src/agents/scheduler-task/AGENT.md +2 -3
- package/src/agents/webhook-handler/AGENT.md +2 -3
- package/src/agents/worker/AGENT.md +9 -10
- package/src/app.mjs +12 -1
- package/src/headless-role.mjs +7 -1
- package/src/mixdog-session-runtime.mjs +15 -14
- package/src/output-styles/default.md +14 -18
- package/src/output-styles/minimal.md +4 -5
- package/src/output-styles/simple.md +7 -8
- package/src/rules/agent/00-common.md +6 -5
- package/src/rules/agent/30-explorer.md +16 -5
- package/src/rules/lead/01-general.md +5 -5
- package/src/rules/lead/lead-brief.md +6 -3
- package/src/rules/lead/lead-tool.md +6 -4
- package/src/rules/shared/01-tool.md +17 -21
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +53 -9
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +23 -10
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +33 -4
- package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +93 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +27 -5
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +1 -1
- package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/channels/backends/discord-gateway.mjs +2 -2
- package/src/runtime/channels/backends/discord.mjs +15 -26
- package/src/runtime/channels/backends/telegram.mjs +8 -12
- package/src/runtime/channels/index.mjs +23 -43
- package/src/runtime/channels/lib/backend-dispatch.mjs +5 -3
- package/src/runtime/channels/lib/config.mjs +37 -149
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -4
- package/src/runtime/channels/lib/event-queue.mjs +15 -9
- package/src/runtime/channels/lib/inbound-routing.mjs +2 -39
- package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
- package/src/runtime/channels/lib/scheduler.mjs +27 -113
- package/src/runtime/channels/lib/webhook/deliveries.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +8 -47
- package/src/runtime/memory/index.mjs +9 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
- package/src/runtime/memory/lib/ko-morph.mjs +195 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +47 -10
- package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
- package/src/runtime/memory/lib/query-handlers.mjs +27 -5
- package/src/runtime/memory/tool-defs.mjs +2 -2
- package/src/runtime/shared/config.mjs +14 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
- package/src/runtime/shared/schedules-store.mjs +13 -3
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/session-runtime/config-helpers.mjs +45 -10
- package/src/session-runtime/quick-model-rows.mjs +46 -11
- package/src/session-runtime/quick-search-models.mjs +28 -27
- package/src/session-runtime/settings-api.mjs +34 -1
- package/src/session-runtime/tool-defs.mjs +1 -1
- package/src/session-runtime/workflow.mjs +1 -1
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/agent-tool.mjs +30 -0
- package/src/standalone/channel-admin.mjs +102 -90
- package/src/standalone/explore-tool.mjs +39 -10
- package/src/tui/App.jsx +131 -28
- package/src/tui/app/app-format.mjs +11 -4
- package/src/tui/app/channel-pickers.mjs +2 -4
- package/src/tui/app/maintenance-pickers.mjs +60 -28
- package/src/tui/app/model-options.mjs +5 -1
- package/src/tui/app/settings-picker.mjs +0 -1
- package/src/tui/app/transcript-window.mjs +8 -2
- package/src/tui/app/use-transcript-scroll.mjs +3 -1
- package/src/tui/app/use-transcript-window.mjs +19 -1
- package/src/tui/components/ConfirmBar.jsx +10 -7
- package/src/tui/components/Picker.jsx +32 -11
- package/src/tui/components/PromptInput.jsx +10 -1
- package/src/tui/components/StatusLine.jsx +6 -3
- package/src/tui/dist/index.mjs +441 -293
- package/src/tui/engine.mjs +48 -12
- package/src/tui/index.jsx +11 -52
- package/src/ui/statusline.mjs +10 -0
- package/src/workflows/default/WORKFLOW.md +15 -17
- package/src/workflows/sequential/WORKFLOW.md +15 -17
- package/vendor/ink/build/ink.js +9 -1
- package/scripts/recall-usecase-probe.json +0 -6
- package/src/runtime/channels/lib/holidays.mjs +0 -138
|
@@ -4,7 +4,7 @@ function __mixdogMemoryLog(...args) {
|
|
|
4
4
|
return __mixdogMemoryStderrWrite(...args);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
import { buildFtsQuery } from './memory-text-utils.mjs'
|
|
7
|
+
import { buildFtsQuery, buildFtsPrefixQuery } from './memory-text-utils.mjs'
|
|
8
8
|
import { VALID_CATEGORY, embeddingToSql } from './memory.mjs'
|
|
9
9
|
import { freshnessFactor } from './memory-score.mjs'
|
|
10
10
|
import { buildRecallScopeFilter } from './memory-recall-scope-filter.mjs'
|
|
@@ -28,6 +28,21 @@ const SEMANTIC_TOP_RANK_MAX = 3
|
|
|
28
28
|
// the cross-language rescue while rejecting pure-noise top ranks.
|
|
29
29
|
const SEMANTIC_TOP_RANK_MIN_SIM = 0.70
|
|
30
30
|
const SHORT_QUERY_TOKEN_MAX = 2
|
|
31
|
+
// Dense-similarity weighting for retrievalScore. Lexical RRF sums 1/(K+rank)
|
|
32
|
+
// per matching leg (K=60), so a row hitting 2 lexical legs at rank 1 scores
|
|
33
|
+
// rrf ≈ 2/(60+1) = 0.0328 — enough to bury a single semantically-close dense
|
|
34
|
+
// hit that never shares lexical terms. We add a normalized dense-similarity
|
|
35
|
+
// term so a strong dense-only match can outrank stale multi-leg lexical hits:
|
|
36
|
+
// denseTerm = W_DENSE * max(0, sim - SIM_FLOOR) / (1 - SIM_FLOOR) * freshness
|
|
37
|
+
// At sim=0.85: (0.85-0.60)/(1-0.60) = 0.625, so denseTerm = 0.04 * 0.625 = 0.025
|
|
38
|
+
// with freshness=1. That alone doesn't beat 0.0328, but combined with the
|
|
39
|
+
// row's own rrf contribution (a dense-only row still has 1/(60+rank) from its
|
|
40
|
+
// dense rank, e.g. rank 1 → 0.0164) the total ≈ 0.0164 + 0.025 = 0.0414,
|
|
41
|
+
// which clears the 0.0328 two-leg lexical baseline. W_DENSE=0.04 was chosen
|
|
42
|
+
// as the smallest round value satisfying this at sim=0.85; SIM_FLOOR=0.60
|
|
43
|
+
// matches the noise floor used elsewhere for MiniLM-class embeddings.
|
|
44
|
+
const SIM_FLOOR = 0.60
|
|
45
|
+
const W_DENSE = 0.04
|
|
31
46
|
|
|
32
47
|
function buildExactTerms(query) {
|
|
33
48
|
const clean = String(query ?? '').replace(/\s+/g, ' ').trim()
|
|
@@ -230,7 +245,13 @@ export async function searchRelevantHybrid(db, query, options = {}) {
|
|
|
230
245
|
? embeddingToSql(options.queryVector)
|
|
231
246
|
: null
|
|
232
247
|
|
|
233
|
-
|
|
248
|
+
// Prefer the Kiwi morph prefix-form query (to_tsquery, ':*' prefix match on
|
|
249
|
+
// content-morpheme stems) when kiwi is ready; else fall back to the plain
|
|
250
|
+
// websearch_to_tsquery path. ftsPrefixMode drives which tsquery ctor the
|
|
251
|
+
// sparse CTE uses (to_tsquery vs websearch_to_tsquery).
|
|
252
|
+
const ftsPrefix = clean.length >= 3 ? buildFtsPrefixQuery(clean) : null
|
|
253
|
+
const ftsQuery = ftsPrefix ? ftsPrefix.query : (clean.length >= 3 ? (buildFtsQuery(clean) ?? null) : null)
|
|
254
|
+
const ftsPrefixMode = Boolean(ftsPrefix)
|
|
234
255
|
const exactTerms = buildExactTerms(clean)
|
|
235
256
|
const queryTokenCount = countQueryTokens(clean)
|
|
236
257
|
const minExactHits = exactTerms.length >= 8 ? 3 : exactTerms.length >= 4 ? 2 : 1
|
|
@@ -329,23 +350,29 @@ dense AS (SELECT NULL::bigint AS id, NULL::float8 AS sim, NULL::bigint AS dense_
|
|
|
329
350
|
|
|
330
351
|
// sparse CTE: active only when ftsQuery is non-null.
|
|
331
352
|
// useHotActive → queries mv_hot_active GIN index (mv_hot_active_tsv).
|
|
353
|
+
// tsqExpr: to_tsquery for the Kiwi morph prefix-form query ('stem:* & ...'),
|
|
354
|
+
// else websearch_to_tsquery for the plain fallback token string. Both parse
|
|
355
|
+
// $2 under the 'simple' config to match search_tsv's simple-config lexemes.
|
|
356
|
+
const tsqExpr = ftsPrefixMode
|
|
357
|
+
? `to_tsquery('simple', $2)`
|
|
358
|
+
: `websearch_to_tsquery('simple', $2)`
|
|
332
359
|
const sparseCte = ftsQuery ? (useHotActive ? `
|
|
333
360
|
sparse AS (
|
|
334
361
|
SELECT id,
|
|
335
|
-
ts_rank_cd(search_tsv,
|
|
336
|
-
ROW_NUMBER() OVER (ORDER BY ts_rank_cd(search_tsv,
|
|
362
|
+
ts_rank_cd(search_tsv, ${tsqExpr}) AS lex,
|
|
363
|
+
ROW_NUMBER() OVER (ORDER BY ts_rank_cd(search_tsv, ${tsqExpr}) DESC) AS sparse_rank
|
|
337
364
|
FROM mv_hot_active
|
|
338
|
-
WHERE search_tsv @@
|
|
365
|
+
WHERE search_tsv @@ ${tsqExpr}
|
|
339
366
|
${mvFilterClause}
|
|
340
367
|
ORDER BY lex DESC
|
|
341
368
|
LIMIT $4
|
|
342
369
|
),` : `
|
|
343
370
|
sparse AS (
|
|
344
371
|
SELECT id,
|
|
345
|
-
ts_rank_cd(search_tsv,
|
|
346
|
-
ROW_NUMBER() OVER (ORDER BY ts_rank_cd(search_tsv,
|
|
372
|
+
ts_rank_cd(search_tsv, ${tsqExpr}) AS lex,
|
|
373
|
+
ROW_NUMBER() OVER (ORDER BY ts_rank_cd(search_tsv, ${tsqExpr}) DESC) AS sparse_rank
|
|
347
374
|
FROM entries
|
|
348
|
-
WHERE search_tsv @@
|
|
375
|
+
WHERE search_tsv @@ ${tsqExpr}
|
|
349
376
|
${filterClause}
|
|
350
377
|
ORDER BY lex DESC
|
|
351
378
|
LIMIT $4
|
|
@@ -489,7 +516,11 @@ LEFT JOIN exact x ON x.id = c.id`
|
|
|
489
516
|
+ (exactRank ? 1 / (K + exactRank) : 0)
|
|
490
517
|
const freshness = applyFreshness ? freshnessFactor(row.ts, nowMs) : 1.0
|
|
491
518
|
const boost = exactTextBoost(clean, row, row.exact_hits)
|
|
492
|
-
|
|
519
|
+
const sim = Number(row.dense_sim)
|
|
520
|
+
const denseTerm = Number.isFinite(sim)
|
|
521
|
+
? W_DENSE * Math.max(0, sim - SIM_FLOOR) / (1 - SIM_FLOOR) * freshness
|
|
522
|
+
: 0
|
|
523
|
+
return { id, row, rrf, freshness, retrievalScore: (rrf * freshness) + boost + denseTerm }
|
|
493
524
|
})
|
|
494
525
|
let semanticOnlyDropped = 0
|
|
495
526
|
let weakTextDropped = 0
|
|
@@ -515,7 +546,13 @@ LEFT JOIN exact x ON x.id = c.id`
|
|
|
515
546
|
if (hasFullPhrase) return true
|
|
516
547
|
if (queryTokenCount <= SHORT_QUERY_TOKEN_MAX) return true
|
|
517
548
|
if (row.sparse_rank != null) return true
|
|
518
|
-
|
|
549
|
+
// Semantic support as a second signal only applies when the dense leg
|
|
550
|
+
// actually ran. With a cold embedding model (no queryVector) semantic
|
|
551
|
+
// support is unattainable, and requiring it silently zeroed every
|
|
552
|
+
// multi-token lexical recall until warmup finished. Token coverage alone
|
|
553
|
+
// carries the filter in that degraded mode.
|
|
554
|
+
const semanticLegActive = vecSql != null
|
|
555
|
+
if ((!semanticLegActive || hasSemanticSupport) && hasQueryTokenCoverage(row, queryTokenCount)) return true
|
|
519
556
|
weakTextDropped += 1
|
|
520
557
|
return false
|
|
521
558
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cleanMemoryText,
|
|
3
3
|
} from './memory-extraction.mjs'
|
|
4
|
+
import { isReady as koMorphReady, stems as koMorphStems } from './ko-morph.mjs'
|
|
4
5
|
|
|
5
6
|
const MEMORY_TOKEN_STOPWORDS = new Set([
|
|
6
7
|
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'did', 'do', 'does', 'for', 'from',
|
|
@@ -55,4 +56,49 @@ export function buildFtsQuery(text) {
|
|
|
55
56
|
return ftsTokens.map(t => t.replace(/["']/g, '')).filter(t => t.length > 0).join(' ')
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
const HANGUL_RE = /[\uAC00-\uD7AF]/
|
|
60
|
+
|
|
61
|
+
// Sanitize a single lexeme for embedding inside a to_tsquery string. Strips the
|
|
62
|
+
// tsquery operator characters so a raw token can never inject syntax.
|
|
63
|
+
function sanitizeLexeme(t) {
|
|
64
|
+
return String(t ?? '').replace(/[&|!():*'"\\\s]+/g, '')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Prefix-form (to_tsquery) builder. Returns null when kiwi morph is not ready,
|
|
68
|
+
// signalling the caller to keep the websearch_to_tsquery fallback path. When
|
|
69
|
+
// ready: Korean tokens → Kiwi content-morpheme stems (NNG/NNP/VV/VA/XR/SL),
|
|
70
|
+
// non-Korean tokens kept as-is; every lexeme gets a ':*' prefix match and the
|
|
71
|
+
// set is '&'-joined to preserve the current AND semantics.
|
|
72
|
+
//
|
|
73
|
+
// Returns { query, prefix:true } on success, or null.
|
|
74
|
+
export function buildFtsPrefixQuery(text) {
|
|
75
|
+
if (!koMorphReady()) return null
|
|
76
|
+
const tokens = tokenizeMemoryText(text)
|
|
77
|
+
if (tokens.length === 0) return null
|
|
78
|
+
const ftsTokens = [...new Set(tokens)].filter(t => t.length >= 3 || (t.length === 2 && HANGUL_RE.test(t)))
|
|
79
|
+
if (ftsTokens.length === 0) return null
|
|
80
|
+
|
|
81
|
+
const lexemes = []
|
|
82
|
+
const seen = new Set()
|
|
83
|
+
for (const tok of ftsTokens) {
|
|
84
|
+
if (HANGUL_RE.test(tok)) {
|
|
85
|
+
// Analyze against the ORIGINAL token (pre suffix-strip) so Kiwi sees the
|
|
86
|
+
// real inflection; normalizeMemoryToken already ran, so also feed the
|
|
87
|
+
// stripped form — union of both keeps recall high.
|
|
88
|
+
const st = koMorphStems(tok)
|
|
89
|
+
const forms = (st && st.length > 0) ? st : [tok]
|
|
90
|
+
for (const f of forms) {
|
|
91
|
+
const lex = sanitizeLexeme(f)
|
|
92
|
+
if (lex.length >= 1 && !seen.has(lex)) { seen.add(lex); lexemes.push(lex) }
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
const lex = sanitizeLexeme(tok)
|
|
96
|
+
if (lex.length >= 1 && !seen.has(lex)) { seen.add(lex); lexemes.push(lex) }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (lexemes.length === 0) return null
|
|
100
|
+
const query = lexemes.map(l => `${l}:*`).join(' & ')
|
|
101
|
+
return { query, prefix: true }
|
|
102
|
+
}
|
|
103
|
+
|
|
58
104
|
|
|
@@ -479,17 +479,39 @@ export function createQueryHandlers({
|
|
|
479
479
|
let queryVector = null
|
|
480
480
|
if (isEmbeddingModelReady()) {
|
|
481
481
|
queryVector = await embedText(query)
|
|
482
|
+
} else if (embeddingWarmupCanStart()) {
|
|
483
|
+
// Cold model: WAIT for warmup (bounded) instead of silently degrading
|
|
484
|
+
// to lexical-only. Raw rows are already embedded at ingest time, so
|
|
485
|
+
// the query vector is the only missing piece of the dense leg — a few
|
|
486
|
+
// seconds of warmup buys full hybrid quality on the first recall.
|
|
487
|
+
// On timeout the warmup keeps running in the background and this
|
|
488
|
+
// recall falls back to lexical (same as before).
|
|
489
|
+
const COLD_RECALL_WARMUP_WAIT_MS = Math.max(0, Number(process.env.MIXDOG_RECALL_WARMUP_WAIT_MS) || 8_000)
|
|
490
|
+
let timer
|
|
491
|
+
try {
|
|
492
|
+
queryVector = await Promise.race([
|
|
493
|
+
warmupEmbeddingProvider().then(() => embedText(query)),
|
|
494
|
+
new Promise((resolve) => { timer = setTimeout(() => resolve(null), COLD_RECALL_WARMUP_WAIT_MS) }),
|
|
495
|
+
])
|
|
496
|
+
} catch (err) {
|
|
497
|
+
log(`[memory-service] embedding warmup during cold recall failed: ${err?.message || err}\n`)
|
|
498
|
+
queryVector = null
|
|
499
|
+
} finally {
|
|
500
|
+
clearTimeout(timer)
|
|
501
|
+
}
|
|
502
|
+
if (!Array.isArray(queryVector)) {
|
|
503
|
+
const now = Date.now()
|
|
504
|
+
if (now - _embeddingColdRecallLogAt > 10_000) {
|
|
505
|
+
_embeddingColdRecallLogAt = now
|
|
506
|
+
log(`[recall] embedding still cold after ${COLD_RECALL_WARMUP_WAIT_MS}ms wait; returning lexical results while background warmup continues\n`)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
482
509
|
} else {
|
|
483
510
|
const now = Date.now()
|
|
484
511
|
if (now - _embeddingColdRecallLogAt > 10_000) {
|
|
485
512
|
_embeddingColdRecallLogAt = now
|
|
486
513
|
log('[recall] embedding model cold; returning lexical results while background warmup continues\n')
|
|
487
514
|
}
|
|
488
|
-
if (embeddingWarmupCanStart()) {
|
|
489
|
-
void warmupEmbeddingProvider().catch((err) => {
|
|
490
|
-
log(`[memory-service] embedding warmup after cold recall failed: ${err?.message || err}\n`)
|
|
491
|
-
})
|
|
492
|
-
}
|
|
493
515
|
}
|
|
494
516
|
if (signal?.aborted) throw signal.reason ?? new Error('aborted')
|
|
495
517
|
const _t1 = Date.now()
|
|
@@ -8,7 +8,7 @@ export const TOOL_DEFS = [
|
|
|
8
8
|
name: 'memory',
|
|
9
9
|
title: 'Memory Cycle',
|
|
10
10
|
annotations: { title: 'Memory Cycle', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
11
|
-
description: 'Core-memory mutation and status. Use recall for retrieval.
|
|
11
|
+
description: 'Core-memory mutation and status. Use recall for retrieval. Persist with action:"core" op:"add" when: user corrects/confirms your approach, states a durable preference, or says "remember". Only what code/git/docs cannot derive; include the why. Never transient task state. No prior recall dedup needed — the cycle dedups.',
|
|
12
12
|
inputSchema: {
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
|
@@ -30,7 +30,7 @@ export const TOOL_DEFS = [
|
|
|
30
30
|
name: 'recall',
|
|
31
31
|
title: 'Recall',
|
|
32
32
|
annotations: { title: 'Recall', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
33
|
-
description: 'Retrieve stored memory/session history
|
|
33
|
+
description: 'Retrieve stored memory/session history. Use only to check prior conversation or past events: resumes, prior work, earlier decisions or messages. Do not call as a reflexive pre-step, to verify a just-made decision, or before storing memory. Query is topic/semantic search, not regex. Patterns: period:"last" for previous conversation; sessionId without query for current session; period:"3h"/"30m"/"24h" for recent; YYYY-MM-DD, date ranges, or HH:MM~HH:MM for time windows; id for exact follow-up.',
|
|
34
34
|
inputSchema: {
|
|
35
35
|
type: 'object',
|
|
36
36
|
properties: {
|
|
@@ -263,10 +263,20 @@ export function diagnoseDiscordTokenValue(value, config = {}) {
|
|
|
263
263
|
const discord = config?.discord && typeof config.discord === 'object' ? config.discord : {}
|
|
264
264
|
const appId = String(discord.applicationId || '').trim()
|
|
265
265
|
if (appId && token === appId) return 'Bot token field contains the Application ID, not the bot token.'
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
// Single main channel: check the `channel` object. Legacy `channelsConfig`
|
|
267
|
+
// is still read here (read-only) so the diagnostic keeps working on configs
|
|
268
|
+
// that predate the single-channel collapse.
|
|
269
|
+
const candidateEntries = []
|
|
270
|
+
if (config?.channel && typeof config.channel === 'object') candidateEntries.push(config.channel)
|
|
271
|
+
if (config?.channelsConfig && typeof config.channelsConfig === 'object') {
|
|
272
|
+
candidateEntries.push(...Object.values(config.channelsConfig))
|
|
273
|
+
}
|
|
274
|
+
for (const ch of candidateEntries) {
|
|
275
|
+
if (!ch || typeof ch !== 'object') continue
|
|
276
|
+
for (const id of [ch.channelId, ch.discordChannelId, ch.telegramChatId]) {
|
|
277
|
+
const channelId = String(id || '').trim()
|
|
278
|
+
if (channelId && token === channelId) return 'Bot token field contains a Channel ID, not the bot token.'
|
|
279
|
+
}
|
|
270
280
|
}
|
|
271
281
|
if (isDiscordSnowflake(token)) return 'Bot token field contains a numeric Discord ID, not the bot token.'
|
|
272
282
|
return null
|
|
@@ -44,6 +44,25 @@ export function readMarkdownDocument(markdown) {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Serialize a single-file frontmatter markdown document:
|
|
48
|
+
// ---
|
|
49
|
+
// key: value
|
|
50
|
+
// ---
|
|
51
|
+
//
|
|
52
|
+
// <body>
|
|
53
|
+
// Values are stringified as-is (enabled:false -> "false"); the reader casts
|
|
54
|
+
// types back. Mirrors the WORKFLOW.md / SKILL.md single-file convention,
|
|
55
|
+
// where `name` and `description` lead the frontmatter.
|
|
56
|
+
export function serializeFrontmatterDoc(meta = {}, body = '') {
|
|
57
|
+
const lines = ['---'];
|
|
58
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
59
|
+
if (value === undefined || value === null) continue;
|
|
60
|
+
lines.push(`${key}: ${value}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('---', '');
|
|
63
|
+
return `${lines.join('\n')}\n${String(body || '').trim()}\n`;
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
export function normalizeAgentPermission(value) {
|
|
48
67
|
const key = clean(value).toLowerCase();
|
|
49
68
|
return AGENT_PERMISSION_ALIASES.get(key) || null;
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `<Mixdog data dir>/schedules/<name>/` is the single source of truth.
|
|
5
5
|
* Each schedule directory contains a single `SCHEDULE.md` — YAML-ish
|
|
6
|
-
* frontmatter (
|
|
7
|
-
*
|
|
6
|
+
* frontmatter (name/description lead per the SKILL.md convention, then
|
|
7
|
+
* metadata: time/timezone/days/channel/model/enabled) plus a markdown
|
|
8
|
+
* body (the prompt). Both the setup UI (POST /schedules) and the
|
|
8
9
|
* `schedule-add` skill write the same one file; every reader —
|
|
9
10
|
* setup-server (GET /schedules), channels/lib/config.mjs (loadConfig),
|
|
10
11
|
* status/aggregator.mjs — must go through listSchedules() so a single
|
|
@@ -35,9 +36,12 @@ function readTextFile(path) {
|
|
|
35
36
|
*
|
|
36
37
|
* Return shape per entry: `{ name, ...frontmatter, prompt }`.
|
|
37
38
|
* - `name` is the directory name (slug).
|
|
39
|
+
* - `description` from frontmatter (display-only; defaults to '').
|
|
38
40
|
* - Spread of `SCHEDULE.md` frontmatter keys (time/timezone/days/channel/
|
|
39
41
|
* model/enabled). All frontmatter values arrive as strings; `enabled` is
|
|
40
42
|
* cast to a boolean here so downstream `s.enabled !== false` filters work.
|
|
43
|
+
* Frontmatter `name` is display-only and never overrides the dir slug used
|
|
44
|
+
* for identity/routing.
|
|
41
45
|
* - `prompt` carries the `SCHEDULE.md` body (empty string when the file is
|
|
42
46
|
* missing — caller decides whether that is a hard error).
|
|
43
47
|
*
|
|
@@ -66,7 +70,13 @@ export function listSchedules() {
|
|
|
66
70
|
if (Object.prototype.hasOwnProperty.call(cfg, 'enabled')) {
|
|
67
71
|
cfg.enabled = cfg.enabled !== 'false' && cfg.enabled !== false;
|
|
68
72
|
}
|
|
69
|
-
|
|
73
|
+
// Dir name is canonical identity; frontmatter `name` is display-only and
|
|
74
|
+
// must not override the slug. `description` defaults to '' (backward compat
|
|
75
|
+
// for old files with no such key).
|
|
76
|
+
const description = String(cfg.description || '');
|
|
77
|
+
delete cfg.name;
|
|
78
|
+
delete cfg.description;
|
|
79
|
+
out.push({ name, description, ...cfg, prompt: body });
|
|
70
80
|
}
|
|
71
81
|
return out;
|
|
72
82
|
}
|
|
@@ -2,10 +2,10 @@ export const TOOL_SYNC_EXECUTION_CONTRACT =
|
|
|
2
2
|
'Runs synchronously in this tool call.';
|
|
3
3
|
|
|
4
4
|
export const TOOL_ASYNC_EXECUTION_CONTRACT =
|
|
5
|
-
'Async returns task_id; completion notification follows. status/read/wait are
|
|
5
|
+
'Async returns task_id; completion notification follows. status/read/wait are recovery/blocking only.';
|
|
6
6
|
|
|
7
7
|
export const TOOL_MANUAL_CONTROL_CONTRACT =
|
|
8
|
-
'
|
|
8
|
+
'wait/read/status/cancel are for explicit blocking or recovery only.';
|
|
9
9
|
|
|
10
10
|
function clean(value) {
|
|
11
11
|
return String(value ?? '').trim();
|
|
@@ -114,15 +114,18 @@ const AUTO_CLEAR_DEFAULT_IDLE_MS = 60 * 60 * 1000;
|
|
|
114
114
|
|
|
115
115
|
// Provider-aware auto-clear idle-sweep defaults. Rationale mirrors the
|
|
116
116
|
// provider cache TTLs documented in agent-runtime/cache-strategy.mjs
|
|
117
|
-
// (explicit breakpoint / managed-cache windows): Anthropic
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
117
|
+
// (explicit breakpoint / managed-cache windows): Anthropic follows the BP4
|
|
118
|
+
// messages-tail TTL (5m default cache_control window; BP1~3 stay on the 1h
|
|
119
|
+
// extended TTL and are prefix-shared, so reaping at 5m only forfeits an
|
|
120
|
+
// already-cold tail — see resolveLeadMessagesTtl). Gemini, xAI and Mistral
|
|
121
|
+
// caches run ~1h, Groq's implicit cache persists ~2h, OpenAI/DeepSeek
|
|
122
|
+
// key-prefix/implicit caches persist far longer (~24h), and OpenAI OAuth's
|
|
123
|
+
// in-memory server cache is short-lived (~5-10min), so we round up slightly
|
|
124
|
+
// to 10m to avoid sweeping a still-warm cache. Unknown/unrecognized
|
|
125
|
+
// providers fall back to the 'default' 1h entry.
|
|
123
126
|
export const AUTO_CLEAR_PROVIDER_IDLE_MS = Object.freeze({
|
|
124
|
-
'anthropic':
|
|
125
|
-
'anthropic-oauth':
|
|
127
|
+
'anthropic': 5 * 60 * 1000,
|
|
128
|
+
'anthropic-oauth': 5 * 60 * 1000,
|
|
126
129
|
'gemini': 60 * 60 * 1000,
|
|
127
130
|
'groq': 2 * 60 * 60 * 1000,
|
|
128
131
|
'openai': 24 * 60 * 60 * 1000,
|
|
@@ -132,15 +135,46 @@ export const AUTO_CLEAR_PROVIDER_IDLE_MS = Object.freeze({
|
|
|
132
135
|
'mistral': 60 * 60 * 1000,
|
|
133
136
|
'default': AUTO_CLEAR_DEFAULT_IDLE_MS,
|
|
134
137
|
});
|
|
138
|
+
export const AUTO_CLEAR_PROVIDER_IDS = Object.freeze(Object.keys(AUTO_CLEAR_PROVIDER_IDLE_MS));
|
|
135
139
|
export const AUTO_CLEAR_UNKNOWN_PROVIDER_IDLE_MS = AUTO_CLEAR_PROVIDER_IDLE_MS.default;
|
|
136
140
|
|
|
141
|
+
export function normalizeAutoClearProviderIdleMs(value = {}) {
|
|
142
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
143
|
+
const out = {};
|
|
144
|
+
for (const [key, ms] of Object.entries(raw)) {
|
|
145
|
+
const provider = clean(key).toLowerCase();
|
|
146
|
+
const idleMs = Number(ms);
|
|
147
|
+
if (!provider || !Number.isFinite(idleMs) || idleMs <= 0) continue;
|
|
148
|
+
out[provider] = Math.max(60_000, Math.round(idleMs));
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
137
153
|
// Resolve the auto-clear idle-sweep threshold for a given provider id.
|
|
138
154
|
// Unknown/missing providers fall back to the 'default' 1h entry (above).
|
|
139
|
-
export function autoClearIdleMsForProvider(provider) {
|
|
155
|
+
export function autoClearIdleMsForProvider(provider, providerIdleMs = null) {
|
|
156
|
+
const overrides = normalizeAutoClearProviderIdleMs(providerIdleMs);
|
|
140
157
|
const key = clean(provider).toLowerCase();
|
|
158
|
+
if (overrides[key]) return overrides[key];
|
|
159
|
+
if (key && AUTO_CLEAR_PROVIDER_IDLE_MS[key]) return AUTO_CLEAR_PROVIDER_IDLE_MS[key];
|
|
160
|
+
if (overrides.default) return overrides.default;
|
|
141
161
|
return AUTO_CLEAR_PROVIDER_IDLE_MS[key] ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
|
|
142
162
|
}
|
|
143
163
|
|
|
164
|
+
export function autoClearProviderDefaults(providerIdleMs = null) {
|
|
165
|
+
const overrides = normalizeAutoClearProviderIdleMs(providerIdleMs);
|
|
166
|
+
return AUTO_CLEAR_PROVIDER_IDS.map((provider) => {
|
|
167
|
+
const builtInMs = AUTO_CLEAR_PROVIDER_IDLE_MS[provider] ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
|
|
168
|
+
const idleMs = overrides[provider] ?? builtInMs;
|
|
169
|
+
return {
|
|
170
|
+
provider,
|
|
171
|
+
idleMs,
|
|
172
|
+
builtInMs,
|
|
173
|
+
custom: overrides[provider] != null,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
144
178
|
// Resolve the effective auto-clear idle window for a config + provider:
|
|
145
179
|
// an explicit user-set idleMs (config.autoClear.idleMs) always wins; else
|
|
146
180
|
// fall back to the provider's default; else the global 1h default.
|
|
@@ -148,7 +182,7 @@ export function resolveAutoClearIdleMs(config, provider) {
|
|
|
148
182
|
const raw = config?.autoClear && typeof config.autoClear === 'object' ? config.autoClear : {};
|
|
149
183
|
const idleMs = Number(raw.idleMs ?? raw.thresholdMs ?? raw.idleMillis);
|
|
150
184
|
if (Number.isFinite(idleMs) && idleMs > 0) return Math.max(60_000, Math.round(idleMs));
|
|
151
|
-
return autoClearIdleMsForProvider(provider);
|
|
185
|
+
return autoClearIdleMsForProvider(provider, raw.providerIdleMs ?? raw.providerDefaults ?? raw.providers);
|
|
152
186
|
}
|
|
153
187
|
|
|
154
188
|
export function normalizeSystemShellConfig(value = {}) {
|
|
@@ -186,6 +220,7 @@ export function normalizeAutoClearConfig(value = {}) {
|
|
|
186
220
|
enabled: raw.enabled !== false,
|
|
187
221
|
idleMs: hasExplicitIdle ? Math.max(60_000, Math.round(idleMs)) : null,
|
|
188
222
|
custom: hasExplicitIdle,
|
|
223
|
+
providerIdleMs: normalizeAutoClearProviderIdleMs(raw.providerIdleMs ?? raw.providerDefaults ?? raw.providers),
|
|
189
224
|
};
|
|
190
225
|
}
|
|
191
226
|
|
|
@@ -4,6 +4,32 @@
|
|
|
4
4
|
import { clean } from './session-text.mjs';
|
|
5
5
|
import { QUICK_SEARCH_MODELS } from './quick-search-models.mjs';
|
|
6
6
|
import { SEARCH_DEFAULT_PROVIDER, SEARCH_DEFAULT_MODEL } from './workflow.mjs';
|
|
7
|
+
import { getModelMetadataSync } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
|
|
8
|
+
|
|
9
|
+
function metadataFor(provider, modelId) {
|
|
10
|
+
try {
|
|
11
|
+
return getModelMetadataSync(modelId, provider) || {};
|
|
12
|
+
} catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hydratedModel(provider, model = {}) {
|
|
18
|
+
const modelId = clean(model?.id || model);
|
|
19
|
+
const meta = metadataFor(provider, modelId);
|
|
20
|
+
const base = model && typeof model === 'object' ? model : { id: modelId };
|
|
21
|
+
return {
|
|
22
|
+
...base,
|
|
23
|
+
contextWindow: meta.contextWindow || base?.contextWindow || null,
|
|
24
|
+
outputTokens: meta.outputTokens || base?.outputTokens || null,
|
|
25
|
+
supportsWebSearch: base?.supportsWebSearch === true || meta.supportsWebSearch === true,
|
|
26
|
+
supportsFunctionCalling: base?.supportsFunctionCalling === true || meta.supportsFunctionCalling === true,
|
|
27
|
+
supportsPromptCaching: base?.supportsPromptCaching === true || meta.supportsPromptCaching === true,
|
|
28
|
+
supportsReasoning: base?.supportsReasoning === true || meta.supportsReasoning === true,
|
|
29
|
+
reasoningOptions: Array.isArray(base?.reasoningOptions) && base.reasoningOptions.length ? base.reasoningOptions : (meta.reasoningOptions || []),
|
|
30
|
+
reasoningContentField: base?.reasoningContentField || meta.reasoningContentField || null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
7
33
|
|
|
8
34
|
export function createQuickModelRows({
|
|
9
35
|
getRoute,
|
|
@@ -32,12 +58,19 @@ export function createQuickModelRows({
|
|
|
32
58
|
const key = `${provider}:${model}`;
|
|
33
59
|
if (seen.has(key)) return;
|
|
34
60
|
seen.add(key);
|
|
61
|
+
const meta = metadataFor(provider, model);
|
|
35
62
|
const row = providerModelCacheRow(provider, {
|
|
36
63
|
id: model,
|
|
37
64
|
name: routeLike.modelDisplay || routeLike.display || model,
|
|
38
65
|
display: routeLike.modelDisplay || routeLike.display || model,
|
|
66
|
+
contextWindow: meta.contextWindow || null,
|
|
67
|
+
outputTokens: meta.outputTokens || null,
|
|
39
68
|
latest: routeLike.latest === true,
|
|
40
|
-
supportsReasoning: !!routeLike.effort,
|
|
69
|
+
supportsReasoning: !!routeLike.effort || meta.supportsReasoning === true,
|
|
70
|
+
supportsFunctionCalling: meta.supportsFunctionCalling === true,
|
|
71
|
+
supportsPromptCaching: meta.supportsPromptCaching === true,
|
|
72
|
+
reasoningOptions: meta.reasoningOptions || [],
|
|
73
|
+
reasoningContentField: meta.reasoningContentField || null,
|
|
41
74
|
mode: 'chat',
|
|
42
75
|
});
|
|
43
76
|
rows.push(row);
|
|
@@ -57,19 +90,21 @@ export function createQuickModelRows({
|
|
|
57
90
|
if (!providerName || !modelId || !isSearchCapableProvider(providerName)) return;
|
|
58
91
|
const key = `${providerName}:${modelId}`;
|
|
59
92
|
if (seen.has(key)) return;
|
|
93
|
+
const resolved = hydratedModel(providerName, model);
|
|
60
94
|
const row = providerModelCacheRow(providerName, {
|
|
61
95
|
id: modelId,
|
|
62
|
-
name:
|
|
63
|
-
display:
|
|
64
|
-
contextWindow:
|
|
65
|
-
outputTokens:
|
|
66
|
-
latest:
|
|
96
|
+
name: resolved?.name || resolved?.display || modelId,
|
|
97
|
+
display: resolved?.display || resolved?.name || modelId,
|
|
98
|
+
contextWindow: resolved?.contextWindow || null,
|
|
99
|
+
outputTokens: resolved?.outputTokens || null,
|
|
100
|
+
latest: resolved?.latest === true,
|
|
67
101
|
supportsWebSearch: true,
|
|
68
|
-
supportsFunctionCalling:
|
|
69
|
-
supportsPromptCaching:
|
|
70
|
-
supportsReasoning:
|
|
71
|
-
reasoningLevels: Array.isArray(
|
|
72
|
-
reasoningOptions: Array.isArray(
|
|
102
|
+
supportsFunctionCalling: resolved?.supportsFunctionCalling === true,
|
|
103
|
+
supportsPromptCaching: resolved?.supportsPromptCaching === true,
|
|
104
|
+
supportsReasoning: resolved?.supportsReasoning === true,
|
|
105
|
+
reasoningLevels: Array.isArray(resolved?.reasoningLevels) ? resolved.reasoningLevels : undefined,
|
|
106
|
+
reasoningOptions: Array.isArray(resolved?.reasoningOptions) ? resolved.reasoningOptions : [],
|
|
107
|
+
reasoningContentField: resolved?.reasoningContentField || null,
|
|
73
108
|
mode: 'chat',
|
|
74
109
|
});
|
|
75
110
|
if (row.supportsWebSearch !== true) return;
|
|
@@ -1,46 +1,47 @@
|
|
|
1
|
-
// Static quick-pick model
|
|
2
|
-
//
|
|
1
|
+
// Static quick-pick model ids surfaced during onboarding / search setup.
|
|
2
|
+
// Limits are intentionally not hardcoded here: rows are hydrated from live
|
|
3
|
+
// provider caches and provider-scoped metadata during catalog construction.
|
|
3
4
|
|
|
4
5
|
export const ONBOARDING_VERSION = 1;
|
|
5
6
|
|
|
6
7
|
export const QUICK_SEARCH_MODELS = Object.freeze({
|
|
7
8
|
'openai-oauth': [
|
|
8
|
-
{ id: 'gpt-5.5', display: 'GPT-5.5', latest: true
|
|
9
|
-
{ id: 'gpt-5.4', display: 'GPT-5.4', latest: true
|
|
10
|
-
{ id: 'gpt-5', display: 'GPT-5'
|
|
11
|
-
{ id: 'gpt-4.1', display: 'GPT-4.1'
|
|
9
|
+
{ id: 'gpt-5.5', display: 'GPT-5.5', latest: true },
|
|
10
|
+
{ id: 'gpt-5.4', display: 'GPT-5.4', latest: true },
|
|
11
|
+
{ id: 'gpt-5', display: 'GPT-5' },
|
|
12
|
+
{ id: 'gpt-4.1', display: 'GPT-4.1' },
|
|
12
13
|
],
|
|
13
14
|
openai: [
|
|
14
|
-
{ id: 'gpt-5.5', display: 'GPT-5.5', latest: true
|
|
15
|
-
{ id: 'gpt-5.4', display: 'GPT-5.4', latest: true
|
|
16
|
-
{ id: 'gpt-5', display: 'GPT-5'
|
|
17
|
-
{ id: 'gpt-4.1', display: 'GPT-4.1'
|
|
18
|
-
{ id: 'gpt-4o', display: 'GPT-4o'
|
|
15
|
+
{ id: 'gpt-5.5', display: 'GPT-5.5', latest: true },
|
|
16
|
+
{ id: 'gpt-5.4', display: 'GPT-5.4', latest: true },
|
|
17
|
+
{ id: 'gpt-5', display: 'GPT-5' },
|
|
18
|
+
{ id: 'gpt-4.1', display: 'GPT-4.1' },
|
|
19
|
+
{ id: 'gpt-4o', display: 'GPT-4o' },
|
|
19
20
|
],
|
|
20
21
|
'grok-oauth': [
|
|
21
|
-
{ id: 'grok-4.3', display: 'Grok 4.3', latest: true
|
|
22
|
-
{ id: 'grok-4.20', display: 'Grok 4.20'
|
|
23
|
-
{ id: 'grok-4', display: 'Grok 4'
|
|
22
|
+
{ id: 'grok-4.3', display: 'Grok 4.3', latest: true },
|
|
23
|
+
{ id: 'grok-4.20', display: 'Grok 4.20' },
|
|
24
|
+
{ id: 'grok-4', display: 'Grok 4' },
|
|
24
25
|
],
|
|
25
26
|
xai: [
|
|
26
|
-
{ id: 'grok-4.3', display: 'Grok 4.3', latest: true
|
|
27
|
-
{ id: 'grok-4.20', display: 'Grok 4.20'
|
|
28
|
-
{ id: 'grok-4', display: 'Grok 4'
|
|
27
|
+
{ id: 'grok-4.3', display: 'Grok 4.3', latest: true },
|
|
28
|
+
{ id: 'grok-4.20', display: 'Grok 4.20' },
|
|
29
|
+
{ id: 'grok-4', display: 'Grok 4' },
|
|
29
30
|
],
|
|
30
31
|
gemini: [
|
|
31
|
-
{ id: 'gemini-3-pro', display: 'Gemini 3 Pro', latest: true
|
|
32
|
-
{ id: 'gemini-2.5-pro', display: 'Gemini 2.5 Pro'
|
|
33
|
-
{ id: 'gemini-2.5-flash', display: 'Gemini 2.5 Flash'
|
|
34
|
-
{ id: 'gemini-2.0-flash', display: 'Gemini 2.0 Flash'
|
|
32
|
+
{ id: 'gemini-3-pro', display: 'Gemini 3 Pro', latest: true },
|
|
33
|
+
{ id: 'gemini-2.5-pro', display: 'Gemini 2.5 Pro' },
|
|
34
|
+
{ id: 'gemini-2.5-flash', display: 'Gemini 2.5 Flash' },
|
|
35
|
+
{ id: 'gemini-2.0-flash', display: 'Gemini 2.0 Flash' },
|
|
35
36
|
],
|
|
36
37
|
'anthropic-oauth': [
|
|
37
|
-
{ id: 'claude-opus-4-8', display: 'Claude Opus 4.8', latest: true
|
|
38
|
-
{ id: 'claude-sonnet-4-6', display: 'Claude Sonnet 4.6', latest: true
|
|
39
|
-
{ id: 'claude-haiku-4-5-20251001', display: 'Claude Haiku 4.5'
|
|
38
|
+
{ id: 'claude-opus-4-8', display: 'Claude Opus 4.8', latest: true },
|
|
39
|
+
{ id: 'claude-sonnet-4-6', display: 'Claude Sonnet 4.6', latest: true },
|
|
40
|
+
{ id: 'claude-haiku-4-5-20251001', display: 'Claude Haiku 4.5' },
|
|
40
41
|
],
|
|
41
42
|
anthropic: [
|
|
42
|
-
{ id: 'claude-opus-4-8', display: 'Claude Opus 4.8', latest: true
|
|
43
|
-
{ id: 'claude-sonnet-4-6', display: 'Claude Sonnet 4.6', latest: true
|
|
44
|
-
{ id: 'claude-haiku-4-5-20251001', display: 'Claude Haiku 4.5'
|
|
43
|
+
{ id: 'claude-opus-4-8', display: 'Claude Opus 4.8', latest: true },
|
|
44
|
+
{ id: 'claude-sonnet-4-6', display: 'Claude Sonnet 4.6', latest: true },
|
|
45
|
+
{ id: 'claude-haiku-4-5-20251001', display: 'Claude Haiku 4.5' },
|
|
45
46
|
],
|
|
46
47
|
});
|