mixdog 0.9.44 → 0.9.46
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 +5 -2
- package/scripts/agent-dispatch-abort-compose-test.mjs +31 -0
- package/scripts/agent-model-liveness-test.mjs +618 -0
- package/scripts/agent-trace-io-test.mjs +68 -4
- package/scripts/bench-run.mjs +30 -3
- package/scripts/compact-pressure-test.mjs +187 -4
- package/scripts/compact-prior-context-flatten-test.mjs +252 -0
- package/scripts/compact-trigger-migration-smoke.mjs +8 -6
- package/scripts/compacted-placeholder-scrub-test.mjs +20 -34
- package/scripts/context-mcp-metering-test.mjs +75 -0
- package/scripts/explore-prompt-policy-test.mjs +15 -16
- package/scripts/explore-timeout-cancel-test.mjs +345 -0
- package/scripts/headless-pristine-execution-test.mjs +614 -0
- package/scripts/internal-comms-smoke.mjs +15 -6
- package/scripts/live-worker-smoke.mjs +1 -1
- package/scripts/memory-core-input-test.mjs +137 -0
- package/scripts/memory-rule-contract-test.mjs +5 -5
- package/scripts/openai-oauth-refresh-race-test.mjs +120 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +26 -0
- package/scripts/parent-abort-link-test.mjs +22 -0
- package/scripts/provider-toolcall-test.mjs +22 -0
- package/scripts/reactive-compact-persist-smoke.mjs +8 -4
- package/scripts/session-bench.mjs +3 -70
- package/scripts/task-bench.mjs +0 -2
- package/scripts/terminal-bench-isolation-guards-test.mjs +102 -0
- package/scripts/tool-smoke.mjs +21 -21
- package/scripts/tool-tui-presentation-test.mjs +68 -0
- package/scripts/tui-transcript-jitter-harness-entry.jsx +91 -10
- package/src/app.mjs +28 -103
- package/src/cli.mjs +17 -13
- package/src/headless-command.mjs +139 -0
- package/src/headless-role.mjs +121 -10
- package/src/help.mjs +4 -1
- package/src/rules/agent/00-common.md +3 -3
- package/src/rules/agent/00-core.md +8 -9
- package/src/rules/agent/20-skip-protocol.md +2 -3
- package/src/rules/agent/30-explorer.md +50 -56
- package/src/rules/agent/40-cycle1-agent.md +10 -12
- package/src/rules/agent/41-cycle2-agent.md +12 -9
- package/src/rules/agent/42-cycle3-agent.md +4 -6
- package/src/rules/lead/01-general.md +5 -6
- package/src/rules/lead/02-channels.md +1 -1
- package/src/rules/lead/lead-brief.md +14 -17
- package/src/rules/lead/lead-tool.md +3 -3
- package/src/rules/shared/01-tool.md +41 -43
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +46 -10
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +44 -31
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +18 -3
- package/src/runtime/agent/orchestrator/config.mjs +96 -30
- package/src/runtime/agent/orchestrator/context/collect.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +123 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +148 -30
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +5 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +62 -14
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +141 -19
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +85 -17
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -17
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +159 -20
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +83 -10
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +75 -7
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +4 -374
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +0 -75
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +0 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +32 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +86 -15
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +30 -27
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +1 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +21 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -51
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/runtime/memory/index.mjs +0 -1
- package/src/runtime/memory/lib/core-memory-store.mjs +44 -24
- package/src/runtime/memory/lib/http-router.mjs +0 -193
- package/src/runtime/memory/lib/memory-action-handlers.mjs +37 -11
- package/src/runtime/memory/tool-defs.mjs +5 -6
- package/src/runtime/shared/config.mjs +11 -34
- package/src/runtime/shared/pristine-execution-contract.json +75 -0
- package/src/runtime/shared/pristine-execution.mjs +356 -0
- package/src/runtime/shared/provider-api-key.mjs +43 -0
- package/src/runtime/shared/provider-auth-binding.mjs +21 -0
- package/src/runtime/shared/update-checker.mjs +40 -10
- package/src/session-runtime/context-status.mjs +61 -13
- package/src/session-runtime/mcp-glue.mjs +29 -2
- package/src/session-runtime/plugin-mcp.mjs +7 -0
- package/src/session-runtime/resource-api.mjs +38 -5
- package/src/session-runtime/runtime-core.mjs +5 -1
- package/src/session-runtime/session-turn-api.mjs +14 -2
- package/src/session-runtime/settings-api.mjs +5 -0
- package/src/session-runtime/tool-catalog.mjs +13 -2
- package/src/session-runtime/tool-defs.mjs +1 -3
- package/src/standalone/agent-task-status.mjs +50 -11
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/explore-tool.mjs +257 -49
- package/src/standalone/seeds.mjs +1 -0
- package/src/tui/App.jsx +26 -16
- package/src/tui/app/use-transcript-scroll.mjs +4 -3
- package/src/tui/app/use-transcript-window.mjs +12 -21
- package/src/tui/components/ContextPanel.jsx +19 -25
- package/src/tui/dist/index.mjs +89 -78
- package/src/tui/engine/agent-envelope.mjs +16 -5
- package/src/tui/engine/labels.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +21 -51
- package/src/workflows/solo/WORKFLOW.md +12 -17
- package/src/workflows/solo-review/WORKFLOW.md +0 -47
|
@@ -11,6 +11,7 @@ import { join, resolve } from 'path';
|
|
|
11
11
|
import { createServer } from 'http';
|
|
12
12
|
import { randomBytes, createHash } from 'crypto';
|
|
13
13
|
import { writeJsonAtomicSync, withFileLock } from '../../../shared/atomic-file.mjs';
|
|
14
|
+
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
14
15
|
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
15
16
|
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
16
17
|
|
|
@@ -71,6 +72,8 @@ function _pushUnique(list, value) {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
export function credentialCandidates() {
|
|
75
|
+
const bound = boundProviderAuthPath('anthropic-oauth');
|
|
76
|
+
if (bound) return [resolve(bound)];
|
|
74
77
|
const explicit = process.env.ANTHROPIC_OAUTH_CREDENTIALS_PATH;
|
|
75
78
|
if (explicit) return [resolve(explicit)];
|
|
76
79
|
return [DEFAULT_CREDENTIALS_PATH];
|
|
@@ -83,6 +83,9 @@ function _anthropicSseError(event) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export async function parseSSEStream(response, signal, abortStream, onStreamDelta, onToolCall, state, onTextDelta, knownToolNames) {
|
|
86
|
+
// parseSSEStream is entered only after Anthropic returned an HTTP response.
|
|
87
|
+
// Headers prove transport health, but are not a semantic model event.
|
|
88
|
+
try { onStreamDelta?.('transport'); } catch {}
|
|
86
89
|
const reader = response.body.getReader();
|
|
87
90
|
const decoder = new TextDecoder();
|
|
88
91
|
// SEMANTIC idle window: reset only by real model events (message/content/
|
|
@@ -159,7 +162,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
159
162
|
toolCalls.push(call);
|
|
160
163
|
if (state) state.emittedToolCall = true;
|
|
161
164
|
try { onToolCall?.(call); } catch {}
|
|
162
|
-
try { onStreamDelta?.(); } catch {}
|
|
165
|
+
try { onStreamDelta?.('tool'); } catch {}
|
|
163
166
|
};
|
|
164
167
|
|
|
165
168
|
// Feed accumulated text through the scanner. On `final` nothing is held
|
|
@@ -172,6 +175,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
172
175
|
leakFenceState = fenceState;
|
|
173
176
|
if (emit) {
|
|
174
177
|
content += emit;
|
|
178
|
+
try { onStreamDelta?.('text'); } catch {}
|
|
175
179
|
if (onTextDelta) {
|
|
176
180
|
if (state) state.emittedText = true;
|
|
177
181
|
try { onTextDelta(emit); } catch {}
|
|
@@ -327,6 +331,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
327
331
|
}
|
|
328
332
|
const { done, value } = chunk;
|
|
329
333
|
if (done) break;
|
|
334
|
+
try { onStreamDelta?.('transport'); } catch {}
|
|
330
335
|
|
|
331
336
|
buffer += decoder.decode(value, { stream: true });
|
|
332
337
|
const lines = buffer.split('\n');
|
|
@@ -377,6 +382,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
377
382
|
if (event.type === 'message_start' && event.message) {
|
|
378
383
|
clearFirstMessageTimer();
|
|
379
384
|
if (state) state.sawMessageStart = true;
|
|
385
|
+
try { onStreamDelta?.('semantic'); } catch {}
|
|
380
386
|
if (event.message.model) model = event.message.model;
|
|
381
387
|
if (event.message.usage) {
|
|
382
388
|
usage.inputTokens = event.message.usage.input_tokens || 0;
|
|
@@ -405,6 +411,8 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
405
411
|
type: 'redacted_thinking',
|
|
406
412
|
data: typeof block.data === 'string' ? block.data : '',
|
|
407
413
|
});
|
|
414
|
+
hasThinkingContent = true;
|
|
415
|
+
try { onStreamDelta?.('reasoning'); } catch {}
|
|
408
416
|
} else {
|
|
409
417
|
// Seed an ordered thinking block; deltas below
|
|
410
418
|
// append text + signature into this same slot.
|
|
@@ -427,7 +435,6 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
427
435
|
// ttftMs was always null and reported as 0ms.
|
|
428
436
|
if (state && !state.ttftAt) state.ttftAt = Date.now();
|
|
429
437
|
if (delta?.type === 'text_delta') {
|
|
430
|
-
try { onStreamDelta?.(); } catch {}
|
|
431
438
|
// Live text relay (gateway): forward the explicit
|
|
432
439
|
// text chunk. thinking/signature/input_json deltas
|
|
433
440
|
// intentionally stay off this path.
|
|
@@ -450,6 +457,9 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
450
457
|
if (state) state.emittedText = true;
|
|
451
458
|
try { onTextDelta(delta.text); } catch {}
|
|
452
459
|
}
|
|
460
|
+
if (delta.text) {
|
|
461
|
+
try { onStreamDelta?.('text'); } catch {}
|
|
462
|
+
}
|
|
453
463
|
}
|
|
454
464
|
}
|
|
455
465
|
if (delta?.type === 'thinking_delta' || delta?.type === 'signature_delta') {
|
|
@@ -474,14 +484,17 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
474
484
|
} else {
|
|
475
485
|
tb.signature += delta.signature || '';
|
|
476
486
|
}
|
|
477
|
-
|
|
487
|
+
if ((delta.type === 'thinking_delta' && delta.thinking)
|
|
488
|
+
|| (delta.type === 'signature_delta' && delta.signature)) {
|
|
489
|
+
try { onStreamDelta?.('reasoning'); } catch {}
|
|
490
|
+
}
|
|
478
491
|
}
|
|
479
492
|
if (delta?.type === 'input_json_delta') {
|
|
480
493
|
const pending = pendingToolInputs.get(event.index);
|
|
481
494
|
if (pending) {
|
|
482
495
|
pending.inputJson += delta.partial_json || '';
|
|
483
496
|
}
|
|
484
|
-
try { onStreamDelta?.(); } catch {}
|
|
497
|
+
try { onStreamDelta?.('tool'); } catch {}
|
|
485
498
|
}
|
|
486
499
|
}
|
|
487
500
|
|
|
@@ -539,7 +552,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
539
552
|
// pending promises by call.id so order is safe.
|
|
540
553
|
try { onToolCall?.(call); } catch {}
|
|
541
554
|
}
|
|
542
|
-
try { onStreamDelta?.(); } catch {}
|
|
555
|
+
try { onStreamDelta?.('tool'); } catch {}
|
|
543
556
|
}
|
|
544
557
|
}
|
|
545
558
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import {
|
|
2
|
+
import { getAgentApiKey } from '../../../shared/provider-api-key.mjs';
|
|
3
3
|
import { sanitizeToolPairs, sanitizeAnthropicContentPairs, foldUserTextIntoToolResultTail } from '../session/context-utils.mjs';
|
|
4
4
|
import { classifyError, midstreamBackoffFor, sleepWithAbort, withRetry, retryAfterMsFromError } from './retry-classifier.mjs';
|
|
5
5
|
import { traceAgentUsage } from '../agent-trace.mjs';
|
|
@@ -538,11 +538,11 @@ export class AnthropicProvider {
|
|
|
538
538
|
}
|
|
539
539
|
reloadApiKey() {
|
|
540
540
|
try {
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
541
|
+
const newKey = getAgentApiKey(this.name)
|
|
542
|
+
|| this.config.apiKey
|
|
543
|
+
|| (this.name === 'anthropic' ? process.env.ANTHROPIC_API_KEY : null);
|
|
544
544
|
if (newKey) {
|
|
545
|
-
this.config = { ...(this.config || {}),
|
|
545
|
+
this.config = { ...(this.config || {}), apiKey: newKey };
|
|
546
546
|
const betaHeaders = this.config.disableBetaHeaders ? null : buildAnthropicBetaHeaders({ toolSearch: true });
|
|
547
547
|
this.client = new (loadAnthropic())({
|
|
548
548
|
apiKey: newKey,
|
|
@@ -564,7 +564,7 @@ export class AnthropicProvider {
|
|
|
564
564
|
} catch (err) {
|
|
565
565
|
if (err.message && (err.message.includes('401') || err.message.includes('403'))
|
|
566
566
|
&& !err.liveTextEmitted && !err.emittedToolCall && !err.unsafeToRetry) {
|
|
567
|
-
process.stderr.write(`[provider] Auth error, re-reading
|
|
567
|
+
process.stderr.write(`[provider] Auth error, re-reading provider authentication...\n`);
|
|
568
568
|
this.reloadApiKey();
|
|
569
569
|
return await this._doSend(messages, model, tools, sendOpts);
|
|
570
570
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
-
import {
|
|
2
|
+
import { getAgentApiKey } from '../../../shared/provider-api-key.mjs';
|
|
3
3
|
import { makeModelCache } from './model-cache.mjs';
|
|
4
4
|
import { withRetry } from './retry-classifier.mjs';
|
|
5
5
|
import { traceAgentUsage, appendAgentTrace } from '../agent-trace.mjs';
|
|
@@ -151,14 +151,14 @@ export class GeminiProvider {
|
|
|
151
151
|
|
|
152
152
|
reloadApiKey() {
|
|
153
153
|
try {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
const newKey = getAgentApiKey('gemini')
|
|
155
|
+
|| this.config?.apiKey
|
|
156
|
+
|| process.env.GEMINI_API_KEY;
|
|
157
157
|
if (newKey) {
|
|
158
158
|
// Keep this.config in sync so REST/cache paths (which read the
|
|
159
159
|
// key via _getApiKey() → this.config.apiKey) don't keep using
|
|
160
160
|
// the stale key after a rotation; genAI alone is not enough.
|
|
161
|
-
this.config = { ...(this.config || {}),
|
|
161
|
+
this.config = { ...(this.config || {}), apiKey: newKey };
|
|
162
162
|
this.genAI = new GoogleGenerativeAI(newKey);
|
|
163
163
|
}
|
|
164
164
|
} catch { /* best effort */ }
|
|
@@ -481,7 +481,7 @@ export class GeminiProvider {
|
|
|
481
481
|
if (err.liveTextEmitted === true || err.emittedToolCall === true || err.unsafeToRetry === true) {
|
|
482
482
|
throw err;
|
|
483
483
|
}
|
|
484
|
-
process.stderr.write(`[provider] Auth error, re-reading
|
|
484
|
+
process.stderr.write(`[provider] Auth error, re-reading provider authentication...\n`);
|
|
485
485
|
this.reloadApiKey();
|
|
486
486
|
return await this._doSend(messages, model, tools, sendOpts);
|
|
487
487
|
}
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
import { createServer } from 'http';
|
|
21
21
|
import { randomBytes, createHash } from 'crypto';
|
|
22
22
|
import { readFileSync, existsSync, mkdirSync, statSync, unlinkSync } from 'fs';
|
|
23
|
-
import { join } from 'path';
|
|
23
|
+
import { join, resolve } from 'path';
|
|
24
24
|
import { getPluginData } from '../config.mjs';
|
|
25
25
|
import { writeJsonAtomicSync, withFileLock } from '../../../shared/atomic-file.mjs';
|
|
26
|
+
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
26
27
|
import { enrichModels, getModelMetadataSync } from './model-catalog.mjs';
|
|
27
28
|
import { sanitizeModelList } from './model-list-sanitize.mjs';
|
|
28
29
|
import { makeModelCache } from './model-cache.mjs';
|
|
@@ -175,6 +176,10 @@ async function fetchDiscovery() {
|
|
|
175
176
|
|
|
176
177
|
// --- Token store ---
|
|
177
178
|
function getOwnTokenPath() {
|
|
179
|
+
const bound = boundProviderAuthPath('grok-oauth');
|
|
180
|
+
if (bound) return resolve(bound);
|
|
181
|
+
const explicit = process.env.GROK_OAUTH_CREDENTIALS_PATH;
|
|
182
|
+
if (explicit) return resolve(explicit);
|
|
178
183
|
const dir = getPluginData();
|
|
179
184
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
180
185
|
return join(dir, 'grok-oauth.json');
|
|
@@ -579,6 +584,120 @@ export async function ensureLatestGrokModel(provider) {
|
|
|
579
584
|
|
|
580
585
|
let _modelRefreshInFlight = null;
|
|
581
586
|
|
|
587
|
+
// Grok's gRPC tool registry rejects a root anyOf/oneOf when even one branch is
|
|
588
|
+
// not an object. Keep this adapter local to OAuth Grok requests: other
|
|
589
|
+
// providers may support the original union schema. Tool definitions are never
|
|
590
|
+
// mutated, so callers can safely reuse them for later requests.
|
|
591
|
+
function schemasDeepEqual(left, right) {
|
|
592
|
+
if (Object.is(left, right)) return true;
|
|
593
|
+
if (!left || !right || typeof left !== 'object' || typeof right !== 'object') return false;
|
|
594
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
595
|
+
return Array.isArray(left) && Array.isArray(right)
|
|
596
|
+
&& left.length === right.length
|
|
597
|
+
&& left.every((value, index) => schemasDeepEqual(value, right[index]));
|
|
598
|
+
}
|
|
599
|
+
const leftKeys = Object.keys(left).sort();
|
|
600
|
+
const rightKeys = Object.keys(right).sort();
|
|
601
|
+
return leftKeys.length === rightKeys.length
|
|
602
|
+
&& leftKeys.every((key, index) => key === rightKeys[index]
|
|
603
|
+
&& schemasDeepEqual(left[key], right[key]));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function pureAnyOfAlternatives(schema) {
|
|
607
|
+
return schema && typeof schema === 'object' && !Array.isArray(schema)
|
|
608
|
+
&& Object.keys(schema).length === 1
|
|
609
|
+
&& Array.isArray(schema.anyOf)
|
|
610
|
+
? schema.anyOf
|
|
611
|
+
: [schema];
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function mergeGrokObjectBranchProperties(objectBranches) {
|
|
615
|
+
const properties = {};
|
|
616
|
+
for (const branch of objectBranches) {
|
|
617
|
+
for (const [name, schema] of Object.entries(branch.properties || {})) {
|
|
618
|
+
if (!Object.prototype.hasOwnProperty.call(properties, name)) {
|
|
619
|
+
properties[name] = schema;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (schemasDeepEqual(properties[name], schema)) continue;
|
|
623
|
+
const alternatives = [
|
|
624
|
+
...pureAnyOfAlternatives(properties[name]),
|
|
625
|
+
...pureAnyOfAlternatives(schema),
|
|
626
|
+
];
|
|
627
|
+
const deduped = alternatives.reduce(
|
|
628
|
+
(unique, alternative) => unique.some(item => schemasDeepEqual(item, alternative))
|
|
629
|
+
? unique
|
|
630
|
+
: [...unique, alternative],
|
|
631
|
+
[],
|
|
632
|
+
);
|
|
633
|
+
properties[name] = deduped.length === 1 ? deduped[0] : { anyOf: deduped };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return properties;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function normalizeGrokToolSchema(schema) {
|
|
640
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)
|
|
641
|
+
|| (!Array.isArray(schema.anyOf) && !Array.isArray(schema.oneOf))) {
|
|
642
|
+
return schema;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const { anyOf, oneOf, ...root } = schema;
|
|
646
|
+
const branches = [...(Array.isArray(anyOf) ? anyOf : []), ...(Array.isArray(oneOf) ? oneOf : [])];
|
|
647
|
+
const objectBranches = branches.filter((branch) => branch && typeof branch === 'object' && !Array.isArray(branch)
|
|
648
|
+
&& (branch.type === 'object'
|
|
649
|
+
|| (Array.isArray(branch.type) && branch.type.includes('object'))
|
|
650
|
+
|| (branch.properties && typeof branch.properties === 'object')));
|
|
651
|
+
|
|
652
|
+
// A function's arguments must be an object even if its original union has
|
|
653
|
+
// no explicit object alternative. The permissive fallback still retains
|
|
654
|
+
// root-level metadata such as description and additionalProperties.
|
|
655
|
+
if (!objectBranches.length) {
|
|
656
|
+
return {
|
|
657
|
+
...root,
|
|
658
|
+
type: 'object',
|
|
659
|
+
...(!Object.prototype.hasOwnProperty.call(root, 'additionalProperties')
|
|
660
|
+
? { additionalProperties: true }
|
|
661
|
+
: {}),
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const properties = objectBranches.some(branch => branch.properties) || root.properties
|
|
666
|
+
? {
|
|
667
|
+
...mergeGrokObjectBranchProperties(objectBranches),
|
|
668
|
+
...(root.properties || {}),
|
|
669
|
+
}
|
|
670
|
+
: undefined;
|
|
671
|
+
const branchRequiredInEvery = (Array.isArray(objectBranches[0].required) ? objectBranches[0].required : [])
|
|
672
|
+
.filter(key => objectBranches.every(branch => Array.isArray(branch.required) && branch.required.includes(key)));
|
|
673
|
+
const required = [...new Set([
|
|
674
|
+
...(Array.isArray(root.required) ? root.required : []),
|
|
675
|
+
...branchRequiredInEvery,
|
|
676
|
+
])];
|
|
677
|
+
const { properties: _rootProperties, required: _rootRequired, ...rootWithoutPropertiesOrRequired } = root;
|
|
678
|
+
const mergedObjectBranches = Object.assign({}, ...objectBranches);
|
|
679
|
+
const {
|
|
680
|
+
properties: _branchProperties,
|
|
681
|
+
required: _branchRequired,
|
|
682
|
+
...mergedObjectBranchesWithoutPropertiesOrRequired
|
|
683
|
+
} = mergedObjectBranches;
|
|
684
|
+
return {
|
|
685
|
+
...mergedObjectBranchesWithoutPropertiesOrRequired,
|
|
686
|
+
...rootWithoutPropertiesOrRequired,
|
|
687
|
+
type: 'object',
|
|
688
|
+
...(properties ? { properties } : {}),
|
|
689
|
+
...(required.length ? { required } : {}),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function normalizeGrokToolSchemas(tools) {
|
|
694
|
+
if (!Array.isArray(tools)) return tools;
|
|
695
|
+
return tools.map((tool) => {
|
|
696
|
+
const inputSchema = normalizeGrokToolSchema(tool?.inputSchema);
|
|
697
|
+
return inputSchema === tool?.inputSchema ? tool : { ...tool, inputSchema };
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
582
701
|
export class GrokOAuthProvider {
|
|
583
702
|
// OpenAI-compatible usage: prompt_tokens includes cached. See registry.mjs.
|
|
584
703
|
static inputExcludesCache = false;
|
|
@@ -692,6 +811,7 @@ export class GrokOAuthProvider {
|
|
|
692
811
|
);
|
|
693
812
|
const tokens = await this.ensureAuth();
|
|
694
813
|
const inner = this._ensureInner(tokens.access_token, useModel);
|
|
814
|
+
const grokTools = normalizeGrokToolSchemas(tools);
|
|
695
815
|
try {
|
|
696
816
|
// Call _doSend directly, bypassing OpenAICompatProvider.send()'s
|
|
697
817
|
// own 401 handler — that one reloads a static apiKey from config,
|
|
@@ -699,7 +819,7 @@ export class GrokOAuthProvider {
|
|
|
699
819
|
// Caller's model passes through (or catalog-top default), with
|
|
700
820
|
// retired aliases normalized. api.x.ai exposes the full grok-4.x
|
|
701
821
|
// catalog to this token — no single-model lock.
|
|
702
|
-
return await inner._doSend(messages, useModel,
|
|
822
|
+
return await inner._doSend(messages, useModel, grokTools, sendOpts);
|
|
703
823
|
} catch (err) {
|
|
704
824
|
// Refresh-and-retry only on 401 (stale/expired access token).
|
|
705
825
|
// Resolve the status from the structured field (falling back to the
|
|
@@ -712,7 +832,7 @@ export class GrokOAuthProvider {
|
|
|
712
832
|
process.stderr.write('[grok-oauth] 401, force-refreshing token...\n');
|
|
713
833
|
const fresh = await this.ensureAuth({ forceRefresh: true });
|
|
714
834
|
const retryInner = this._ensureInner(fresh.access_token, useModel);
|
|
715
|
-
return await retryInner._doSend(messages, useModel,
|
|
835
|
+
return await retryInner._doSend(messages, useModel, grokTools, sendOpts);
|
|
716
836
|
}
|
|
717
837
|
throw err;
|
|
718
838
|
}
|
|
@@ -58,7 +58,10 @@ export function hasAnthropicOAuthCredentials() {
|
|
|
58
58
|
|
|
59
59
|
export function hasOpenAIOAuthCredentials() {
|
|
60
60
|
return memoProbe('openai-oauth', () => {
|
|
61
|
-
const paths = [
|
|
61
|
+
const paths = [
|
|
62
|
+
process.env.OPENAI_OAUTH_CREDENTIALS_PATH,
|
|
63
|
+
join(resolvePluginData(), 'openai-oauth.json'),
|
|
64
|
+
].filter(Boolean);
|
|
62
65
|
for (const path of paths) {
|
|
63
66
|
const raw = readJsonIfExists(path);
|
|
64
67
|
if (raw?.access_token && raw?.refresh_token) return true;
|
|
@@ -69,7 +72,13 @@ export function hasOpenAIOAuthCredentials() {
|
|
|
69
72
|
|
|
70
73
|
export function hasGrokOAuthCredentials() {
|
|
71
74
|
return memoProbe('grok-oauth', () => {
|
|
72
|
-
const
|
|
73
|
-
|
|
75
|
+
const paths = [
|
|
76
|
+
process.env.GROK_OAUTH_CREDENTIALS_PATH,
|
|
77
|
+
join(resolvePluginData(), 'grok-oauth.json'),
|
|
78
|
+
].filter(Boolean);
|
|
79
|
+
return paths.some((path) => {
|
|
80
|
+
const own = readJsonIfExists(path);
|
|
81
|
+
return !!(own?.access_token && own?.refresh_token);
|
|
82
|
+
});
|
|
74
83
|
});
|
|
75
84
|
}
|