mixdog 1.1.0 → 1.1.1
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 +13 -6
- package/package.json +1 -1
- package/scripts/latest-guard.mjs +63 -0
- package/scripts/lib/run-node-tests.mjs +103 -27
- package/scripts/lib/test-failure-summary.mjs +7 -2
- package/scripts/merge-mac-update-feed.mjs +107 -0
- package/scripts/mobile-release.mjs +210 -0
- package/scripts/post-release-check.mjs +353 -0
- package/scripts/publish-asset-release.mjs +43 -0
- package/scripts/release-guard.mjs +63 -0
- package/scripts/test.mjs +29 -7
- package/src/defaults/shipped-history.json +2 -0
- package/src/defaults/skills/computer-use/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +1 -0
- package/src/lib/keychain-cjs.cjs +582 -17
- package/src/runtime/agent/orchestrator/config-storage.mjs +32 -4
- package/src/runtime/agent/orchestrator/config.mjs +18 -0
- package/src/runtime/agent/orchestrator/context/instruction-files.mjs +130 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/lib/open-login-url.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +254 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +218 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +239 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +233 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-login.mjs +247 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +225 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +542 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +14 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +99 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +4 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +2 -7
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-call-ids.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +19 -11
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/turn-effort.mjs +18 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +11 -8
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +9 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +245 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +13 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +49 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -7
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +61 -8
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +28 -3
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +73 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +17 -6
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +23 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +93 -43
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +32 -6
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +124 -81
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +19 -4
- package/src/runtime/computer-bridge/client.mjs +25 -1
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +84 -0
- package/src/runtime/computer-bridge/linux-readiness.d.mts +52 -0
- package/src/runtime/computer-bridge/linux-readiness.mjs +411 -0
- package/src/runtime/computer-bridge/test-linux-display.mjs +5 -0
- package/src/runtime/effort-judge/compact-tokenizer.mjs +13 -8
- package/src/runtime/effort-judge/judge-input.mjs +5 -2
- package/src/runtime/effort-judge/model-install.mjs +9 -3
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/memory/lib/embedding-worker.mjs +9 -3
- package/src/runtime/memory/lib/pg/supervisor.mjs +3 -0
- package/src/runtime/shared/config.mjs +50 -2
- package/src/runtime/shared/developer-options.mjs +7 -0
- package/src/runtime/shared/instruction-files-mode.mjs +9 -0
- package/src/runtime/shared/llm/model-catalog.mjs +2 -1
- package/src/runtime/shared/llm/openai-daybreak.mjs +79 -0
- package/src/runtime/shared/pristine-execution-contract.json +9 -0
- package/src/runtime/shared/pristine-execution.mjs +1 -1
- package/src/runtime/shared/provider-accounts.mjs +2 -0
- package/src/runtime/shared/resource-admission.mjs +10 -3
- package/src/runtime/shared/schedule-model-ref.mjs +11 -0
- package/src/runtime/shared/session-approval-wait.mjs +35 -0
- package/src/runtime/shared/shell-display.mjs +4 -2
- package/src/runtime/shared/turn-worktree-snapshot.mjs +11 -10
- package/src/session-runtime/boot/apis.mjs +5 -0
- package/src/session-runtime/model-recency.mjs +3 -0
- package/src/session-runtime/provider-auth/login.mjs +10 -10
- package/src/session-runtime/provider-auth/setup.mjs +22 -2
- package/src/session-runtime/provider-models/row-hydration.mjs +12 -4
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +5 -0
- package/src/session-runtime/schedule-session-run.mjs +3 -1
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +55 -14
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +15 -3
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/approval-wait.mjs +97 -0
- package/src/session-runtime/services/agent-tool/worker-index/mutation-batch.mjs +4 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +58 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +69 -15
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +2 -0
- package/src/session-runtime/services/channel-admin.mjs +4 -2
- package/src/session-runtime/services/provider-admin.mjs +80 -17
- package/src/session-runtime/services/seeds.mjs +7 -10
- package/src/session-runtime/services/usage-dashboard-model.mjs +6 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +15 -0
- package/src/session-runtime/setup-tool/executor.mjs +5 -0
- package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
- package/src/session-runtime/tool-policy-refresh.mjs +3 -0
- package/src/session-runtime/webhook-session-run.mjs +3 -1
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/tui/app/model-options.mjs +13 -1
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +10 -4
- package/src/tui/app/provider-setup/oauth-actions.mjs +5 -0
- package/src/tui/dist/index.mjs +84 -34
- package/src/tui/session/context-state.mjs +2 -0
- package/src/tui/session/live-share/owner.mjs +17 -0
- package/src/tui/session/oauth-flows.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +8 -8
- package/src/tui/session/session-api/routes.mjs +2 -2
- package/src/tui/session/session-api/settings.mjs +1 -0
- package/src/tui/session/tool-card-results/grouped-result.mjs +11 -2
- package/src/tui/session/transcript-spill/spill-dir.mjs +22 -6
- package/src/tui/spinner-meta.mjs +26 -10
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Muse Code subscription provider (Meta Muse Spark models).
|
|
3
|
+
*
|
|
4
|
+
* Inference authenticates with the subscription-minted Model API key (never
|
|
5
|
+
* the Meta account token) against https://api.meta.ai/v1 over the Responses
|
|
6
|
+
* wire, delegated to the shared OpenAI-compatible transport. The key never
|
|
7
|
+
* expires and cannot be refreshed: a 401 means the user must sign in again.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
11
|
+
import { fileMtimeMs } from './lib/oauth-token-utils.mjs';
|
|
12
|
+
import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
|
|
13
|
+
import {
|
|
14
|
+
API_VERSION,
|
|
15
|
+
MODEL_API_BASE_URL,
|
|
16
|
+
getOwnTokenPath,
|
|
17
|
+
loadTokens,
|
|
18
|
+
requestMuseCodeKey,
|
|
19
|
+
scrubMuseSecrets,
|
|
20
|
+
} from './muse-code-oauth-tokens.mjs';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
hasMuseCodeOAuthCredentials,
|
|
24
|
+
describeMuseCodeOAuthCredentials,
|
|
25
|
+
forgetMuseCodeOAuthCredentials,
|
|
26
|
+
} from './muse-code-oauth-tokens.mjs';
|
|
27
|
+
export { beginOAuthLogin, loginOAuth } from './muse-code-oauth-login.mjs';
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_MODEL = 'muse-spark-1.3';
|
|
30
|
+
const FIVE_LEVELS = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
|
31
|
+
const SIX_LEVELS = Object.freeze([...FIVE_LEVELS, 'max']);
|
|
32
|
+
const effortOptions = (levels) => [{ type: 'effort', values: [...levels] }];
|
|
33
|
+
// Image and voice generation ids are served by the same catalog but are not chat models.
|
|
34
|
+
const NON_CHAT_MODEL_RE = /^muse-(image|voice)-/i;
|
|
35
|
+
const SPARK_CONTEXT = 1_048_576;
|
|
36
|
+
const SPARK_MAX_OUTPUT = 131_072;
|
|
37
|
+
const STANDARD_COST = { inputCostPerM: 1.25, outputCostPerM: 4.25, cacheReadCostPerM: 0.15, cacheWriteCostPerM: 0 };
|
|
38
|
+
const CONTRIBUTOR_COST = { inputCostPerM: 0.1, outputCostPerM: 0.2, cacheReadCostPerM: 0.002, cacheWriteCostPerM: 0 };
|
|
39
|
+
|
|
40
|
+
// Seeded roster: the discovery endpoint lists bare ids, so limits, pricing and
|
|
41
|
+
// the effort ladder come from here. Only the 1.3 revisions accept `max`.
|
|
42
|
+
const SEEDS = Object.freeze({
|
|
43
|
+
'muse-spark-1.1': { name: 'Muse Spark 1.1', levels: FIVE_LEVELS, cost: STANDARD_COST },
|
|
44
|
+
'muse-spark-1.2': { name: 'Muse Spark 1.2', levels: FIVE_LEVELS, cost: STANDARD_COST },
|
|
45
|
+
'muse-spark-1.2-contributor': { name: 'Muse Spark 1.2 (C)', levels: FIVE_LEVELS, cost: CONTRIBUTOR_COST },
|
|
46
|
+
'muse-spark-1.3': { name: 'Muse Spark 1.3', levels: SIX_LEVELS, cost: STANDARD_COST },
|
|
47
|
+
'muse-spark-1.3-contributor': { name: 'Muse Spark 1.3 (C)', levels: SIX_LEVELS, cost: CONTRIBUTOR_COST },
|
|
48
|
+
});
|
|
49
|
+
const SEED_MODELS = Object.freeze(Object.keys(SEEDS));
|
|
50
|
+
|
|
51
|
+
function seedRow(id) {
|
|
52
|
+
const seed = SEEDS[id];
|
|
53
|
+
return {
|
|
54
|
+
id,
|
|
55
|
+
name: seed.name,
|
|
56
|
+
provider: 'muse-code-oauth',
|
|
57
|
+
family: 'muse-spark',
|
|
58
|
+
contextWindow: SPARK_CONTEXT,
|
|
59
|
+
outputTokens: SPARK_MAX_OUTPUT,
|
|
60
|
+
supportsReasoning: true,
|
|
61
|
+
supportsVision: true,
|
|
62
|
+
supportsFunctionCalling: true,
|
|
63
|
+
reasoningLevels: [...seed.levels],
|
|
64
|
+
reasoningOptions: effortOptions(seed.levels),
|
|
65
|
+
...seed.cost,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Revisioned Spark ids the seed does not list inherit the newest seeded
|
|
70
|
+
// revision of their tier (limits, vision, pricing) but take the five-level
|
|
71
|
+
// ladder: only reviewed seeds advertise `max`.
|
|
72
|
+
const SPARK_ID_RE = /^muse-spark-(\d+)\.(\d+)(?:\.(\d+))?(-contributor)?$/i;
|
|
73
|
+
|
|
74
|
+
function revisionOf(id) {
|
|
75
|
+
const m = SPARK_ID_RE.exec(id);
|
|
76
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3] || 0)] : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function compareRevision(a, b) {
|
|
80
|
+
for (let i = 0; i < 3; i += 1) if (a[i] !== b[i]) return a[i] - b[i];
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function lineageRow(id) {
|
|
85
|
+
if (SEEDS[id]) return null;
|
|
86
|
+
const match = SPARK_ID_RE.exec(id);
|
|
87
|
+
if (!match) return null;
|
|
88
|
+
const contributor = Boolean(match[4]);
|
|
89
|
+
let template = null;
|
|
90
|
+
let templateRevision = null;
|
|
91
|
+
for (const seedId of SEED_MODELS) {
|
|
92
|
+
if (SPARK_ID_RE.exec(seedId)[4] ? !contributor : contributor) continue;
|
|
93
|
+
const candidate = revisionOf(seedId);
|
|
94
|
+
if (!templateRevision || compareRevision(candidate, templateRevision) > 0) {
|
|
95
|
+
template = seedId;
|
|
96
|
+
templateRevision = candidate;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!template) return null;
|
|
100
|
+
const revision =
|
|
101
|
+
match[3] && Number(match[3]) !== 0 ? `${match[1]}.${match[2]}.${match[3]}` : `${match[1]}.${match[2]}`;
|
|
102
|
+
return {
|
|
103
|
+
...seedRow(template),
|
|
104
|
+
id,
|
|
105
|
+
name: `Muse Spark ${revision}${contributor ? ' (C)' : ''}`,
|
|
106
|
+
reasoningLevels: [...FIVE_LEVELS],
|
|
107
|
+
reasoningOptions: effortOptions(FIVE_LEVELS),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Seeds win for the ids they cover; lineage rows yield to discovered metadata.
|
|
112
|
+
// A name supplied by discovery is always kept. Other discovered rows keep
|
|
113
|
+
// their own metadata and only gain the default effort ladder when they report none.
|
|
114
|
+
function museRow(id, discovered) {
|
|
115
|
+
const discoveredName =
|
|
116
|
+
typeof discovered?.name === 'string' && discovered.name && discovered.name !== id ? discovered.name : '';
|
|
117
|
+
if (SEEDS[id]) return { ...(discovered || {}), ...seedRow(id), ...(discoveredName ? { name: discoveredName } : {}) };
|
|
118
|
+
const lineage = lineageRow(id);
|
|
119
|
+
const row = {
|
|
120
|
+
id,
|
|
121
|
+
name: id,
|
|
122
|
+
provider: 'muse-code-oauth',
|
|
123
|
+
family: 'muse-spark',
|
|
124
|
+
...(lineage || {}),
|
|
125
|
+
...(discovered || {}),
|
|
126
|
+
};
|
|
127
|
+
if (discoveredName) row.name = discoveredName;
|
|
128
|
+
else if (lineage) row.name = lineage.name;
|
|
129
|
+
if (!row.reasoningOptions?.length) {
|
|
130
|
+
row.reasoningOptions = effortOptions(FIVE_LEVELS);
|
|
131
|
+
row.reasoningLevels = [...FIVE_LEVELS];
|
|
132
|
+
}
|
|
133
|
+
return row;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const EFFORT_DOWNGRADE = { max: 'xhigh' };
|
|
137
|
+
const SUMMARY_MODES = new Set(['auto', 'detailed', 'concise']);
|
|
138
|
+
|
|
139
|
+
// Cache identities beyond the wire limit become a bounded hash identity.
|
|
140
|
+
function normalizeCacheKey(value) {
|
|
141
|
+
const text = String(value ?? '').trim();
|
|
142
|
+
if (!text) return '';
|
|
143
|
+
const wellFormed = text.toWellFormed();
|
|
144
|
+
if (wellFormed.length <= 64) return wellFormed;
|
|
145
|
+
return `pc_${createHash('sha256').update(wellFormed).digest('hex').slice(0, 32)}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Final request shaping on the Responses wire (installed through the shared
|
|
149
|
+
// transport's opt-in params hook).
|
|
150
|
+
function shapeResponsesParams(params, { opts, modelInfo }, config = {}) {
|
|
151
|
+
delete params.parallel_tool_calls;
|
|
152
|
+
const modelMax = Number(modelInfo?.outputTokens) > 0 ? Number(modelInfo.outputTokens) : SPARK_MAX_OUTPUT;
|
|
153
|
+
if (params.max_output_tokens) params.max_output_tokens = Math.min(params.max_output_tokens, modelMax);
|
|
154
|
+
|
|
155
|
+
delete params.reasoning;
|
|
156
|
+
const requested = String(opts.effort ?? config.reasoningEffort ?? '')
|
|
157
|
+
.trim()
|
|
158
|
+
.toLowerCase();
|
|
159
|
+
const allowed = modelInfo?.reasoningOptions?.find((o) => o?.type === 'effort')?.values || FIVE_LEVELS;
|
|
160
|
+
const forcedOff = opts.forceReasoningOff === true || requested === 'none' || requested === 'off';
|
|
161
|
+
if (forcedOff) {
|
|
162
|
+
// Explicit off: the only disable level the wire defines, with no encrypted replay.
|
|
163
|
+
params.reasoning = { effort: 'none' };
|
|
164
|
+
} else {
|
|
165
|
+
if (opts.disableReasoning === true) {
|
|
166
|
+
// Disabling without a request selects the model's lowest supported effort.
|
|
167
|
+
if (!requested && allowed.length) params.reasoning = { effort: allowed[0] };
|
|
168
|
+
} else if (requested || opts.reasoningSummary !== undefined) {
|
|
169
|
+
// A summary request alone still engages reasoning at the default effort.
|
|
170
|
+
const wanted = requested || 'medium';
|
|
171
|
+
const effort = allowed.includes(wanted) ? wanted : EFFORT_DOWNGRADE[wanted];
|
|
172
|
+
if (effort && allowed.includes(effort)) {
|
|
173
|
+
params.reasoning = { effort };
|
|
174
|
+
const summary = opts.reasoningSummary === undefined ? 'auto' : opts.reasoningSummary;
|
|
175
|
+
if (summary !== null) params.reasoning.summary = SUMMARY_MODES.has(summary) ? summary : 'auto';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Caller includes survive (copied, deduped); encrypted reasoning is added
|
|
181
|
+
// only when reasoning is not forced off and the model and caller allow it.
|
|
182
|
+
const include = Array.isArray(opts.include) ? opts.include.filter((v) => typeof v === 'string' && v) : [];
|
|
183
|
+
if (!forcedOff && modelInfo?.supportsReasoning !== false && opts.includeEncryptedReasoning !== false) {
|
|
184
|
+
include.push('reasoning.encrypted_content');
|
|
185
|
+
}
|
|
186
|
+
if (include.length) params.include = [...new Set(include)];
|
|
187
|
+
else delete params.include;
|
|
188
|
+
|
|
189
|
+
for (const [field, value] of [
|
|
190
|
+
['temperature', opts.temperature],
|
|
191
|
+
['top_p', opts.topP ?? opts.top_p],
|
|
192
|
+
['top_k', opts.topK ?? opts.top_k],
|
|
193
|
+
['min_p', opts.minP ?? opts.min_p],
|
|
194
|
+
['presence_penalty', opts.presencePenalty ?? opts.presence_penalty],
|
|
195
|
+
['repetition_penalty', opts.repetitionPenalty ?? opts.repetition_penalty],
|
|
196
|
+
]) {
|
|
197
|
+
if (typeof value === 'number' && Number.isFinite(value)) params[field] = value;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (opts.cacheRetention !== 'none') {
|
|
201
|
+
const key = normalizeCacheKey(opts.promptCacheKey ?? opts.sessionId);
|
|
202
|
+
if (key) params.prompt_cache_key = key;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (opts.extraBody && typeof opts.extraBody === 'object' && !Array.isArray(opts.extraBody)) {
|
|
206
|
+
Object.assign(params, opts.extraBody);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function resetAtMs(value) {
|
|
211
|
+
if (typeof value === 'string') {
|
|
212
|
+
const ms = Date.parse(value);
|
|
213
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
214
|
+
}
|
|
215
|
+
const n = Number(value);
|
|
216
|
+
if (!Number.isFinite(n) || n <= 0) return 0;
|
|
217
|
+
return n < 1e12 ? n * 1000 : n;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
221
|
+
|
|
222
|
+
function usageStatus(usedFraction) {
|
|
223
|
+
if (usedFraction >= 1) return 'exhausted';
|
|
224
|
+
if (usedFraction >= 0.9) return 'warning';
|
|
225
|
+
return 'ok';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function usageWindow({ id, label, longLabel, durationMs }, entry, tier) {
|
|
229
|
+
const used = entry?.used_percent;
|
|
230
|
+
if (!entry || typeof used !== 'number' || !Number.isFinite(used) || used < 0) return null;
|
|
231
|
+
const resetAt = resetAtMs(entry.resets_at);
|
|
232
|
+
const usedPct = Math.min(100, used);
|
|
233
|
+
const usedFraction = usedPct / 100;
|
|
234
|
+
return {
|
|
235
|
+
id,
|
|
236
|
+
label,
|
|
237
|
+
longLabel,
|
|
238
|
+
source: 'muse-code-key',
|
|
239
|
+
usedPct: Math.round(usedPct * 100) / 100,
|
|
240
|
+
usedFraction,
|
|
241
|
+
remainingFraction: 1 - usedFraction,
|
|
242
|
+
status: usageStatus(usedFraction),
|
|
243
|
+
...(durationMs ? { durationMs } : {}),
|
|
244
|
+
...(tier ? { tier } : {}),
|
|
245
|
+
...(resetAt ? { resetAt } : {}),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function rollingWindowSpec(minutes) {
|
|
250
|
+
const m = Number(minutes);
|
|
251
|
+
if (!Number.isFinite(m) || m <= 0) return { id: 'rolling', label: 'W', longLabel: 'Rolling Window' };
|
|
252
|
+
const rounded = Math.round(m);
|
|
253
|
+
const hours = rounded / 60;
|
|
254
|
+
const whole = rounded % 60 === 0;
|
|
255
|
+
const count = whole ? hours : rounded;
|
|
256
|
+
const unit = whole ? 'Hour' : 'Minute';
|
|
257
|
+
return {
|
|
258
|
+
id: `${rounded}m`,
|
|
259
|
+
label: whole ? `${hours}H` : `${rounded}M`,
|
|
260
|
+
longLabel: `${count} ${count === 1 ? unit : `${unit}s`}`,
|
|
261
|
+
durationMs: m * 60_000,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Map the key endpoint's `subs_usage` onto the shared quota-window shape. */
|
|
266
|
+
export function quotaWindowsFromSubsUsage(subsUsage, tier) {
|
|
267
|
+
const windows = [];
|
|
268
|
+
const rolling = usageWindow(rollingWindowSpec(subsUsage?.window?.window_duration_mins), subsUsage?.window, tier);
|
|
269
|
+
if (rolling) windows.push(rolling);
|
|
270
|
+
const weekly = usageWindow(
|
|
271
|
+
{ id: '1w', label: '7D', longLabel: 'Weekly', durationMs: WEEK_MS },
|
|
272
|
+
subsUsage?.weekly,
|
|
273
|
+
tier
|
|
274
|
+
);
|
|
275
|
+
if (weekly) windows.push(weekly);
|
|
276
|
+
return windows;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function reauthError(detail, status = 401) {
|
|
280
|
+
return Object.assign(new Error(`[muse-code-oauth] ${detail} — open /providers in mixdog to sign in again`), {
|
|
281
|
+
reauthRequired: true,
|
|
282
|
+
status,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const USAGE_FAILURE_BACKOFF_MS = 5 * 60_000;
|
|
287
|
+
|
|
288
|
+
// Scrub the key from errors where the transport first surfaces them, so the
|
|
289
|
+
// shared retry reporter (which logs the original error) never sees it.
|
|
290
|
+
// Exact known secret values only: normal stream output must pass through unchanged.
|
|
291
|
+
function replaceExact(text, secrets) {
|
|
292
|
+
let out = text;
|
|
293
|
+
for (const s of secrets) if (s) out = out.split(s).join('[REDACTED]');
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// `exact` true replaces only the known secret values; false additionally applies
|
|
298
|
+
// the generic field-pattern redaction (error objects and messages).
|
|
299
|
+
function scrubDeep(value, secrets, exact, seen = new WeakSet()) {
|
|
300
|
+
if (typeof value === 'string') return exact ? replaceExact(value, secrets) : scrubMuseSecrets(value, secrets);
|
|
301
|
+
if (!value || typeof value !== 'object' || seen.has(value)) return value;
|
|
302
|
+
seen.add(value);
|
|
303
|
+
if (value instanceof Error && typeof value.message === 'string') {
|
|
304
|
+
value.message = scrubMuseSecrets(value.message, secrets);
|
|
305
|
+
}
|
|
306
|
+
for (const k of Object.keys(value)) {
|
|
307
|
+
try {
|
|
308
|
+
value[k] = scrubDeep(value[k], secrets, exact, seen);
|
|
309
|
+
} catch {
|
|
310
|
+
/* read-only field */
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function redactClientErrors(client, apiKey, accessToken) {
|
|
317
|
+
const responses = client?.responses;
|
|
318
|
+
const secrets = [apiKey, accessToken].filter(Boolean);
|
|
319
|
+
const marker = secrets.join('|');
|
|
320
|
+
if (!responses || responses.create?.museRedacting === marker) return;
|
|
321
|
+
const create = responses.create.bind(responses);
|
|
322
|
+
const redact = (err) => scrubDeep(err, secrets, false);
|
|
323
|
+
const wrapped = async (...args) => {
|
|
324
|
+
try {
|
|
325
|
+
const stream = await create(...args);
|
|
326
|
+
if (stream && typeof stream[Symbol.asyncIterator] === 'function') {
|
|
327
|
+
const iterate = stream[Symbol.asyncIterator].bind(stream);
|
|
328
|
+
stream[Symbol.asyncIterator] = () => {
|
|
329
|
+
const it = iterate();
|
|
330
|
+
return {
|
|
331
|
+
next: async (...a) => {
|
|
332
|
+
try {
|
|
333
|
+
const step = await it.next(...a);
|
|
334
|
+
// Any event payload can become an exception downstream; scrub every
|
|
335
|
+
// string field at the source rather than by event name.
|
|
336
|
+
if (step && !step.done) scrubDeep(step.value, secrets, true);
|
|
337
|
+
return step;
|
|
338
|
+
} catch (err) {
|
|
339
|
+
throw redact(err);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
return: (...a) => it.return?.(...a),
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return stream;
|
|
347
|
+
} catch (err) {
|
|
348
|
+
throw redact(err);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
wrapped.museRedacting = marker;
|
|
352
|
+
responses.create = wrapped;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export class MuseCodeOAuthProvider {
|
|
356
|
+
// No document block on this wire: a PDF is lowered to text before the request is built.
|
|
357
|
+
nativePdf = false;
|
|
358
|
+
static inputExcludesCache = false;
|
|
359
|
+
name = 'muse-code-oauth';
|
|
360
|
+
config;
|
|
361
|
+
tokens = null;
|
|
362
|
+
_inner = null;
|
|
363
|
+
_innerKey = null;
|
|
364
|
+
_lastDiskScan = 0;
|
|
365
|
+
_usageFailure = null;
|
|
366
|
+
_usageLastGood = null;
|
|
367
|
+
|
|
368
|
+
_clearUsageState() {
|
|
369
|
+
this._usageFailure = null;
|
|
370
|
+
this._usageLastGood = null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
constructor(config) {
|
|
374
|
+
this.config = config || {};
|
|
375
|
+
this.tokens = loadTokens();
|
|
376
|
+
try {
|
|
377
|
+
preloadOpenAICompatRuntime();
|
|
378
|
+
} catch {
|
|
379
|
+
/* authoritative send surfaces load failures */
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// The stored key is the whole credential; pick up a newer login from disk
|
|
384
|
+
// but never mint or refresh anything here.
|
|
385
|
+
async ensureAuth() {
|
|
386
|
+
const modified = fileMtimeMs(getOwnTokenPath());
|
|
387
|
+
if (modified === 0) {
|
|
388
|
+
// A missing credential file is signed out, whatever was cached.
|
|
389
|
+
if (this.tokens) this._clearUsageState();
|
|
390
|
+
this.tokens = null;
|
|
391
|
+
this._lastDiskScan = 0;
|
|
392
|
+
} else if (!this.tokens || modified !== this._lastDiskScan) {
|
|
393
|
+
this.tokens = loadTokens();
|
|
394
|
+
this._lastDiskScan = modified;
|
|
395
|
+
}
|
|
396
|
+
if (!this.tokens?.apiKey) {
|
|
397
|
+
throw new Error('[muse-code-oauth] credentials not found — open /providers in mixdog to sign in first');
|
|
398
|
+
}
|
|
399
|
+
return this.tokens;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
_ensureInner(apiKey) {
|
|
403
|
+
if (this._inner && this._innerKey === apiKey) return this._inner;
|
|
404
|
+
this._inner = new OpenAICompatProvider('muse-code-oauth', {
|
|
405
|
+
...this.config,
|
|
406
|
+
apiKey,
|
|
407
|
+
baseURL: MODEL_API_BASE_URL,
|
|
408
|
+
// JSON accept for discovery; streaming requests override it per call.
|
|
409
|
+
extraHeaders: { Accept: 'application/json', 'x-api-version': API_VERSION },
|
|
410
|
+
// Never inherit OPENAI_ORG_ID / OPENAI_PROJECT_ID from the environment.
|
|
411
|
+
clientOptions: { organization: null, project: null },
|
|
412
|
+
responsesSystemAsInstructions: true,
|
|
413
|
+
responsesParamsHook: (params, ctx) => shapeResponsesParams(params, ctx, this.config),
|
|
414
|
+
});
|
|
415
|
+
this._innerKey = apiKey;
|
|
416
|
+
// The Responses builder reads limits and the effort ladder from the inner
|
|
417
|
+
// provider's cached model row; seeds fill in what discovery omits.
|
|
418
|
+
const innerRow = this._inner.getCachedModelInfo.bind(this._inner);
|
|
419
|
+
this._inner.getCachedModelInfo = (model) => {
|
|
420
|
+
const discovered = innerRow(model);
|
|
421
|
+
return discovered || SEEDS[model] || lineageRow(model) ? museRow(model, discovered) : null;
|
|
422
|
+
};
|
|
423
|
+
return this._inner;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async send(messages, model, tools, sendOpts) {
|
|
427
|
+
const { apiKey, accessToken } = await this.ensureAuth();
|
|
428
|
+
const inner = this._ensureInner(apiKey);
|
|
429
|
+
redactClientErrors(inner.client, apiKey, accessToken);
|
|
430
|
+
// The endpoint accepts only tool_choice "auto"; the field is omitted entirely.
|
|
431
|
+
const { toolChoice: _unsupported, ...opts } = sendOpts || {};
|
|
432
|
+
try {
|
|
433
|
+
// _doSend bypasses the compat 401 handler, which re-reads a static config key.
|
|
434
|
+
return await inner._doSend(messages, model || DEFAULT_MODEL, tools, {
|
|
435
|
+
...opts,
|
|
436
|
+
compatWireApi: 'responses',
|
|
437
|
+
requestHeaders: { Accept: 'text/event-stream', ...(opts.requestHeaders || {}) },
|
|
438
|
+
});
|
|
439
|
+
} catch (err) {
|
|
440
|
+
noteErrorUsage(err);
|
|
441
|
+
if (Number(err?.httpStatus || err?.status) === 401) {
|
|
442
|
+
const detail = scrubMuseSecrets(String(err?.message || ''), [apiKey])
|
|
443
|
+
.trim()
|
|
444
|
+
.slice(0, 300);
|
|
445
|
+
throw reauthError(`Model API key rejected${detail ? ` (${detail})` : ''}`);
|
|
446
|
+
}
|
|
447
|
+
if (err && typeof err.message === 'string') err.message = scrubMuseSecrets(err.message, [apiKey]);
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async listModels() {
|
|
453
|
+
const { apiKey } = await this.ensureAuth();
|
|
454
|
+
const inner = this._ensureInner(apiKey);
|
|
455
|
+
let items;
|
|
456
|
+
try {
|
|
457
|
+
items = await inner._fetchModelItems();
|
|
458
|
+
} catch {
|
|
459
|
+
// Discovery failed: fall back to the seeded roster.
|
|
460
|
+
return SEED_MODELS.map((id) => museRow(id));
|
|
461
|
+
}
|
|
462
|
+
// A successful roster is authoritative, even when it holds no chat models.
|
|
463
|
+
const rows = (Array.isArray(items) ? items : [])
|
|
464
|
+
.filter((m) => typeof m?.id === 'string' && m.id && !NON_CHAT_MODEL_RE.test(m.id))
|
|
465
|
+
.map((m) => {
|
|
466
|
+
const contextWindow = Number(m.context_window ?? m.max_context_window ?? m.context_length ?? 0);
|
|
467
|
+
const outputTokens = Number(m.max_output_tokens ?? m.output_tokens ?? 0);
|
|
468
|
+
const name = [m.name, m.display_name].find((v) => typeof v === 'string' && v.trim());
|
|
469
|
+
return museRow(m.id, {
|
|
470
|
+
...(name ? { name: name.trim() } : {}),
|
|
471
|
+
...(contextWindow > 0 ? { contextWindow } : {}),
|
|
472
|
+
...(outputTokens > 0 ? { outputTokens } : {}),
|
|
473
|
+
...(typeof m.created === 'number' ? { created: m.created } : {}),
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
inner._enrichedModels = rows;
|
|
477
|
+
return rows;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
getCachedModelInfo(model) {
|
|
481
|
+
return this._inner?.getCachedModelInfo?.(model) || (SEEDS[model] || lineageRow(model) ? museRow(model) : null);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** Subscription window/weekly usage, read from the key endpoint without onboarding. */
|
|
485
|
+
async getUsageSnapshot() {
|
|
486
|
+
const { accessToken, accountId, email: storedEmail } = await this.ensureAuth();
|
|
487
|
+
if (!accessToken) return null;
|
|
488
|
+
// State is bound to the credential: a replaced login never sees another account's cooldown.
|
|
489
|
+
const identity = `${accountId || ''}:${createHash('sha256').update(accessToken).digest('hex').slice(0, 16)}`;
|
|
490
|
+
if (this._usageFailure && this._usageFailure.identity !== identity) this._clearUsageState();
|
|
491
|
+
if (this._usageLastGood && this._usageLastGood.identity !== identity) this._usageLastGood = null;
|
|
492
|
+
const lastGood = () => this._usageLastGood?.snapshot ?? null;
|
|
493
|
+
// A recent failure suppresses re-querying the rate-limited endpoint.
|
|
494
|
+
if (this._usageFailure && Date.now() < this._usageFailure.until) {
|
|
495
|
+
if (this._usageFailure.error) throw this._usageFailure.error;
|
|
496
|
+
return lastGood();
|
|
497
|
+
}
|
|
498
|
+
const fail = (error) => {
|
|
499
|
+
// ±25% jitter keeps accounts from retrying in lockstep.
|
|
500
|
+
const cooldown = USAGE_FAILURE_BACKOFF_MS * (1 + (Math.random() * 0.5 - 0.25));
|
|
501
|
+
this._usageFailure = { identity, until: Date.now() + cooldown, error };
|
|
502
|
+
// A rejected credential must not keep showing stale quota.
|
|
503
|
+
if (error) this._usageLastGood = null;
|
|
504
|
+
return error;
|
|
505
|
+
};
|
|
506
|
+
let payload;
|
|
507
|
+
try {
|
|
508
|
+
payload = await requestMuseCodeKey(accessToken);
|
|
509
|
+
} catch (err) {
|
|
510
|
+
if (err?.status === 401 || err?.status === 403) {
|
|
511
|
+
throw fail(reauthError(`account session rejected (${err.status})`, err.status));
|
|
512
|
+
}
|
|
513
|
+
fail(null);
|
|
514
|
+
return lastGood();
|
|
515
|
+
}
|
|
516
|
+
if (payload.is_subs_active === false) throw fail(reauthError('Muse Code subscription is inactive', 403));
|
|
517
|
+
this._usageFailure = null;
|
|
518
|
+
const tier = payload.subs_tier_name?.trim() || payload.subs_tier_id?.trim() || undefined;
|
|
519
|
+
const quotaWindows = quotaWindowsFromSubsUsage(payload.subs_usage, tier);
|
|
520
|
+
if (!quotaWindows.length) return null;
|
|
521
|
+
const email = payload.user_email?.trim().toLowerCase() || storedEmail || undefined;
|
|
522
|
+
const { api_key: _apiKey, ...raw } = payload;
|
|
523
|
+
const snapshot = {
|
|
524
|
+
provider: 'muse-code-oauth',
|
|
525
|
+
model: null,
|
|
526
|
+
source: 'muse-code-key',
|
|
527
|
+
fetchedAt: Date.now(),
|
|
528
|
+
quotaWindows,
|
|
529
|
+
...(tier ? { tier } : {}),
|
|
530
|
+
...(email ? { email } : {}),
|
|
531
|
+
accountId: payload.user_id?.trim() || accountId || email || undefined,
|
|
532
|
+
raw,
|
|
533
|
+
rawKeys: Object.keys(raw).sort(),
|
|
534
|
+
};
|
|
535
|
+
this._usageLastGood = { identity, snapshot };
|
|
536
|
+
return snapshot;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async isAvailable() {
|
|
540
|
+
return Boolean(this.tokens?.apiKey || loadTokens()?.apiKey);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
5
5
|
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
6
6
|
import { cursorTokenExpiry } from './cursor-auth.mjs';
|
|
7
|
+
import { isValidMuseCredentialDocument } from './muse-code-oauth-tokens.mjs';
|
|
7
8
|
import { developerOptionEnabled, developerOptionForProvider } from '../../../shared/developer-options.mjs';
|
|
8
9
|
|
|
9
10
|
const ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join(resolvePluginData(), 'anthropic-oauth-credentials.json');
|
|
@@ -166,6 +167,28 @@ function grokOAuthState() {
|
|
|
166
167
|
);
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
function mimoOAuthState() {
|
|
171
|
+
const paths = [
|
|
172
|
+
boundProviderAuthPath('mimo-oauth') ||
|
|
173
|
+
process.env.MIMO_OAUTH_CREDENTIALS_PATH ||
|
|
174
|
+
join(resolvePluginData(), 'mimo-oauth.json'),
|
|
175
|
+
];
|
|
176
|
+
return memoProbe('mimo-oauth', paths, () =>
|
|
177
|
+
resolveProbeState(paths, (docs) => docs.some((own) => !!own?.apiKey))
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function museCodeOAuthState() {
|
|
182
|
+
const paths = [
|
|
183
|
+
boundProviderAuthPath('muse-code-oauth') ||
|
|
184
|
+
process.env.MUSE_CODE_OAUTH_CREDENTIALS_PATH ||
|
|
185
|
+
join(resolvePluginData(), 'muse-code-oauth.json'),
|
|
186
|
+
];
|
|
187
|
+
return memoProbe('muse-code-oauth', paths, () =>
|
|
188
|
+
resolveProbeState(paths, (docs) => docs.some(isValidMuseCredentialDocument))
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
169
192
|
// Antigravity needs a resolved Cloud project alongside the tokens: without it
|
|
170
193
|
// every request 400s, so a half-finished login must not enable the provider.
|
|
171
194
|
function antigravityOAuthState() {
|
|
@@ -228,6 +251,8 @@ const OAUTH_PROBE_STATES = new Map([
|
|
|
228
251
|
['anthropic-oauth', anthropicOAuthState],
|
|
229
252
|
['openai-oauth', openAIOAuthState],
|
|
230
253
|
['grok-oauth', grokOAuthState],
|
|
254
|
+
['muse-code-oauth', museCodeOAuthState],
|
|
255
|
+
['mimo-oauth', mimoOAuthState],
|
|
231
256
|
['cursor-oauth', cursorOAuthState],
|
|
232
257
|
['antigravity-oauth', antigravityOAuthState],
|
|
233
258
|
]);
|
|
@@ -865,7 +865,10 @@ async function measureUsage(provider, providerObj, routeInfo) {
|
|
|
865
865
|
if (provider === 'anthropic-oauth') return await fetchAnthropicUsage(providerObj);
|
|
866
866
|
if (provider === 'grok-oauth') return await fetchGrokUsage(providerObj, routeInfo);
|
|
867
867
|
if (
|
|
868
|
-
(provider === 'cursor-oauth' ||
|
|
868
|
+
(provider === 'cursor-oauth' ||
|
|
869
|
+
provider === 'cursor-api' ||
|
|
870
|
+
provider === 'antigravity-oauth' ||
|
|
871
|
+
provider === 'muse-code-oauth') &&
|
|
869
872
|
typeof providerObj?.getUsageSnapshot === 'function'
|
|
870
873
|
) {
|
|
871
874
|
return await providerObj.getUsageSnapshot();
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* per-family "latest" pass. No module state; openai-oauth.mjs re-exports
|
|
7
7
|
* _displayCodexModel for existing importers.
|
|
8
8
|
*/
|
|
9
|
+
import { accessProgramOnly, catalogAccessPrograms } from '../../../shared/llm/openai-daybreak.mjs';
|
|
9
10
|
|
|
10
11
|
// OAuth catalog returns dated ids (gpt-5.4-mini-2026-03-17). Strip the trailing
|
|
11
12
|
// -YYYY-MM-DD to get the version alias (gpt-5.4-mini). Unknown shapes pass
|
|
@@ -59,6 +60,9 @@ export function _normalizeCodexModel(m) {
|
|
|
59
60
|
? m.additional_speed_tiers.map((t) => String(t || '').trim()).filter(Boolean)
|
|
60
61
|
: [];
|
|
61
62
|
const reasoningLevels = (m?.supported_reasoning_levels || []).map((r) => r.effort);
|
|
63
|
+
// Explicit access programs this caller may select (Daybreak Blue); absent
|
|
64
|
+
// when the catalog does not say, which is not the same as an empty list.
|
|
65
|
+
const accessPrograms = catalogAccessPrograms(m?.available_access_programs);
|
|
62
66
|
// Catalog ids are version aliases without separate display dating.
|
|
63
67
|
return {
|
|
64
68
|
id,
|
|
@@ -97,6 +101,15 @@ export function _normalizeCodexModel(m) {
|
|
|
97
101
|
serviceTiers,
|
|
98
102
|
defaultServiceTier: m?.default_service_tier || null,
|
|
99
103
|
additionalSpeedTiers,
|
|
104
|
+
...(accessPrograms ? { accessPrograms } : {}),
|
|
105
|
+
// The catalog's own replacement for a model being retired.
|
|
106
|
+
...(m?.upgrade?.model
|
|
107
|
+
? { upgrade: { model: String(m.upgrade.model), retirementAt: m.upgrade.retirement_at || null } }
|
|
108
|
+
: {}),
|
|
109
|
+
// Out of the pickers: the catalog hides it, or it is a standalone
|
|
110
|
+
// access-program variant that a route toggle on a regular model replaces.
|
|
111
|
+
// Saved routes on it keep resolving.
|
|
112
|
+
...(m?.visibility === 'hide' || accessProgramOnly({ accessPrograms }) ? { hidden: true } : {}),
|
|
100
113
|
};
|
|
101
114
|
}
|
|
102
115
|
|
|
@@ -55,7 +55,7 @@ function resolveReasoning(provider, useModel, opts) {
|
|
|
55
55
|
|
|
56
56
|
/** The request body: lowered transcript plus the per-turn knobs (output cap,
|
|
57
57
|
* tools and tool choice, reasoning effort). */
|
|
58
|
-
function buildCompatResponsesParams({ provider, useModel, input, previousResponseId, tools, opts }) {
|
|
58
|
+
function buildCompatResponsesParams({ provider, useModel, input, previousResponseId, tools, opts, instructions }) {
|
|
59
59
|
const params = {
|
|
60
60
|
model: useModel,
|
|
61
61
|
input,
|
|
@@ -73,6 +73,8 @@ function buildCompatResponsesParams({ provider, useModel, input, previousRespons
|
|
|
73
73
|
applyCompatToolChoice(params, opts);
|
|
74
74
|
const reasoning = resolveReasoning(provider, useModel, opts);
|
|
75
75
|
if (reasoning) params.reasoning = reasoning;
|
|
76
|
+
// Opt-in system-as-instructions: set before the hook so the hook can override it.
|
|
77
|
+
if (instructions) params.instructions = instructions;
|
|
76
78
|
// Opt-in per-provider final shaping (a provider passes the hook in its config).
|
|
77
79
|
const hook = provider.config?.responsesParamsHook;
|
|
78
80
|
if (typeof hook === 'function') {
|
|
@@ -163,11 +165,17 @@ export async function sendCompatResponses(provider, messages, useModel, tools, o
|
|
|
163
165
|
// Opt-in: system prompts travel as top-level `instructions`, not input items.
|
|
164
166
|
includeSystem: provider.config?.responsesSystemAsInstructions !== true,
|
|
165
167
|
});
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
const instructions =
|
|
169
|
+
provider.config?.responsesSystemAsInstructions === true ? xaiSystemInstructions(messages) : '';
|
|
170
|
+
const params = buildCompatResponsesParams({
|
|
171
|
+
provider,
|
|
172
|
+
useModel,
|
|
173
|
+
input,
|
|
174
|
+
previousResponseId,
|
|
175
|
+
tools,
|
|
176
|
+
opts,
|
|
177
|
+
instructions,
|
|
178
|
+
});
|
|
171
179
|
|
|
172
180
|
try {
|
|
173
181
|
opts.onStageChange?.('requesting');
|