mixdog 1.0.10 → 1.0.11
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/package.json +3 -3
- package/src/rules/agent/40-cycle1-agent.md +19 -23
- package/src/rules/lead/LEAD.md +5 -5
- package/src/rules/shared/05-tool-calls.md +6 -5
- package/src/rules/shared/60-verification.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +37 -13
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +6 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +234 -12
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +22 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +9 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +77 -0
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -6
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-image-input.mjs +34 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +94 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +670 -198
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +9 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +14 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +24 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +10 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +9 -1
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +13 -11
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +17 -9
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +40 -71
- package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -7
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +54 -12
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +27 -5
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +8 -51
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +55 -2
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +23 -5
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +79 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -4
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +7 -6
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +9 -19
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +85 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +51 -28
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +47 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +10 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +18 -7
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-roots.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/work.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +13 -1
- package/src/runtime/attachments/limits.mjs +20 -0
- package/src/runtime/{agent/orchestrator/tools/builtin/read-office-files.mjs → attachments/office-extract.mjs} +356 -125
- package/src/runtime/attachments/pdf-extract.mjs +2 -2
- package/src/runtime/attachments/store.mjs +232 -72
- package/src/runtime/browser-bridge/client.mjs +3 -3
- package/src/runtime/effort-judge/compact-tokenizer.mjs +282 -0
- package/src/runtime/effort-judge/judge-client.mjs +194 -0
- package/src/runtime/effort-judge/judge-worker.mjs +106 -0
- package/src/runtime/local-provider/input-capabilities.mjs +30 -12
- package/src/runtime/media/jobs.mjs +0 -1
- package/src/runtime/media/media-usage.mjs +2 -1
- package/src/runtime/memory/lib/embedding-model-config.mjs +6 -6
- package/src/runtime/memory/lib/embedding-model-files.mjs +44 -0
- package/src/runtime/memory/lib/embedding-tokenizer.mjs +253 -0
- package/src/runtime/memory/lib/embedding-worker.mjs +97 -103
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +40 -218
- package/src/runtime/memory/lib/memory-embed.mjs +0 -9
- package/src/runtime/memory/lib/memory.mjs +34 -23
- package/src/runtime/memory/tool-defs.mjs +2 -1
- package/src/runtime/shared/automation-attachments.mjs +9 -7
- package/src/runtime/shared/environment-error.mjs +14 -0
- package/src/runtime/shared/error-presentation.mjs +10 -2
- package/src/runtime/shared/inline-file-kind.mjs +28 -2
- package/src/runtime/shared/llm/model-catalog.mjs +17 -3
- package/src/runtime/shared/llm/quota-value-estimate.mjs +33 -18
- package/src/runtime/shared/llm/usage-accounting.mjs +8 -5
- package/src/runtime/shared/llm/usage-ledger-quota.mjs +29 -10
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +47 -4
- package/src/runtime/shared/read-image-resize.mjs +56 -0
- package/src/runtime/shared/tool-card-model/displayed-result.mjs +7 -5
- package/src/runtime/shared/tool-card-model.mjs +2 -1
- package/src/runtime/shared/tool-result-summary.mjs +120 -59
- package/src/runtime/shared/turn-review-reply.mjs +63 -0
- package/src/runtime/shared/turn-snapshot.mjs +8 -5
- package/src/runtime/shared/turn-worktree-snapshot.mjs +56 -19
- package/src/session-runtime/boot/apis.mjs +5 -0
- package/src/session-runtime/boot/providers.mjs +2 -0
- package/src/session-runtime/boot/session.mjs +5 -0
- package/src/session-runtime/boot/shared.mjs +2 -3
- package/src/session-runtime/native-web-search/tool-shape.mjs +8 -8
- package/src/session-runtime/native-web-search.mjs +6 -8
- package/src/session-runtime/provider-models/row-hydration.mjs +9 -0
- package/src/session-runtime/services/provider-admin.mjs +17 -3
- package/src/session-runtime/services/usage-stats-model.mjs +37 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +29 -4
- package/src/session-runtime/setup-tool/executor.mjs +10 -1
- package/src/session-runtime/webhook-session-run.mjs +0 -9
- package/src/standalone/daemon-stored-session-views.mjs +15 -0
- package/src/standalone/session-service/entries/session-loads.mjs +11 -3
- package/src/standalone/session-service/entries.mjs +1 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +31 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +2 -1
- package/src/standalone/session-service.mjs +2 -0
- package/src/tui/app/usage-context-panels/context-model.mjs +4 -1
- package/src/tui/app/usage-context-panels.mjs +3 -1
- package/src/tui/app/use-prompt-queue-history/restore-to-prompt.mjs +1 -0
- package/src/tui/components/ContextPanel.jsx +2 -0
- package/src/tui/dist/index.mjs +135 -69
- package/src/tui/paste-attachments.mjs +2 -1
- package/src/tui/session/session-api/intake/abort.mjs +35 -3
- package/src/tui/session/session-api/integrations.mjs +2 -59
- package/src/tui/session/session-api/media.mjs +1 -1
- package/src/tui/session/session-flow/queue/take-entries.mjs +21 -11
- package/src/tui/session/session-flow/steering.mjs +17 -3
- package/src/tui/session/session-flow.mjs +1 -0
- package/src/tui/session/turn.mjs +18 -4
- package/src/vendor/statusline/src/gateway/route-meta.mjs +2 -0
- package/scripts/memory-layer2-check.mjs +0 -186
|
@@ -22,9 +22,12 @@ import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
|
22
22
|
import { loadAnthropic, _normalizeAnthropicModel, _setApiKeyCatalogMirror } from './anthropic-messages.mjs';
|
|
23
23
|
import { ANTHROPIC_VERSION, MODELS } from './lib/anthropic-models.mjs';
|
|
24
24
|
import { streamCallbacks } from './lib/send-callbacks.mjs';
|
|
25
|
+
import { preparePdfTextForProvider } from './media-normalization.mjs';
|
|
25
26
|
export { _test, _toAnthropicMessagesForTest } from './anthropic-messages.mjs';
|
|
26
27
|
|
|
27
28
|
export class AnthropicProvider {
|
|
29
|
+
// Takes a PDF as a native document block (decided by this adapter's wire protocol).
|
|
30
|
+
nativePdf = true;
|
|
28
31
|
// Anthropic reports usage.input_tokens EXCLUDING cache_read/cache_creation
|
|
29
32
|
// (those are separate fields), so the live context-window footprint must
|
|
30
33
|
// add cache_read back. See providerInputExcludesCache() in registry.mjs.
|
|
@@ -77,6 +80,9 @@ export class AnthropicProvider {
|
|
|
77
80
|
// dispatch with an aborted-mid-flight tool_use would otherwise hit
|
|
78
81
|
// the provider as a hard 400 (`tool_use ids ... without tool_result`).
|
|
79
82
|
messages = sanitizeToolPairs(messages);
|
|
83
|
+
// The flag alone decides: an instance that takes no native PDF (a custom
|
|
84
|
+
// gateway, OpenCode Go's Anthropic route) gets the PDF as text.
|
|
85
|
+
messages = await preparePdfTextForProvider(messages, this);
|
|
80
86
|
try {
|
|
81
87
|
return await this._doSend(messages, model, tools, sendOpts);
|
|
82
88
|
} catch (err) {
|
|
@@ -11,6 +11,7 @@ import { createServer } from 'node:http';
|
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
12
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
13
13
|
import { createOAuthPkce, parseOAuthCodeInput } from './lib/oauth-pkce.mjs';
|
|
14
|
+
import { OAUTH_PAGE_CONTENT_TYPE, oauthErrorHtml, oauthSuccessHtml } from './lib/oauth-page.mjs';
|
|
14
15
|
import {
|
|
15
16
|
AUTH_URL,
|
|
16
17
|
CALLBACK_HOST,
|
|
@@ -34,9 +35,10 @@ import {
|
|
|
34
35
|
} from './antigravity-oauth-tokens.mjs';
|
|
35
36
|
|
|
36
37
|
const ONBOARD_INTERVAL_MS = 1_000;
|
|
37
|
-
const CALLBACK_FAILURE_HTML =
|
|
38
|
-
'
|
|
39
|
-
'
|
|
38
|
+
const CALLBACK_FAILURE_HTML = oauthErrorHtml(
|
|
39
|
+
'Antigravity sign-in was not completed.',
|
|
40
|
+
'Return to Mixdog for the error details and any account verification link.'
|
|
41
|
+
);
|
|
40
42
|
|
|
41
43
|
function extractProjectId(payload) {
|
|
42
44
|
return typeof payload?.cloudaicompanionProject === 'string' ? payload.cloudaicompanionProject : '';
|
|
@@ -353,7 +355,7 @@ export async function beginOAuthLogin({
|
|
|
353
355
|
const authorizationError = requestUrl.searchParams.get('error');
|
|
354
356
|
if (authorizationError) {
|
|
355
357
|
const detail = requestUrl.searchParams.get('error_description') || authorizationError;
|
|
356
|
-
res.writeHead(400, { 'Content-Type':
|
|
358
|
+
res.writeHead(400, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
357
359
|
res.end(CALLBACK_FAILURE_HTML);
|
|
358
360
|
// A late denial must not cancel an already accepted code.
|
|
359
361
|
if (!exchangePromise) {
|
|
@@ -369,12 +371,12 @@ export async function beginOAuthLogin({
|
|
|
369
371
|
}
|
|
370
372
|
try {
|
|
371
373
|
await acceptCode(code);
|
|
372
|
-
res.writeHead(200, { 'Content-Type':
|
|
373
|
-
res.end('
|
|
374
|
+
res.writeHead(200, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
375
|
+
res.end(oauthSuccessHtml('Antigravity'));
|
|
374
376
|
} catch {
|
|
375
377
|
// The shared exchange reports its error through waitForCallback.
|
|
376
378
|
// Do not interpolate provider-controlled text into browser HTML.
|
|
377
|
-
res.writeHead(500, { 'Content-Type':
|
|
379
|
+
res.writeHead(500, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
378
380
|
res.end(CALLBACK_FAILURE_HTML);
|
|
379
381
|
}
|
|
380
382
|
});
|
|
@@ -63,6 +63,8 @@ async function storedAuth(options) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export class AntigravityOAuthProvider {
|
|
66
|
+
// Takes a PDF as a native document block (decided by this adapter's wire protocol).
|
|
67
|
+
nativePdf = true;
|
|
66
68
|
// usageMetadata.promptTokenCount is the total, cached tokens included.
|
|
67
69
|
static inputExcludesCache = false;
|
|
68
70
|
name = 'antigravity-oauth';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID, createHash } from 'node:crypto';
|
|
2
2
|
import { createProviderReplay, providerReplayItems } from './lib/provider-replay.mjs';
|
|
3
|
-
import { toGeminiContents,
|
|
3
|
+
import { toGeminiContents, toGeminiRequestTools, toGeminiToolConfig } from './gemini-schema.mjs';
|
|
4
4
|
import { geminiThinkingConfig } from './gemini-thinking.mjs';
|
|
5
5
|
|
|
6
6
|
const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator';
|
|
@@ -105,8 +105,9 @@ export function buildAntigravityRequest(messages, model, tools, opts = {}, proje
|
|
|
105
105
|
};
|
|
106
106
|
const request = { contents };
|
|
107
107
|
if (systemText) request.systemInstruction = { role: 'user', parts: [{ text: systemText }] };
|
|
108
|
+
const requestTools = toGeminiRequestTools(tools, opts.nativeTools);
|
|
109
|
+
if (requestTools.length) request.tools = requestTools;
|
|
108
110
|
if (tools?.length) {
|
|
109
|
-
request.tools = [toGeminiTools(tools)];
|
|
110
111
|
request.toolConfig = toGeminiToolConfig(opts.toolChoice) || {
|
|
111
112
|
functionCallingConfig: { mode: antigravityFunctionCallingMode() },
|
|
112
113
|
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Auto effort: turns the effort judge's difficulty distribution into this
|
|
2
|
+
// turn's effort by moving along the model's effort ladder from a base effort.
|
|
3
|
+
// Every turn starts again from the base, so a run of hard turns never
|
|
4
|
+
// ratchets the effort upward.
|
|
5
|
+
//
|
|
6
|
+
// The base is `medium` on every model: raised only when the request needs it,
|
|
7
|
+
// lowered when it is easy. The ladder is the model's supported efforts from
|
|
8
|
+
// `low` up to the tier below the top: `none`/`minimal` switch thinking off and
|
|
9
|
+
// `max`/`ultra` are reserved for an explicit user choice, so auto never picks
|
|
10
|
+
// them. A chosen `max`/`ultra` is left untouched.
|
|
11
|
+
|
|
12
|
+
const EFFORT_ORDER = Object.freeze(['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
|
|
13
|
+
const FLOOR_INDEX = EFFORT_ORDER.indexOf('low');
|
|
14
|
+
const RESERVED_TOP = new Set(['max', 'ultra']);
|
|
15
|
+
|
|
16
|
+
export const AUTO_EFFORT_BASE = 'medium';
|
|
17
|
+
|
|
18
|
+
/** The effort auto moves from: `medium`, unless the user chose `max`/`ultra`. */
|
|
19
|
+
export function autoEffortBase(chosen) {
|
|
20
|
+
const effort = String(chosen ?? '')
|
|
21
|
+
.trim()
|
|
22
|
+
.toLowerCase();
|
|
23
|
+
return RESERVED_TOP.has(effort) ? effort : AUTO_EFFORT_BASE;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const AUTO_EFFORT_MODES = Object.freeze(['off', 'observe', 'on']);
|
|
27
|
+
|
|
28
|
+
// Below this top-class probability the judge is unsure and the default stays.
|
|
29
|
+
export const AUTO_EFFORT_MIN_CONFIDENCE = 0.4;
|
|
30
|
+
// xhigh costs far more than high, so "very hard" needs a firmer judge;
|
|
31
|
+
// below this it is treated as "hard". 0.65 minimised the eval cost with a
|
|
32
|
+
// false xhigh weighted three times a missed one.
|
|
33
|
+
export const AUTO_EFFORT_VERY_HARD_CONFIDENCE = 0.65;
|
|
34
|
+
|
|
35
|
+
// The judge rates four levels, each one ladder step from the base: easy -1
|
|
36
|
+
// (low), normal 0 (medium), hard +1 (high), very hard +2 (xhigh).
|
|
37
|
+
export const AUTO_EFFORT_LEVELS = Object.freeze(['easy', 'normal', 'hard', 'very hard']);
|
|
38
|
+
const STEP_OF_LEVEL = [-1, 0, 1, 2];
|
|
39
|
+
|
|
40
|
+
export function normalizeAutoEffortMode(value) {
|
|
41
|
+
const mode = String(value ?? '')
|
|
42
|
+
.trim()
|
|
43
|
+
.toLowerCase();
|
|
44
|
+
return AUTO_EFFORT_MODES.includes(mode) ? mode : 'off';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The efforts auto may choose for a model, in ascending order. */
|
|
48
|
+
export function autoEffortLadder(options) {
|
|
49
|
+
const supported = new Set((options || []).map((value) => String(value).trim().toLowerCase()));
|
|
50
|
+
return EFFORT_ORDER.filter(
|
|
51
|
+
(level, index) => supported.has(level) && index >= FLOOR_INDEX && !RESERVED_TOP.has(level)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Ladder step for a judge distribution over the four levels (index 0 = easy). */
|
|
56
|
+
export function judgedStep(probs) {
|
|
57
|
+
const values = Array.isArray(probs) && probs.length === AUTO_EFFORT_LEVELS.length ? probs.map(Number) : null;
|
|
58
|
+
if (!values || values.some((value) => !Number.isFinite(value))) return { step: 0, level: null, confidence: 0 };
|
|
59
|
+
const confidence = Math.max(...values);
|
|
60
|
+
if (confidence < AUTO_EFFORT_MIN_CONFIDENCE) return { step: 0, level: null, confidence };
|
|
61
|
+
let level = values.indexOf(confidence);
|
|
62
|
+
if (level === 3 && confidence < AUTO_EFFORT_VERY_HARD_CONFIDENCE) level = 2;
|
|
63
|
+
return { step: STEP_OF_LEVEL[level], level, confidence };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* This turn's effort, or null when auto does not apply (the default lies
|
|
68
|
+
* outside the ladder). `effort === base` when the judge keeps the default.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveAutoEffort({ base, options, probs }) {
|
|
71
|
+
const ladder = autoEffortLadder(options);
|
|
72
|
+
const at = ladder.indexOf(String(base ?? '').toLowerCase());
|
|
73
|
+
if (at < 0) return null;
|
|
74
|
+
const { step, level, confidence } = judgedStep(probs);
|
|
75
|
+
const effort = ladder[Math.max(0, Math.min(ladder.length - 1, at + step))];
|
|
76
|
+
return { base: ladder[at], effort, step, level, confidence: Number(confidence.toFixed(3)) };
|
|
77
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
toOpenAIMessages,
|
|
8
8
|
toOpenAITools,
|
|
9
9
|
} from './openai-compat-wire.mjs';
|
|
10
|
+
import { preparePdfTextForProvider } from './media-normalization.mjs';
|
|
10
11
|
import { consumeCompatChatCompletionStream } from './openai-compat-stream.mjs';
|
|
11
12
|
import { ensureChatToolPairs } from './lib/wire-pairing.mjs';
|
|
12
13
|
import { cursorTokenExpiry, exchangeCursorToken, resolveCursorOAuthAccessToken } from './cursor-auth.mjs';
|
|
@@ -28,10 +29,10 @@ async function loadCursorRuntime() {
|
|
|
28
29
|
return runtimePromise;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
function toCursorMessages(messages, providerName) {
|
|
32
|
+
function toCursorMessages(messages, providerName, { nativePdf } = {}) {
|
|
32
33
|
const output = [];
|
|
33
34
|
for (const message of messages || []) {
|
|
34
|
-
const converted = toOpenAIMessages([message], providerName);
|
|
35
|
+
const converted = toOpenAIMessages([message], providerName, { nativePdf });
|
|
35
36
|
if (message?.role !== 'tool') {
|
|
36
37
|
output.push(...converted);
|
|
37
38
|
continue;
|
|
@@ -544,6 +545,9 @@ function selectCursorVariant(group, { effort = null, fast = false } = {}) {
|
|
|
544
545
|
}
|
|
545
546
|
|
|
546
547
|
class CursorProviderBase {
|
|
548
|
+
// Cursor's chat wire has no document block: PDFs lower to text.
|
|
549
|
+
nativePdf = false;
|
|
550
|
+
|
|
547
551
|
static inputExcludesCache = false;
|
|
548
552
|
name;
|
|
549
553
|
config;
|
|
@@ -614,7 +618,7 @@ class CursorProviderBase {
|
|
|
614
618
|
// Wire-level pairing guard: a call whose result never committed
|
|
615
619
|
// (cancel/abort) is hard-rejected unpaired, so synthesize the
|
|
616
620
|
// missing tool messages on the assembled array.
|
|
617
|
-
messages: ensureChatToolPairs(toCursorMessages(messages, this.name)),
|
|
621
|
+
messages: ensureChatToolPairs(toCursorMessages(messages, this.name, { nativePdf: this.nativePdf })),
|
|
618
622
|
mixdog_session_id: sessionScope,
|
|
619
623
|
stream: true,
|
|
620
624
|
stream_options: { include_usage: true },
|
|
@@ -690,10 +694,12 @@ class CursorProviderBase {
|
|
|
690
694
|
sendOpts.sessionId || sendOpts.providerCacheKey || sendOpts.promptCacheKey || `cursor-call:${randomUUID()}`
|
|
691
695
|
);
|
|
692
696
|
const openAiTools = tools?.length ? toOpenAITools(tools) : undefined;
|
|
693
|
-
const dispatch = () => {
|
|
697
|
+
const dispatch = async () => {
|
|
694
698
|
noteRequestServiceTier(cursorSelectionIsFast(cursorSelection) ? 'fast' : '');
|
|
699
|
+
// Every attempt (retries included) prepares right before it lowers.
|
|
700
|
+
const prepared = await preparePdfTextForProvider(messages, this);
|
|
695
701
|
return this._dispatchChat(
|
|
696
|
-
this._chatBody(
|
|
702
|
+
this._chatBody(prepared, { cursorSelection, sessionScope, openAiTools, toolChoice: sendOpts.toolChoice }),
|
|
697
703
|
{ runtime, accessToken, signal, sendOpts, openAiTools }
|
|
698
704
|
);
|
|
699
705
|
};
|
|
@@ -114,7 +114,7 @@ async function discoverAnthropicModels(provider) {
|
|
|
114
114
|
signal: AbortSignal.timeout(10_000),
|
|
115
115
|
headers: { 'x-api-key': provider.apiKey || '', 'anthropic-version': ANTHROPIC_VERSION },
|
|
116
116
|
});
|
|
117
|
-
if (!res.ok) throw new Error(`${provider.name} models ${res.status}`);
|
|
117
|
+
if (!res.ok) throw Object.assign(new Error(`${provider.name} models ${res.status}`), { status: res.status });
|
|
118
118
|
const data = await res.json();
|
|
119
119
|
const items = Array.isArray(data?.data) ? data.data : [];
|
|
120
120
|
return items
|
|
@@ -143,6 +143,8 @@ export async function createCustomProvider(id, config) {
|
|
|
143
143
|
const Custom = withConfiguredModels(AnthropicProvider, discoverAnthropicModels);
|
|
144
144
|
// The Anthropic SDK appends /v1/messages itself; accept a /v1 base URL.
|
|
145
145
|
provider = new Custom({ ...engine, name: providerId, baseURL: cfg.baseURL.replace(/\/v1$/i, '') });
|
|
146
|
+
// An unknown third-party gateway behind the Anthropic wire: PDFs lower to text.
|
|
147
|
+
provider.nativePdf = false;
|
|
146
148
|
} else {
|
|
147
149
|
const { OpenAICompatProvider } = await import('./openai-compat.mjs');
|
|
148
150
|
const Base =
|
|
@@ -88,11 +88,13 @@ function validSnapshot(value, provider, model, mode) {
|
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
// `turnEffort` overrides the session's saved effort for this turn only (auto
|
|
92
|
+
// effort); the saved default itself is never changed here.
|
|
93
|
+
export function prepareTurnEffortConfiguration(session, provider, turnEffort) {
|
|
92
94
|
const config = provider?.config || {};
|
|
93
95
|
const opts = { ...config, modelParameters: session.modelParameters || {} };
|
|
94
96
|
const mode = effortConfigurationMode(session.provider, session.model, opts);
|
|
95
|
-
const effort = normalizedEffort(session.provider, session.model, session.effort);
|
|
97
|
+
const effort = normalizedEffort(session.provider, session.model, turnEffort ?? session.effort);
|
|
96
98
|
if (!mode || !EFFORTS.has(effort)) return null;
|
|
97
99
|
const first = (session.messages || []).find((message) =>
|
|
98
100
|
validSnapshot(message?.meta?.[META_KEY], session.provider, session.model, mode)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* gemini-schema.mjs.
|
|
10
10
|
*/
|
|
11
11
|
import { geminiThinkingConfig } from './gemini-thinking.mjs';
|
|
12
|
-
import { toGeminiContents,
|
|
12
|
+
import { toGeminiContents, toGeminiRequestTools, toGeminiToolConfig } from './gemini-schema.mjs';
|
|
13
13
|
|
|
14
14
|
// Request pieces shared by the cached REST path and the SDK path.
|
|
15
15
|
export function buildGeminiRequest(messages, useModel, tools, opts) {
|
|
@@ -29,11 +29,9 @@ export function buildGeminiRequest(messages, useModel, tools, opts) {
|
|
|
29
29
|
const contents = toGeminiContents(chatMsgs, useModel);
|
|
30
30
|
if (!contents.length) throw new Error('No messages to send');
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
nativeGeminiTools.length || functionGeminiTools.length ? [...nativeGeminiTools, ...functionGeminiTools] : undefined;
|
|
36
|
-
const toolConfig = functionGeminiTools.length ? toGeminiToolConfig(opts.toolChoice) : undefined;
|
|
32
|
+
const requestTools = toGeminiRequestTools(tools, opts.nativeTools);
|
|
33
|
+
const geminiTools = requestTools.length ? requestTools : undefined;
|
|
34
|
+
const toolConfig = tools?.length ? toGeminiToolConfig(opts.toolChoice) : undefined;
|
|
37
35
|
return { generationConfig, systemInstruction, contents, geminiTools, toolConfig };
|
|
38
36
|
}
|
|
39
37
|
|
|
@@ -593,6 +593,13 @@ export function toGeminiNativeTools(nativeTools) {
|
|
|
593
593
|
return out;
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
+
// The request `tools` array: hosted tools first, then one function-declaration
|
|
597
|
+
// entry when function tools are present. Empty when neither is present.
|
|
598
|
+
export function toGeminiRequestTools(tools, nativeTools) {
|
|
599
|
+
const functionTools = tools?.length ? [toGeminiTools(tools)] : [];
|
|
600
|
+
return [...toGeminiNativeTools(nativeTools), ...functionTools];
|
|
601
|
+
}
|
|
602
|
+
|
|
596
603
|
export function collectGeminiGroundingSources(candidate) {
|
|
597
604
|
const out = [];
|
|
598
605
|
const seen = new Set();
|
|
@@ -131,6 +131,8 @@ function throwIfGeminiAborted(signal) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
export class GeminiProvider {
|
|
134
|
+
// Takes a PDF as a native document block (decided by this adapter's wire protocol).
|
|
135
|
+
nativePdf = true;
|
|
134
136
|
// promptTokenCount is the total (cachedContentTokenCount is a subset), so
|
|
135
137
|
// input already includes cache. See registry.mjs.
|
|
136
138
|
static inputExcludesCache = false;
|
|
@@ -4,6 +4,7 @@ import { createServer } from 'node:http';
|
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
5
|
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
6
6
|
import { createOAuthPkce, parseOAuthCodeInput } from './lib/oauth-pkce.mjs';
|
|
7
|
+
import { OAUTH_PAGE_CONTENT_TYPE, oauthSuccessHtml } from './lib/oauth-page.mjs';
|
|
7
8
|
import {
|
|
8
9
|
CLIENT_ID,
|
|
9
10
|
SCOPE,
|
|
@@ -119,8 +120,8 @@ export async function beginOAuthLogin() {
|
|
|
119
120
|
res.end('Invalid');
|
|
120
121
|
return;
|
|
121
122
|
}
|
|
122
|
-
res.writeHead(200, { 'Content-Type':
|
|
123
|
-
res.end('
|
|
123
|
+
res.writeHead(200, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
124
|
+
res.end(oauthSuccessHtml('Grok'));
|
|
124
125
|
try {
|
|
125
126
|
const tokens = await exchangeAuthorizationCode({ discovery, pkce, code });
|
|
126
127
|
finish(tokens);
|
|
@@ -259,6 +259,8 @@ async function ensureLatestGrokModel(provider) {
|
|
|
259
259
|
let _modelRefreshInFlight = null;
|
|
260
260
|
|
|
261
261
|
export class GrokOAuthProvider {
|
|
262
|
+
// No document block on this wire: a PDF is lowered to text before the request is built.
|
|
263
|
+
nativePdf = false;
|
|
262
264
|
// OpenAI-compatible usage: prompt_tokens includes cached. See registry.mjs.
|
|
263
265
|
static inputExcludesCache = false;
|
|
264
266
|
name = 'grok-oauth';
|
|
@@ -2,14 +2,16 @@ import {
|
|
|
2
2
|
API_IMAGE_MAX_BASE64_SIZE,
|
|
3
3
|
IMAGE_MAX_WIDTH,
|
|
4
4
|
IMAGE_MAX_HEIGHT,
|
|
5
|
-
imageMetadataText,
|
|
6
5
|
resizeImageBuffer,
|
|
6
|
+
supersedingImageMetadataText,
|
|
7
7
|
} from '../../tools/builtin/read-image-resize.mjs';
|
|
8
8
|
|
|
9
|
+
import { isEnvironmentError } from '../../../../shared/environment-error.mjs';
|
|
10
|
+
|
|
9
11
|
const MAX_INPUT_BASE64_SIZE = Math.ceil((64 * 1024 * 1024) / 3) * 4;
|
|
10
12
|
|
|
11
13
|
export class AnthropicImagePreparationError extends Error {
|
|
12
|
-
constructor(path, detail, cause) {
|
|
14
|
+
constructor(path, detail, cause, failingImageData) {
|
|
13
15
|
super(
|
|
14
16
|
`Anthropic image preparation failed at ${path}: ${detail}. The original image and conversation were not changed.`,
|
|
15
17
|
{ cause }
|
|
@@ -17,12 +19,28 @@ export class AnthropicImagePreparationError extends Error {
|
|
|
17
19
|
this.name = 'AnthropicImagePreparationError';
|
|
18
20
|
this.code = 'ANTHROPIC_IMAGE_PREPARATION_FAILED';
|
|
19
21
|
this.status = 400;
|
|
22
|
+
// Identifies the rejected image for recovery; never part of the message.
|
|
23
|
+
Object.defineProperty(this, 'failingImageData', { value: failingImageData, enumerable: false });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The image may be fine; this machine cannot resize it (broken sharp runtime,
|
|
28
|
+
// out of memory...). Not an image rejection: recovery must not strip anything.
|
|
29
|
+
export class AnthropicImageRuntimeError extends Error {
|
|
30
|
+
constructor(path, cause) {
|
|
31
|
+
super(
|
|
32
|
+
`Anthropic image preparation unavailable at ${path}: image resizing is unavailable; repair the sharp runtime. The original image and conversation were not changed.`,
|
|
33
|
+
{ cause }
|
|
34
|
+
);
|
|
35
|
+
this.name = 'AnthropicImageRuntimeError';
|
|
36
|
+
this.code = 'ANTHROPIC_IMAGE_RUNTIME_UNAVAILABLE';
|
|
37
|
+
this.status = 400;
|
|
20
38
|
}
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
// Work on the lowered request, not stored history: this covers attachments,
|
|
24
|
-
// tool results, replayed images, and provider switches alike. Keep the
|
|
25
|
-
//
|
|
42
|
+
// tool results, replayed images, and provider switches alike. Keep the
|
|
43
|
+
// IMAGE_MAX_WIDTH x IMAGE_MAX_HEIGHT display box (1568px) independent of image count so crossing 20 images never invalidates
|
|
26
44
|
// an earlier image or changes its cached rendition.
|
|
27
45
|
export async function prepareAnthropicImages(messages, { signal } = {}) {
|
|
28
46
|
if (!Array.isArray(messages)) return messages;
|
|
@@ -64,12 +82,16 @@ export async function prepareAnthropicImages(messages, { signal } = {}) {
|
|
|
64
82
|
{ profile: 'anthropic' }
|
|
65
83
|
);
|
|
66
84
|
} catch (error) {
|
|
67
|
-
throw new
|
|
85
|
+
if (isEnvironmentError(error)) throw new AnthropicImageRuntimeError(partPath, error);
|
|
86
|
+
throw new AnthropicImagePreparationError(
|
|
87
|
+
partPath,
|
|
88
|
+
'image could not be decoded; reattach a valid image',
|
|
89
|
+
error,
|
|
90
|
+
source.data
|
|
91
|
+
);
|
|
68
92
|
}
|
|
69
93
|
signal?.throwIfAborted();
|
|
70
|
-
if (!resized)
|
|
71
|
-
throw new AnthropicImagePreparationError(partPath, 'image resizing is unavailable; repair the sharp runtime');
|
|
72
|
-
}
|
|
94
|
+
if (!resized) throw new AnthropicImageRuntimeError(partPath);
|
|
73
95
|
const dims = resized.dimensions;
|
|
74
96
|
if (
|
|
75
97
|
!dims?.displayWidth ||
|
|
@@ -80,7 +102,9 @@ export async function prepareAnthropicImages(messages, { signal } = {}) {
|
|
|
80
102
|
) {
|
|
81
103
|
throw new AnthropicImagePreparationError(
|
|
82
104
|
partPath,
|
|
83
|
-
'image still exceeds the supported size; use a smaller image'
|
|
105
|
+
'image still exceeds the supported size; use a smaller image',
|
|
106
|
+
undefined,
|
|
107
|
+
source.data
|
|
84
108
|
);
|
|
85
109
|
}
|
|
86
110
|
if (resized.data === source.data && resized.mimeType === source.media_type) {
|
|
@@ -89,10 +113,7 @@ export async function prepareAnthropicImages(messages, { signal } = {}) {
|
|
|
89
113
|
}
|
|
90
114
|
changed = true;
|
|
91
115
|
if (dims.originalWidth !== dims.displayWidth || dims.originalHeight !== dims.displayHeight) {
|
|
92
|
-
next.push({
|
|
93
|
-
type: 'text',
|
|
94
|
-
text: `${imageMetadataText(dims)} These display dimensions supersede any earlier display-size annotation for the following image.`,
|
|
95
|
-
});
|
|
116
|
+
next.push({ type: 'text', text: supersedingImageMetadataText(dims) });
|
|
96
117
|
}
|
|
97
118
|
// Cache markers stay attached to the same logical block. No
|
|
98
119
|
// sanitizer or reordering runs after this byte-preparation pass.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Shared browser pages for the loopback OAuth callbacks (success / failure).
|
|
2
|
+
|
|
3
|
+
const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="44 44 168 168" aria-hidden="true">
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="titanium-a" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
6
|
+
<stop offset="0%" stop-color="#ffffff"/><stop offset="50%" stop-color="#94a3b8"/><stop offset="100%" stop-color="#ffffff"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<linearGradient id="titanium-b" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
9
|
+
<stop offset="0%" stop-color="#cbd5e1"/><stop offset="50%" stop-color="#475569"/><stop offset="100%" stop-color="#cbd5e1"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="titanium-c" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
12
|
+
<stop offset="0%" stop-color="#94a3b8"/><stop offset="50%" stop-color="#334155"/><stop offset="100%" stop-color="#94a3b8"/>
|
|
13
|
+
</linearGradient>
|
|
14
|
+
</defs>
|
|
15
|
+
<g fill="none" stroke-width="22" stroke-linecap="round">
|
|
16
|
+
<path d="M116.2 61A68 68 0 0 1 191.9 104.7" stroke="url(#titanium-a)"/>
|
|
17
|
+
<path d="M116.2 61A68 68 0 0 1 191.9 104.7" transform="rotate(120 128 128)" stroke="url(#titanium-b)"/>
|
|
18
|
+
<path d="M116.2 61A68 68 0 0 1 191.9 104.7" transform="rotate(240 128 128)" stroke="url(#titanium-c)"/>
|
|
19
|
+
</g>
|
|
20
|
+
<polygon points="128,112 133,123 144,128 133,133 128,144 123,133 112,128 123,123" fill="#cbd5e1"/>
|
|
21
|
+
<circle cx="128" cy="128" r="3.5" fill="#07080b"/>
|
|
22
|
+
<circle cx="128" cy="128" r="1.5" fill="#ffffff"/>
|
|
23
|
+
</svg>`;
|
|
24
|
+
|
|
25
|
+
function escapeHtml(value) {
|
|
26
|
+
return String(value)
|
|
27
|
+
.replaceAll('&', '&')
|
|
28
|
+
.replaceAll('<', '<')
|
|
29
|
+
.replaceAll('>', '>')
|
|
30
|
+
.replaceAll('"', '"')
|
|
31
|
+
.replaceAll("'", ''');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function renderPage({ heading, message, details }) {
|
|
35
|
+
const title = escapeHtml(heading);
|
|
36
|
+
return `<!doctype html>
|
|
37
|
+
<html lang="en">
|
|
38
|
+
<head>
|
|
39
|
+
<meta charset="utf-8" />
|
|
40
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
41
|
+
<title>${title} · Mixdog</title>
|
|
42
|
+
<style>
|
|
43
|
+
* { box-sizing: border-box; }
|
|
44
|
+
html { color-scheme: dark; }
|
|
45
|
+
body {
|
|
46
|
+
margin: 0;
|
|
47
|
+
min-height: 100vh;
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
padding: 24px;
|
|
52
|
+
background: #07080b;
|
|
53
|
+
color: #f8fafc;
|
|
54
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
|
55
|
+
text-align: center;
|
|
56
|
+
}
|
|
57
|
+
main { width: 100%; max-width: 560px; display: flex; flex-direction: column; align-items: center; }
|
|
58
|
+
.logo { width: 72px; height: 72px; margin-bottom: 24px; }
|
|
59
|
+
.logo svg { width: 100%; height: 100%; display: block; }
|
|
60
|
+
h1 { margin: 0 0 10px; font-size: 28px; line-height: 1.15; font-weight: 650; }
|
|
61
|
+
p { margin: 0; line-height: 1.7; color: #94a3b8; font-size: 15px; }
|
|
62
|
+
.details {
|
|
63
|
+
margin-top: 16px;
|
|
64
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
65
|
+
font-size: 13px;
|
|
66
|
+
color: #94a3b8;
|
|
67
|
+
white-space: pre-wrap;
|
|
68
|
+
word-break: break-word;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
71
|
+
</head>
|
|
72
|
+
<body>
|
|
73
|
+
<main>
|
|
74
|
+
<div class="logo">${LOGO_SVG}</div>
|
|
75
|
+
<h1>${title}</h1>
|
|
76
|
+
<p>${escapeHtml(message)}</p>
|
|
77
|
+
${details ? `<div class="details">${escapeHtml(details)}</div>` : ''}
|
|
78
|
+
</main>
|
|
79
|
+
</body>
|
|
80
|
+
</html>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const OAUTH_PAGE_CONTENT_TYPE = 'text/html; charset=utf-8';
|
|
84
|
+
|
|
85
|
+
export function oauthSuccessHtml(providerName) {
|
|
86
|
+
return renderPage({
|
|
87
|
+
heading: 'Authentication successful',
|
|
88
|
+
message: `Signed in to ${providerName}. You can close this tab and return to Mixdog.`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function oauthErrorHtml(message, details) {
|
|
93
|
+
return renderPage({ heading: 'Authentication failed', message, details });
|
|
94
|
+
}
|