mixdog 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -23
- package/package.json +33 -27
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +344 -0
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1223 -95
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +118 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +145 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -98
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +367 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +746 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -56,7 +56,7 @@ function cacheKey(provider) {
|
|
|
56
56
|
function freshSnapshot(snapshot, ttlMs) {
|
|
57
57
|
const cachedAt = num(snapshot?.cachedAt, 0);
|
|
58
58
|
if (!cachedAt || Date.now() - cachedAt > ttlMs) return null;
|
|
59
|
-
if (snapshot?.balance || Array.isArray(snapshot?.quotaWindows)) return snapshot;
|
|
59
|
+
if (snapshot?.balance || Array.isArray(snapshot?.quotaWindows) || snapshot?.tokenUsage || snapshot?.unavailable) return snapshot;
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -175,6 +175,39 @@ function parseOpenAICosts(data) {
|
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
function parseOpenAICompletionUsage(data) {
|
|
179
|
+
const buckets = Array.isArray(data?.data) ? data.data : [];
|
|
180
|
+
let inputTokens = 0;
|
|
181
|
+
let outputTokens = 0;
|
|
182
|
+
let cachedInputTokens = 0;
|
|
183
|
+
let requests = 0;
|
|
184
|
+
let seen = false;
|
|
185
|
+
for (const bucket of buckets) {
|
|
186
|
+
const results = Array.isArray(bucket?.results) ? bucket.results : [];
|
|
187
|
+
for (const result of results) {
|
|
188
|
+
inputTokens += num(result?.input_tokens, 0);
|
|
189
|
+
outputTokens += num(result?.output_tokens, 0);
|
|
190
|
+
cachedInputTokens += num(result?.input_cached_tokens, 0);
|
|
191
|
+
requests += num(result?.num_model_requests, 0);
|
|
192
|
+
seen = true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!seen && Array.isArray(data?.data)) seen = true;
|
|
196
|
+
if (!seen) return null;
|
|
197
|
+
return {
|
|
198
|
+
provider: 'openai',
|
|
199
|
+
source: 'openai-organization-usage-completions',
|
|
200
|
+
tokenUsage: {
|
|
201
|
+
inputTokens,
|
|
202
|
+
outputTokens,
|
|
203
|
+
cachedInputTokens,
|
|
204
|
+
requests,
|
|
205
|
+
source: 'openai-organization-usage-completions',
|
|
206
|
+
},
|
|
207
|
+
cachedAt: Date.now(),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
178
211
|
function parseOpenAICreditGrants(data) {
|
|
179
212
|
const remainingUsd = num(data?.total_available ?? data?.totalAvailable ?? data?.available, null);
|
|
180
213
|
if (remainingUsd === null) return null;
|
|
@@ -205,6 +238,13 @@ function parseOpenAICreditGrants(data) {
|
|
|
205
238
|
};
|
|
206
239
|
}
|
|
207
240
|
|
|
241
|
+
function ymd(value) {
|
|
242
|
+
const d = new Date(value);
|
|
243
|
+
if (!Number.isFinite(d.getTime())) return '';
|
|
244
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
245
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
208
248
|
function uniqueValues(values) {
|
|
209
249
|
const seen = new Set();
|
|
210
250
|
const out = [];
|
|
@@ -237,6 +277,49 @@ async function fetchOpenAICreditGrantSnapshot(apiKey) {
|
|
|
237
277
|
return null;
|
|
238
278
|
}
|
|
239
279
|
|
|
280
|
+
async function fetchOpenAIDashboardUsageSnapshot(sessionKey) {
|
|
281
|
+
if (!sessionKey) return null;
|
|
282
|
+
const headers = authHeaders(sessionKey, { Accept: 'application/json' });
|
|
283
|
+
let subscription = null;
|
|
284
|
+
let usage = null;
|
|
285
|
+
try {
|
|
286
|
+
subscription = await fetchJson('https://api.openai.com/dashboard/billing/subscription', { headers });
|
|
287
|
+
} catch {
|
|
288
|
+
subscription = null;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const endDate = ymd(Date.now());
|
|
292
|
+
const startDate = ymd(Date.now() - 31 * 24 * 60 * 60_000);
|
|
293
|
+
usage = await fetchJson(`https://api.openai.com/dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, { headers });
|
|
294
|
+
} catch {
|
|
295
|
+
usage = null;
|
|
296
|
+
}
|
|
297
|
+
const totalUsageCents = num(usage?.total_usage ?? usage?.totalUsage, null);
|
|
298
|
+
const usedUsd = totalUsageCents === null ? null : totalUsageCents / 100;
|
|
299
|
+
const limitUsd = num(
|
|
300
|
+
subscription?.hard_limit_usd
|
|
301
|
+
?? subscription?.hardLimitUsd
|
|
302
|
+
?? subscription?.system_hard_limit_usd
|
|
303
|
+
?? subscription?.systemHardLimitUsd
|
|
304
|
+
?? subscription?.soft_limit_usd
|
|
305
|
+
?? subscription?.softLimitUsd,
|
|
306
|
+
null,
|
|
307
|
+
);
|
|
308
|
+
if (usedUsd === null && limitUsd === null) return null;
|
|
309
|
+
const balance = {
|
|
310
|
+
source: 'openai-dashboard-billing',
|
|
311
|
+
};
|
|
312
|
+
if (usedUsd !== null) balance.usedUsd = round(usedUsd, 4);
|
|
313
|
+
if (limitUsd !== null) balance.limitUsd = round(limitUsd, 4);
|
|
314
|
+
if (usedUsd !== null && limitUsd !== null) balance.remainingUsd = round(Math.max(0, limitUsd - usedUsd), 4);
|
|
315
|
+
return {
|
|
316
|
+
provider: 'openai',
|
|
317
|
+
source: 'openai-dashboard-billing',
|
|
318
|
+
balance,
|
|
319
|
+
cachedAt: Date.now(),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
240
323
|
async function fetchOpenAICostSnapshot(apiKey) {
|
|
241
324
|
if (!apiKey) return null;
|
|
242
325
|
const start = Math.floor((Date.now() - 31 * 24 * 60 * 60_000) / 1000);
|
|
@@ -249,11 +332,115 @@ async function fetchOpenAICostSnapshot(apiKey) {
|
|
|
249
332
|
return parseOpenAICosts(data);
|
|
250
333
|
}
|
|
251
334
|
|
|
335
|
+
async function fetchOpenAICompletionUsageSnapshot(apiKey) {
|
|
336
|
+
if (!apiKey) return null;
|
|
337
|
+
const start = Math.floor((Date.now() - 31 * 24 * 60 * 60_000) / 1000);
|
|
338
|
+
const url = new URL('https://api.openai.com/v1/organization/usage/completions');
|
|
339
|
+
url.searchParams.set('start_time', String(start));
|
|
340
|
+
url.searchParams.set('bucket_width', '1d');
|
|
341
|
+
url.searchParams.set('limit', '31');
|
|
342
|
+
const data = await fetchJson(url, {
|
|
343
|
+
headers: authHeaders(apiKey, { Accept: 'application/json' }),
|
|
344
|
+
});
|
|
345
|
+
return parseOpenAICompletionUsage(data);
|
|
346
|
+
}
|
|
347
|
+
|
|
252
348
|
async function fetchOpenAIUsageSnapshot() {
|
|
253
349
|
const key = getAgentApiKey('openai');
|
|
254
350
|
const credit = await fetchOpenAICreditGrantSnapshot(key);
|
|
255
351
|
if (credit) return credit;
|
|
256
|
-
|
|
352
|
+
const dashboard = await fetchOpenAIDashboardUsageSnapshot(getOpenAIUsageSessionKey());
|
|
353
|
+
if (dashboard) return dashboard;
|
|
354
|
+
try {
|
|
355
|
+
const costs = await fetchOpenAICostSnapshot(key);
|
|
356
|
+
if (costs) return costs;
|
|
357
|
+
} catch (err) {
|
|
358
|
+
const status = num(err?.status, null);
|
|
359
|
+
if (status !== 401 && status !== 403 && status !== 404) throw err;
|
|
360
|
+
}
|
|
361
|
+
return await fetchOpenAICompletionUsageSnapshot(key);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function anthropicAdminKeys() {
|
|
365
|
+
return uniqueValues([
|
|
366
|
+
process.env.ANTHROPIC_ADMIN_API_KEY,
|
|
367
|
+
process.env.MIXDOG_ANTHROPIC_ADMIN_API_KEY,
|
|
368
|
+
process.env.ANTHROPIC_ADMIN_KEY,
|
|
369
|
+
getAgentApiKey('anthropic'),
|
|
370
|
+
]);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function parseAnthropicCostReport(data) {
|
|
374
|
+
const buckets = Array.isArray(data?.data) ? data.data : [];
|
|
375
|
+
let usedUsd = 0;
|
|
376
|
+
let seen = false;
|
|
377
|
+
for (const bucket of buckets) {
|
|
378
|
+
const results = Array.isArray(bucket?.results) ? bucket.results : [];
|
|
379
|
+
for (const item of results) {
|
|
380
|
+
const currency = String(item?.currency || 'USD').toUpperCase();
|
|
381
|
+
if (currency && currency !== 'USD') continue;
|
|
382
|
+
const cents = num(item?.amount, null);
|
|
383
|
+
if (cents === null) continue;
|
|
384
|
+
usedUsd += cents / 100;
|
|
385
|
+
seen = true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (!seen && Array.isArray(data?.data)) seen = true;
|
|
389
|
+
if (!seen) return null;
|
|
390
|
+
return {
|
|
391
|
+
provider: 'anthropic',
|
|
392
|
+
source: 'anthropic-cost-report',
|
|
393
|
+
balance: {
|
|
394
|
+
usedUsd: round(usedUsd, 4),
|
|
395
|
+
source: 'anthropic-cost-report',
|
|
396
|
+
},
|
|
397
|
+
cachedAt: Date.now(),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function fetchAnthropicCostSnapshot() {
|
|
402
|
+
const keys = anthropicAdminKeys();
|
|
403
|
+
if (!keys.length) return unavailableSnapshot('anthropic', { message: 'Set ANTHROPIC_ADMIN_API_KEY for usage/cost report' });
|
|
404
|
+
const start = new Date(Date.now() - 31 * 24 * 60 * 60_000).toISOString();
|
|
405
|
+
const url = new URL('https://api.anthropic.com/v1/organizations/cost_report');
|
|
406
|
+
url.searchParams.set('starting_at', start);
|
|
407
|
+
url.searchParams.set('bucket_width', '1d');
|
|
408
|
+
url.searchParams.set('limit', '31');
|
|
409
|
+
let firstAuthError = null;
|
|
410
|
+
for (const key of keys) {
|
|
411
|
+
for (const authMode of ['bearer', 'x-api-key']) {
|
|
412
|
+
try {
|
|
413
|
+
const headers = {
|
|
414
|
+
Accept: 'application/json',
|
|
415
|
+
'anthropic-version': '2023-06-01',
|
|
416
|
+
...(authMode === 'bearer' ? { Authorization: `Bearer ${key}` } : { 'x-api-key': key }),
|
|
417
|
+
};
|
|
418
|
+
const data = await fetchJson(url, { headers });
|
|
419
|
+
return parseAnthropicCostReport(data) || unavailableSnapshot('anthropic', { message: 'Anthropic cost report returned no usage data' });
|
|
420
|
+
} catch (err) {
|
|
421
|
+
const status = num(err?.status, null);
|
|
422
|
+
if (status === 401 || status === 403 || status === 404) {
|
|
423
|
+
firstAuthError ||= err;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
throw err;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return unavailableSnapshot('anthropic', firstAuthError || { message: 'Anthropic Admin API usage/cost auth failed' });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function fetchGeminiUsageSnapshot() {
|
|
434
|
+
const key = getAgentApiKey('gemini');
|
|
435
|
+
if (!key) return null;
|
|
436
|
+
try {
|
|
437
|
+
await fetchJson(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key)}`, {
|
|
438
|
+
headers: { Accept: 'application/json' },
|
|
439
|
+
});
|
|
440
|
+
return unavailableSnapshot('gemini', { message: 'Gemini API key valid; balance/usage is AI Studio or Cloud Billing only' });
|
|
441
|
+
} catch (err) {
|
|
442
|
+
return unavailableSnapshot('gemini', err);
|
|
443
|
+
}
|
|
257
444
|
}
|
|
258
445
|
|
|
259
446
|
function managementKey(provider) {
|
|
@@ -285,6 +472,28 @@ function moneyNumber(value) {
|
|
|
285
472
|
return null;
|
|
286
473
|
}
|
|
287
474
|
|
|
475
|
+
function errorMessage(err) {
|
|
476
|
+
const body = err?.body;
|
|
477
|
+
const value = body?.error?.message ?? body?.error ?? body?.message ?? err?.message;
|
|
478
|
+
if (typeof value === 'string') return value.trim();
|
|
479
|
+
if (value && typeof value === 'object') {
|
|
480
|
+
try { return JSON.stringify(value).slice(0, 300); } catch {}
|
|
481
|
+
}
|
|
482
|
+
return '';
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function unavailableSnapshot(provider, err) {
|
|
486
|
+
return {
|
|
487
|
+
provider,
|
|
488
|
+
source: 'provider-api-unavailable',
|
|
489
|
+
unavailable: {
|
|
490
|
+
status: num(err?.status, null),
|
|
491
|
+
message: errorMessage(err),
|
|
492
|
+
},
|
|
493
|
+
cachedAt: Date.now(),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
288
497
|
function parseXaiPrepaidBalance(data) {
|
|
289
498
|
const remainingUsd = moneyNumber(
|
|
290
499
|
data?.total
|
|
@@ -311,13 +520,34 @@ function parseXaiPrepaidBalance(data) {
|
|
|
311
520
|
async function fetchXaiUsageSnapshot() {
|
|
312
521
|
const key = managementKey('xai');
|
|
313
522
|
const team = teamId('xai');
|
|
314
|
-
if (!key || !team) return
|
|
523
|
+
if (!key || !team) return await fetchXaiPublicUsageSnapshot() || unavailableSnapshot('xai', { message: 'Set XAI_MANAGEMENT_API_KEY and XAI_TEAM_ID for prepaid balance' });
|
|
315
524
|
const data = await fetchJson(`https://management-api.x.ai/v1/billing/teams/${encodeURIComponent(team)}/prepaid/balance`, {
|
|
316
525
|
headers: authHeaders(key, { Accept: 'application/json' }),
|
|
317
526
|
});
|
|
318
527
|
return parseXaiPrepaidBalance(data);
|
|
319
528
|
}
|
|
320
529
|
|
|
530
|
+
async function fetchXaiPublicUsageSnapshot() {
|
|
531
|
+
const key = getAgentApiKey('xai');
|
|
532
|
+
if (!key) return null;
|
|
533
|
+
const urls = [
|
|
534
|
+
'https://api.x.ai/v1/usage',
|
|
535
|
+
'https://api.x.ai/v1/rate_limits',
|
|
536
|
+
];
|
|
537
|
+
let firstError = null;
|
|
538
|
+
for (const url of urls) {
|
|
539
|
+
try {
|
|
540
|
+
const data = await fetchJson(url, { headers: authHeaders(key, { Accept: 'application/json' }) });
|
|
541
|
+
const parsed = parseXaiPrepaidBalance(data);
|
|
542
|
+
if (parsed) return { ...parsed, source: 'xai-public-usage-probe', balance: { ...parsed.balance, source: 'xai-public-usage-probe' } };
|
|
543
|
+
return unavailableSnapshot('xai', { message: `xAI public usage probe returned unsupported data from ${url}` });
|
|
544
|
+
} catch (err) {
|
|
545
|
+
firstError ||= err;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return unavailableSnapshot('xai', firstError || { message: 'xAI public usage probes failed' });
|
|
549
|
+
}
|
|
550
|
+
|
|
321
551
|
export async function fetchApiUsageSnapshot(provider, { force = false } = {}) {
|
|
322
552
|
const id = cacheKey(provider);
|
|
323
553
|
if (!force) {
|
|
@@ -329,10 +559,12 @@ export async function fetchApiUsageSnapshot(provider, { force = false } = {}) {
|
|
|
329
559
|
try {
|
|
330
560
|
if (id === 'deepseek') snapshot = await fetchDeepSeekUsageSnapshot();
|
|
331
561
|
else if (id === 'openai') snapshot = await fetchOpenAIUsageSnapshot();
|
|
562
|
+
else if (id === 'anthropic') snapshot = await fetchAnthropicCostSnapshot();
|
|
563
|
+
else if (id === 'gemini') snapshot = await fetchGeminiUsageSnapshot();
|
|
332
564
|
else if (id === 'xai') snapshot = await fetchXaiUsageSnapshot();
|
|
333
565
|
} catch (err) {
|
|
334
566
|
const status = num(err?.status, null);
|
|
335
|
-
if (status === 401 || status === 403 || status === 404) return
|
|
567
|
+
if (status === 401 || status === 403 || status === 404) return unavailableSnapshot(id, err);
|
|
336
568
|
throw err;
|
|
337
569
|
}
|
|
338
570
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function isResponsesFreeformTool(tool) {
|
|
2
|
+
return !!(tool?.freeform
|
|
3
|
+
&& tool.freeform.type === 'grammar'
|
|
4
|
+
&& typeof tool.freeform.syntax === 'string'
|
|
5
|
+
&& typeof tool.freeform.definition === 'string');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function toResponsesCustomTool(tool) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'custom',
|
|
11
|
+
name: tool.name,
|
|
12
|
+
description: tool.freeformDescription || tool.description,
|
|
13
|
+
format: tool.freeform,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function customToolInputFromArguments(name, args) {
|
|
18
|
+
if (typeof args === 'string') return args;
|
|
19
|
+
if (name === 'apply_patch' && typeof args?.patch === 'string') return args.patch;
|
|
20
|
+
if (typeof args?.input === 'string') return args.input;
|
|
21
|
+
try { return JSON.stringify(args ?? {}); } catch { return String(args ?? ''); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function customToolArgumentsFromInput(name, input) {
|
|
25
|
+
const text = typeof input === 'string'
|
|
26
|
+
? input
|
|
27
|
+
: (() => {
|
|
28
|
+
try { return JSON.stringify(input ?? ''); } catch { return String(input ?? ''); }
|
|
29
|
+
})();
|
|
30
|
+
if (name === 'apply_patch') return { patch: text };
|
|
31
|
+
return { input: text };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function customToolCallFromResponseItem(item) {
|
|
35
|
+
if (!item || item.type !== 'custom_tool_call') return null;
|
|
36
|
+
const id = item.call_id || item.id || '';
|
|
37
|
+
const name = item.name || '';
|
|
38
|
+
if (!id || !name) return null;
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
name,
|
|
42
|
+
arguments: customToolArgumentsFromInput(name, item.input ?? ''),
|
|
43
|
+
nativeType: 'custom_tool_call',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isCustomToolCallRecord(call) {
|
|
48
|
+
return call?.nativeType === 'custom_tool_call';
|
|
49
|
+
}
|
|
@@ -2,7 +2,7 @@ import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai';
|
|
|
2
2
|
import { loadConfig } from '../config.mjs';
|
|
3
3
|
import { makeModelCache } from './model-cache.mjs';
|
|
4
4
|
import { withRetry } from './retry-classifier.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import { traceAgentUsage, appendAgentTrace } from '../agent-trace.mjs';
|
|
6
6
|
import {
|
|
7
7
|
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
8
8
|
PROVIDER_MAX_BEFORE_WARN_MS,
|
|
@@ -822,6 +822,41 @@ function toGeminiTools(tools) {
|
|
|
822
822
|
})),
|
|
823
823
|
};
|
|
824
824
|
}
|
|
825
|
+
function toGeminiNativeTools(nativeTools) {
|
|
826
|
+
if (!Array.isArray(nativeTools)) return [];
|
|
827
|
+
const out = [];
|
|
828
|
+
for (const tool of nativeTools) {
|
|
829
|
+
const type = String(tool?.type || '').trim().toLowerCase();
|
|
830
|
+
if (type === 'google_search' || type === 'google_search_retrieval') {
|
|
831
|
+
out.push({ googleSearch: {} });
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return out;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function collectGeminiGroundingSources(candidate) {
|
|
838
|
+
const out = [];
|
|
839
|
+
const seen = new Set();
|
|
840
|
+
const add = (source) => {
|
|
841
|
+
if (!source || typeof source !== 'object') return;
|
|
842
|
+
const web = source.web && typeof source.web === 'object' ? source.web : source;
|
|
843
|
+
const url = String(web.uri || web.url || source.uri || source.url || '').trim();
|
|
844
|
+
if (!url || seen.has(url)) return;
|
|
845
|
+
seen.add(url);
|
|
846
|
+
out.push({
|
|
847
|
+
title: String(web.title || source.title || url).trim(),
|
|
848
|
+
url,
|
|
849
|
+
snippet: '',
|
|
850
|
+
source: 'gemini-grounding',
|
|
851
|
+
provider: 'gemini',
|
|
852
|
+
});
|
|
853
|
+
};
|
|
854
|
+
const grounding = candidate?.groundingMetadata || {};
|
|
855
|
+
for (const chunk of Array.isArray(grounding.groundingChunks) ? grounding.groundingChunks : []) add(chunk);
|
|
856
|
+
const citationMetadata = candidate?.citationMetadata || {};
|
|
857
|
+
for (const source of Array.isArray(citationMetadata.citationSources) ? citationMetadata.citationSources : []) add(source);
|
|
858
|
+
return out;
|
|
859
|
+
}
|
|
825
860
|
|
|
826
861
|
// Map the orchestrator-level toolChoice to Gemini's functionCallingConfig.
|
|
827
862
|
// auto -> AUTO
|
|
@@ -908,7 +943,7 @@ function toGeminiContents(messages) {
|
|
|
908
943
|
return contents;
|
|
909
944
|
}
|
|
910
945
|
|
|
911
|
-
function parseToolCalls(parts) {
|
|
946
|
+
export function parseToolCalls(parts) {
|
|
912
947
|
const calls = parts.filter((p) => 'functionCall' in p && !!p.functionCall);
|
|
913
948
|
if (!calls.length)
|
|
914
949
|
return undefined;
|
|
@@ -998,6 +1033,7 @@ export class GeminiProvider {
|
|
|
998
1033
|
// tokens/hour) is dwarfed by the 75% input-price discount on hits beyond
|
|
999
1034
|
// a few iterations.
|
|
1000
1035
|
async _ensureGeminiCache({ apiKey, model, systemInstruction, geminiTools, contents, opts }) {
|
|
1036
|
+
if (Array.isArray(opts?.nativeTools) && opts.nativeTools.length) return null;
|
|
1001
1037
|
const state = opts.providerState?.gemini || null;
|
|
1002
1038
|
const now = Date.now();
|
|
1003
1039
|
const currentIter = Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : 1;
|
|
@@ -1029,7 +1065,7 @@ export class GeminiProvider {
|
|
|
1029
1065
|
const canAttachState = !!state?.cacheName && cacheLiveMs > 0 && modelMatches && prefixMatches;
|
|
1030
1066
|
const canReuseState = canAttachState && cacheLiveMs > 6 * 60 * 1000 && itersSinceCreate < refreshEveryN;
|
|
1031
1067
|
try {
|
|
1032
|
-
|
|
1068
|
+
appendAgentTrace({
|
|
1033
1069
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1034
1070
|
iteration: currentIter,
|
|
1035
1071
|
kind: 'gemini_cache_decision',
|
|
@@ -1063,7 +1099,7 @@ export class GeminiProvider {
|
|
|
1063
1099
|
const estimatedTokens = _estimateGeminiCacheTokens(systemInstruction, geminiTools, contents);
|
|
1064
1100
|
if (estimatedTokens < minTokens) {
|
|
1065
1101
|
try {
|
|
1066
|
-
|
|
1102
|
+
appendAgentTrace({
|
|
1067
1103
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1068
1104
|
iteration: currentIter,
|
|
1069
1105
|
kind: 'gemini_cache_skip',
|
|
@@ -1095,7 +1131,7 @@ export class GeminiProvider {
|
|
|
1095
1131
|
const globalCache = _getGeminiGlobalCache(globalCacheKey, now);
|
|
1096
1132
|
if (globalCache) {
|
|
1097
1133
|
try {
|
|
1098
|
-
|
|
1134
|
+
appendAgentTrace({
|
|
1099
1135
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1100
1136
|
iteration: currentIter,
|
|
1101
1137
|
kind: 'gemini_cache_global_hit',
|
|
@@ -1115,7 +1151,7 @@ export class GeminiProvider {
|
|
|
1115
1151
|
const created = await inFlightCreate.catch(() => null);
|
|
1116
1152
|
if (created?.cacheName) {
|
|
1117
1153
|
try {
|
|
1118
|
-
|
|
1154
|
+
appendAgentTrace({
|
|
1119
1155
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1120
1156
|
iteration: currentIter,
|
|
1121
1157
|
kind: 'gemini_cache_global_wait_hit',
|
|
@@ -1169,7 +1205,7 @@ export class GeminiProvider {
|
|
|
1169
1205
|
if (!res.ok) {
|
|
1170
1206
|
const text = await res.text().catch(() => '');
|
|
1171
1207
|
try {
|
|
1172
|
-
|
|
1208
|
+
appendAgentTrace({
|
|
1173
1209
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1174
1210
|
iteration: currentIter,
|
|
1175
1211
|
kind: 'gemini_cache_create_fail',
|
|
@@ -1189,7 +1225,7 @@ export class GeminiProvider {
|
|
|
1189
1225
|
if (!cacheName) return null;
|
|
1190
1226
|
const cacheTokenSize = Number(data?.usageMetadata?.totalTokenCount || 0) || 0;
|
|
1191
1227
|
try {
|
|
1192
|
-
|
|
1228
|
+
appendAgentTrace({
|
|
1193
1229
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1194
1230
|
iteration: currentIter,
|
|
1195
1231
|
kind: 'gemini_cache_create_ok',
|
|
@@ -1242,6 +1278,9 @@ export class GeminiProvider {
|
|
|
1242
1278
|
}
|
|
1243
1279
|
|
|
1244
1280
|
async send(messages, model, tools, sendOpts) {
|
|
1281
|
+
// Re-warm a kept-alive socket before the turn (TTL-gated no-op while
|
|
1282
|
+
// hot) so a post-idle request skips the cold TLS handshake.
|
|
1283
|
+
preconnect('https://generativelanguage.googleapis.com');
|
|
1245
1284
|
try {
|
|
1246
1285
|
return await this._doSend(messages, model, tools, sendOpts);
|
|
1247
1286
|
} catch (err) {
|
|
@@ -1275,8 +1314,12 @@ export class GeminiProvider {
|
|
|
1275
1314
|
if (!contents.length)
|
|
1276
1315
|
throw new Error('No messages to send');
|
|
1277
1316
|
|
|
1278
|
-
const
|
|
1279
|
-
const
|
|
1317
|
+
const nativeGeminiTools = toGeminiNativeTools(opts.nativeTools);
|
|
1318
|
+
const functionGeminiTools = tools?.length ? [toGeminiTools(tools)] : [];
|
|
1319
|
+
const geminiTools = nativeGeminiTools.length || functionGeminiTools.length
|
|
1320
|
+
? [...nativeGeminiTools, ...functionGeminiTools]
|
|
1321
|
+
: undefined;
|
|
1322
|
+
const toolConfig = functionGeminiTools.length ? toGeminiToolConfig(opts.toolChoice) : undefined;
|
|
1280
1323
|
try { opts.onStageChange?.('requesting'); } catch {}
|
|
1281
1324
|
|
|
1282
1325
|
// Explicit cachedContents (system + tools + prior-turn transcript).
|
|
@@ -1478,6 +1521,7 @@ export class GeminiProvider {
|
|
|
1478
1521
|
const textParts = candidate?.content?.parts?.filter(p => 'text' in p) ?? [];
|
|
1479
1522
|
const content = textParts.map(p => 'text' in p ? p.text : '').join('');
|
|
1480
1523
|
const toolCalls = parseToolCalls(candidate?.content?.parts ?? []);
|
|
1524
|
+
const citations = collectGeminiGroundingSources(candidate);
|
|
1481
1525
|
emitGeminiToolCalls(toolCalls, onToolCall);
|
|
1482
1526
|
// Inspect candidate.finishReason — Gemini reports terminal status here.
|
|
1483
1527
|
// Only STOP (and the legacy "FINISH_REASON_STOP") plus tool/function-
|
|
@@ -1537,7 +1581,7 @@ export class GeminiProvider {
|
|
|
1537
1581
|
const outputTokens = (um.candidatesTokenCount || 0) + (um.thoughtsTokenCount || 0);
|
|
1538
1582
|
if (cachedContent && inputTokens > 0 && cachedTokens <= 0) {
|
|
1539
1583
|
try {
|
|
1540
|
-
|
|
1584
|
+
appendAgentTrace({
|
|
1541
1585
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1542
1586
|
iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
|
|
1543
1587
|
kind: 'gemini_cache_anomaly',
|
|
@@ -1553,7 +1597,7 @@ export class GeminiProvider {
|
|
|
1553
1597
|
});
|
|
1554
1598
|
} catch {}
|
|
1555
1599
|
}
|
|
1556
|
-
|
|
1600
|
+
traceAgentUsage({
|
|
1557
1601
|
sessionId: opts.sessionId || opts.session?.id || null,
|
|
1558
1602
|
iteration: Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null,
|
|
1559
1603
|
inputTokens,
|
|
@@ -1571,6 +1615,7 @@ export class GeminiProvider {
|
|
|
1571
1615
|
content,
|
|
1572
1616
|
model: useModel,
|
|
1573
1617
|
toolCalls,
|
|
1618
|
+
citations: citations.length ? citations : undefined,
|
|
1574
1619
|
providerState: opts.providerState,
|
|
1575
1620
|
usage: um ? (() => {
|
|
1576
1621
|
const input = um.promptTokenCount || um.totalTokenCount || 0;
|
|
@@ -1580,7 +1625,7 @@ export class GeminiProvider {
|
|
|
1580
1625
|
// Use the already-computed cachedTokens (with
|
|
1581
1626
|
// cache-create fallback applied) rather than the raw
|
|
1582
1627
|
// metadata field, so the returned usage matches what
|
|
1583
|
-
//
|
|
1628
|
+
// traceAgentUsage recorded for this same call.
|
|
1584
1629
|
cachedTokens,
|
|
1585
1630
|
// Gemini promptTokenCount is total (cachedContentTokenCount
|
|
1586
1631
|
// is a subset). Alias directly into promptTokens.
|