mixdog 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/lib/gate-scheduler.mjs +2 -2
- package/scripts/lib/run-node-tests.mjs +5 -14
- package/scripts/mobile-release.mjs +5 -4
- package/scripts/provider-pingpong-live.mjs +9 -9
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +11 -0
- package/src/defaults/skills/browser-use/SKILL.md +2 -1
- package/src/defaults/skills/computer-use/SKILL.md +3 -1
- package/src/defaults/skills/visualize/SKILL.md +3 -2
- package/src/lib/rules-builder.cjs +1 -1
- package/src/output-styles/common.md +22 -5
- package/src/output-styles/detailed.md +7 -5
- package/src/output-styles/minimal.md +7 -3
- package/src/output-styles/simple.md +2 -3
- package/src/rules/agent/AGENT.md +3 -1
- package/src/rules/lead/LEAD.md +10 -7
- package/src/rules/shared/10-tool-workflow.md +2 -1
- package/src/rules/shared/60-verification.md +4 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
- package/src/runtime/agent/orchestrator/config.mjs +6 -0
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
- package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
- package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +255 -32
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
- package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
- package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
- package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
- package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/attachments/office-extract.mjs +11 -9
- package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
- package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
- package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
- package/src/runtime/office/core/tabular.mjs +1 -1
- package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
- package/src/runtime/office/portable/docx-formatting.mjs +1 -1
- package/src/runtime/office/portable/docx-runs.mjs +1 -1
- package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
- package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
- package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
- package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
- package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
- package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
- package/src/runtime/office/portable/portable-opc.mjs +27 -13
- package/src/runtime/office/portable/portable-pivot.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
- package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
- package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
- package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
- package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
- package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
- package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
- package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
- package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
- package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
- package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
- package/src/runtime/office/portable/portable-xml.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +22 -13
- package/src/runtime/shared/background-tasks.mjs +60 -20
- package/src/runtime/shared/child-spawn-gate.mjs +26 -16
- package/src/runtime/shared/config.mjs +0 -10
- package/src/runtime/shared/file-lock.mjs +7 -1
- package/src/runtime/shared/file-permissions-worker.mjs +38 -0
- package/src/runtime/shared/file-permissions.d.mts +19 -4
- package/src/runtime/shared/file-permissions.mjs +310 -4
- package/src/runtime/shared/git-runner-worker.mjs +6 -78
- package/src/runtime/shared/llm/http-agent.mjs +7 -3
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
- package/src/runtime/shared/memory-snapshot.mjs +5 -1
- package/src/runtime/shared/notify-trace.mjs +8 -1
- package/src/runtime/shared/pristine-execution-contract.json +5 -1
- package/src/runtime/shared/process-runner-worker.mjs +7 -0
- package/src/runtime/shared/process-runner.mjs +38 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -1
- package/src/runtime/shared/provider-api-key.mjs +0 -1
- package/src/runtime/shared/read-row-numbers.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +60 -18
- package/src/runtime/shared/run-child.mjs +84 -0
- package/src/runtime/shared/session-activity-signal.mjs +23 -0
- package/src/runtime/shared/shell-output-summary.mjs +23 -0
- package/src/runtime/shared/singleton-owner.mjs +126 -42
- package/src/runtime/shared/tool-output-limit.mjs +4 -2
- package/src/runtime/shared/tool-output-marker.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +28 -9
- package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
- package/src/runtime/shared/worktrees/git-head.mjs +20 -2
- package/src/runtime/shared/worktrees/lock.mjs +3 -2
- package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
- package/src/runtime/tidy/runners/biome.mjs +3 -1
- package/src/runtime/tidy/shadow.mjs +2 -1
- package/src/runtime/web-search/index.mjs +5 -7
- package/src/runtime/web-search/lib/formatter.mjs +42 -6
- package/src/runtime/web-search/tool-defs.mjs +6 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
- package/src/session-runtime/lifecycle/shared.mjs +19 -2
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +5 -2
- package/src/session-runtime/lifecycle-api.mjs +3 -1
- package/src/session-runtime/provider-auth/login.mjs +10 -6
- package/src/session-runtime/provider-auth/setup.mjs +19 -2
- package/src/session-runtime/provider-catalog-cache.mjs +101 -25
- package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
- package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
- package/src/session-runtime/provider-models.mjs +76 -10
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
- package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
- package/src/session-runtime/services/agent-tool.mjs +20 -0
- package/src/session-runtime/services/provider-admin.mjs +92 -20
- package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
- package/src/session-runtime/services/usage-dashboard.mjs +27 -69
- package/src/session-runtime/turn/session-ops.mjs +2 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/usage-stats-api.mjs +6 -1
- package/src/standalone/channel-transport/http-routes.mjs +10 -9
- package/src/standalone/daemon-desktop-runtime.mjs +20 -7
- package/src/standalone/daemon-owner.mjs +117 -25
- package/src/standalone/daemon.mjs +43 -13
- package/src/standalone/desktop-service-registry.mjs +32 -5
- package/src/standalone/session-call-cache.mjs +6 -0
- package/src/standalone/session-protocol.mjs +1 -1
- package/src/standalone/session-service/retention/busy.mjs +7 -2
- package/src/standalone/session-transport/routes.mjs +7 -6
- package/src/tui/app/model-options.mjs +39 -2
- package/src/tui/app/model-picker.mjs +16 -2
- package/src/tui/app/provider-setup-picker.mjs +1 -2
- package/src/tui/dist/index.mjs +436 -123
- package/src/tui/session/notice-text.mjs +0 -1
- package/src/tui/session/session-api/intake/submission.mjs +7 -13
- package/src/tui/session/session-api/integrations.mjs +1 -5
- package/src/tui/session/session-flow/drain.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +46 -0
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/turn-stream.mjs +33 -0
- package/src/tui/session/turn.mjs +30 -3
- package/src/ui/statusline.mjs +1 -6
- package/src/ui/streaming-markdown-heal.mjs +94 -25
- package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
- package/src/workflows/default/WORKFLOW.md +9 -13
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Bounded, single-flight, cancellable provider catalog work, shared by the
|
|
2
|
+
// registry's catalog refreshes and the session runtime's shared catalog.
|
|
3
|
+
//
|
|
4
|
+
// - Deadline: a caller waits at most `timeoutMs` and then gets an ETIMEDOUT
|
|
5
|
+
// error entry instead of the provider's answer, so one hung provider cannot
|
|
6
|
+
// hold a catalog read or a startup/forced refresh.
|
|
7
|
+
// - Cancellation: on that deadline the operation's AbortSignal fires; a
|
|
8
|
+
// provider whose listModels honors `{ signal }` stops its work there.
|
|
9
|
+
// - Single flight: per provider at most one operation is pending in this
|
|
10
|
+
// process. A caller that finds one of its own kind joins it (with its own
|
|
11
|
+
// deadline); one that finds the other kind (a refresh vs a model list)
|
|
12
|
+
// waits for it inside its deadline before starting its own. An operation
|
|
13
|
+
// leaves the table only when the underlying work settles, not when a
|
|
14
|
+
// caller gives up, so repeated timed-out reads never pile up work; once an
|
|
15
|
+
// operation overran one deadline, later callers fail fast on it instead of
|
|
16
|
+
// each waiting out another deadline, until it settles.
|
|
17
|
+
// - Generations: one auth/account generation spans refreshes and lists alike
|
|
18
|
+
// (bumpProviderCatalogGeneration). Work of the other kind pending from an
|
|
19
|
+
// older generation, or from an older provider instance, never blocks or
|
|
20
|
+
// fails a caller of the newer one: that caller starts afresh. Same-kind
|
|
21
|
+
// work is never joined across generations, instances or a caller-supplied
|
|
22
|
+
// `epoch` (the catalog revision a list belongs to) either, unless it
|
|
23
|
+
// stalled (below).
|
|
24
|
+
// - Stalled work: an operation that overran its deadline (its signal fired)
|
|
25
|
+
// and still has not settled ignores cancellation. Until it settles, that
|
|
26
|
+
// provider name is not asked again for the same kind, not by a newer
|
|
27
|
+
// generation, epoch or provider instance: callers get an ETIMEDOUT entry
|
|
28
|
+
// marked `stalled` at once. Otherwise every account change, catalog
|
|
29
|
+
// revision or registry rebuild would leave one more never-ending call
|
|
30
|
+
// behind in the provider. Superseded calls can only overlap inside one
|
|
31
|
+
// deadline window, before any of them stalls.
|
|
32
|
+
import { clearProviderCatalogFailures, recordProviderCatalogOutcome } from './catalog-failures.mjs';
|
|
33
|
+
|
|
34
|
+
export const PROVIDER_CATALOG_TIMEOUT_MS = 4_000;
|
|
35
|
+
|
|
36
|
+
/** name -> the latest op started for that provider (either kind). */
|
|
37
|
+
const pendingOps = new Map();
|
|
38
|
+
/** `${kind}\0${name}` -> Set of the unsettled ops of that kind, of any
|
|
39
|
+
* generation, epoch or provider instance; emptied sets are removed. */
|
|
40
|
+
const runningOps = new Map();
|
|
41
|
+
/** `${kind}\0${name}` -> sequence number of the latest op started of that kind. */
|
|
42
|
+
const latestOpSeq = new Map();
|
|
43
|
+
let opSeq = 0;
|
|
44
|
+
let generation = 0;
|
|
45
|
+
|
|
46
|
+
const runKey = (name, kind) => `${kind}\0${name}`;
|
|
47
|
+
|
|
48
|
+
/** Opens a new auth/account generation for every provider's catalog work. */
|
|
49
|
+
export function bumpProviderCatalogGeneration() {
|
|
50
|
+
generation += 1;
|
|
51
|
+
// Failures of the previous account say nothing about the new one.
|
|
52
|
+
clearProviderCatalogFailures();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Provider names with catalog work still pending (diagnostics and tests). */
|
|
56
|
+
export function pendingProviderCatalogOps() {
|
|
57
|
+
return [...pendingOps.keys()];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `{ name, kind }` of every operation that ignored its deadline's abort and
|
|
61
|
+
* is still unsettled (diagnostics and tests). */
|
|
62
|
+
export function stalledProviderCatalogOps() {
|
|
63
|
+
return [...runningOps.values()]
|
|
64
|
+
.flatMap((ops) => [...ops])
|
|
65
|
+
.filter((op) => op.timedOut)
|
|
66
|
+
.map(({ name, kind }) => ({ name, kind }));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isStalled = (name, kind) => [...(runningOps.get(runKey(name, kind)) || [])].some((op) => op.timedOut);
|
|
70
|
+
|
|
71
|
+
function startOp(name, provider, kind, run, epoch, opGeneration) {
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const settled = Promise.resolve()
|
|
74
|
+
.then(() => run(controller.signal))
|
|
75
|
+
.then(
|
|
76
|
+
(value) => ({ value }),
|
|
77
|
+
(error) => ({ error })
|
|
78
|
+
);
|
|
79
|
+
const seq = ++opSeq;
|
|
80
|
+
const op = { name, provider, kind, epoch, generation: opGeneration, seq, controller, settled, done: false };
|
|
81
|
+
const key = runKey(name, kind);
|
|
82
|
+
latestOpSeq.set(key, seq);
|
|
83
|
+
const running = runningOps.get(key) || new Set();
|
|
84
|
+
running.add(op);
|
|
85
|
+
runningOps.set(key, running);
|
|
86
|
+
pendingOps.set(name, op);
|
|
87
|
+
void settled.then(() => {
|
|
88
|
+
op.done = true;
|
|
89
|
+
if (pendingOps.get(name) === op) pendingOps.delete(name);
|
|
90
|
+
running.delete(op);
|
|
91
|
+
if (running.size === 0 && runningOps.get(key) === running) runningOps.delete(key);
|
|
92
|
+
});
|
|
93
|
+
return op;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function timeoutError(message) {
|
|
97
|
+
const error = new Error(message);
|
|
98
|
+
error.code = 'ETIMEDOUT';
|
|
99
|
+
return error;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Resolves `{ value }`, `{ error }`, or `{ error, timedOut: true }`; never rejects.
|
|
103
|
+
* The outcome is also kept as the provider's latest catalog failure (or
|
|
104
|
+
* recovery) for views that explain an empty list (catalog-failures.mjs),
|
|
105
|
+
* but only while it is current: a caller of a superseded generation, or one
|
|
106
|
+
* answered by an op that a newer op of its kind replaced, publishes nothing. */
|
|
107
|
+
export async function boundedProviderCatalogOp(op) {
|
|
108
|
+
const callerGeneration = generation;
|
|
109
|
+
const answered = { op: null };
|
|
110
|
+
const outcome = await runBoundedOp(op, callerGeneration, answered);
|
|
111
|
+
const currentOp = !answered.op || latestOpSeq.get(runKey(op.name, op.kind)) === answered.op.seq;
|
|
112
|
+
if (callerGeneration === generation && currentOp) recordProviderCatalogOutcome(op.name, outcome.error);
|
|
113
|
+
return outcome;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function runBoundedOp(
|
|
117
|
+
{ name, provider, kind, run, timeoutMs = PROVIDER_CATALOG_TIMEOUT_MS, epoch = 0 },
|
|
118
|
+
callerGeneration,
|
|
119
|
+
answered
|
|
120
|
+
) {
|
|
121
|
+
let timer = null;
|
|
122
|
+
let watched = null;
|
|
123
|
+
const deadline = new Promise((resolve) => {
|
|
124
|
+
timer = setTimeout(() => {
|
|
125
|
+
const error = timeoutError(`provider "${name}" catalog ${kind} did not finish within ${timeoutMs}ms`);
|
|
126
|
+
if (watched) {
|
|
127
|
+
// Stalled until it settles: an op that honors the abort settles (and
|
|
128
|
+
// leaves the table) a few microtasks later, one that ignores it stays.
|
|
129
|
+
watched.timedOut = true;
|
|
130
|
+
watched.controller.abort(error);
|
|
131
|
+
}
|
|
132
|
+
resolve({ error, timedOut: true });
|
|
133
|
+
}, timeoutMs);
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
for (;;) {
|
|
137
|
+
let op = pendingOps.get(name);
|
|
138
|
+
const superseded =
|
|
139
|
+
!op || op.provider !== provider || op.generation < callerGeneration || (op.kind === kind && op.epoch !== epoch);
|
|
140
|
+
if (superseded) {
|
|
141
|
+
if (isStalled(name, kind)) {
|
|
142
|
+
const error = timeoutError(`provider "${name}" catalog ${kind} ignored its deadline and is still pending`);
|
|
143
|
+
return { error, timedOut: true, stalled: true };
|
|
144
|
+
}
|
|
145
|
+
op = startOp(name, provider, kind, run, epoch, callerGeneration);
|
|
146
|
+
} else if (op.timedOut) {
|
|
147
|
+
// Already overran a deadline and still not settled: fail fast instead
|
|
148
|
+
// of making every later caller wait out another deadline for it.
|
|
149
|
+
const error = timeoutError(`provider "${name}" catalog ${op.kind} is still pending after its deadline`);
|
|
150
|
+
return { error, timedOut: true };
|
|
151
|
+
}
|
|
152
|
+
watched = op;
|
|
153
|
+
if (op.kind === kind) {
|
|
154
|
+
answered.op = op;
|
|
155
|
+
return await Promise.race([op.settled, deadline]);
|
|
156
|
+
}
|
|
157
|
+
const outcome = await Promise.race([op.settled.then(() => null), deadline]);
|
|
158
|
+
if (outcome) return outcome;
|
|
159
|
+
}
|
|
160
|
+
} finally {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The latest catalog failure of each provider, kept so a view that lists a
|
|
2
|
+
// provider's models can say WHY the list is empty (expired key, spent credits,
|
|
3
|
+
// timeout) instead of showing a silent 0/0. Cleared by the provider's next
|
|
4
|
+
// answer and by an account change.
|
|
5
|
+
import { safeErrorDetails } from '../../../shared/error-presentation.mjs';
|
|
6
|
+
import { providerErrorKind } from './provider-error-kind.mjs';
|
|
7
|
+
|
|
8
|
+
const MESSAGE_LIMIT = 300;
|
|
9
|
+
const failures = new Map();
|
|
10
|
+
|
|
11
|
+
/** Records a provider's catalog failure (`error`) or its recovery (no error). */
|
|
12
|
+
export function recordProviderCatalogOutcome(name, error) {
|
|
13
|
+
if (!error) {
|
|
14
|
+
failures.delete(name);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const reason = typeof error.providerError?.message === 'string' ? error.providerError.message : '';
|
|
18
|
+
const text = safeErrorDetails(reason ? `${error.message}: ${reason}` : error.message);
|
|
19
|
+
failures.set(name, {
|
|
20
|
+
// Plain text, never a JSON envelope: describeError reads the kind below.
|
|
21
|
+
message: text.replace(/\s+/g, ' ').slice(0, MESSAGE_LIMIT),
|
|
22
|
+
errorKind: providerErrorKind(error),
|
|
23
|
+
at: Date.now(),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `{ message, errorKind, at }` of the provider's latest failure, or null. */
|
|
28
|
+
export function providerCatalogFailure(name) {
|
|
29
|
+
return failures.get(name) ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function clearProviderCatalogFailures() {
|
|
33
|
+
failures.clear();
|
|
34
|
+
}
|
|
@@ -4,7 +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
|
+
import { OAUTH_PAGE_CONTENT_TYPE, oauthErrorHtml, oauthSuccessHtml } from './lib/oauth-page.mjs';
|
|
8
8
|
import { emailFromJwts } from './lib/oauth-token-utils.mjs';
|
|
9
9
|
import {
|
|
10
10
|
CLIENT_ID,
|
|
@@ -26,7 +26,8 @@ export function generatePKCE() {
|
|
|
26
26
|
return createOAuthPkce();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export async function exchangeAuthorizationCode({ discovery, pkce, code }) {
|
|
29
|
+
export async function exchangeAuthorizationCode({ discovery, pkce, code, signal = null }) {
|
|
30
|
+
signal?.throwIfAborted();
|
|
30
31
|
const cleanCode = String(code || '').trim();
|
|
31
32
|
if (!cleanCode) throw new Error('[grok-oauth] authorization code is required');
|
|
32
33
|
const tokenRes = await fetch(discovery.token_endpoint, {
|
|
@@ -47,7 +48,9 @@ export async function exchangeAuthorizationCode({ discovery, pkce, code }) {
|
|
|
47
48
|
// Secret-bearing (authorization code + verifier): refuse
|
|
48
49
|
// redirects so they can't be replayed to an untrusted host.
|
|
49
50
|
redirect: 'error',
|
|
50
|
-
signal:
|
|
51
|
+
signal: signal
|
|
52
|
+
? AbortSignal.any([signal, AbortSignal.timeout(TOKEN_TIMEOUT_MS)])
|
|
53
|
+
: AbortSignal.timeout(TOKEN_TIMEOUT_MS),
|
|
51
54
|
dispatcher: getLlmDispatcher(),
|
|
52
55
|
});
|
|
53
56
|
if (!tokenRes.ok) {
|
|
@@ -72,6 +75,8 @@ export async function exchangeAuthorizationCode({ discovery, pkce, code }) {
|
|
|
72
75
|
? { email: emailFromJwts(json.id_token, json.access_token) }
|
|
73
76
|
: {}),
|
|
74
77
|
};
|
|
78
|
+
// A cancelled login never stores the account it was exchanging.
|
|
79
|
+
signal?.throwIfAborted();
|
|
75
80
|
saveTokens(tokens);
|
|
76
81
|
return tokens;
|
|
77
82
|
}
|
|
@@ -96,11 +101,30 @@ export async function beginOAuthLogin({ openBrowser = true } = {}) {
|
|
|
96
101
|
let server = null;
|
|
97
102
|
let timeout = null;
|
|
98
103
|
let finish = null;
|
|
104
|
+
let exchangePromise = null;
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
// Browser and manual callbacks share one exchange: an authorization code is
|
|
107
|
+
// single-use, and a duplicate must not settle the login before the first.
|
|
108
|
+
const acceptCode = (code) => {
|
|
109
|
+
if (exchangePromise) return exchangePromise;
|
|
110
|
+
controller.signal.throwIfAborted();
|
|
111
|
+
// The browser deadline ends when a code arrives; the exchange has its own.
|
|
112
|
+
if (timeout) clearTimeout(timeout);
|
|
113
|
+
timeout = null;
|
|
114
|
+
exchangePromise = exchangeAuthorizationCode({ discovery, pkce, code, signal: controller.signal });
|
|
115
|
+
exchangePromise.then(
|
|
116
|
+
(tokens) => finish(tokens),
|
|
117
|
+
(err) => finish(null, err instanceof Error ? err : new Error(String(err)))
|
|
118
|
+
);
|
|
119
|
+
return exchangePromise;
|
|
120
|
+
};
|
|
99
121
|
const waitForCallback = new Promise((resolve, reject) => {
|
|
100
122
|
let settled = false;
|
|
101
123
|
finish = (value, error = null) => {
|
|
102
124
|
if (settled) return;
|
|
103
125
|
settled = true;
|
|
126
|
+
// Work arriving after the login settled (or was cancelled) is rejected.
|
|
127
|
+
controller.abort();
|
|
104
128
|
if (timeout) clearTimeout(timeout);
|
|
105
129
|
try {
|
|
106
130
|
server?.close();
|
|
@@ -120,17 +144,18 @@ export async function beginOAuthLogin({ openBrowser = true } = {}) {
|
|
|
120
144
|
const code = u.searchParams.get('code');
|
|
121
145
|
if (!code || u.searchParams.get('state') !== state) {
|
|
122
146
|
// Reject this request only; the valid callback may still arrive.
|
|
123
|
-
res.writeHead(400);
|
|
124
|
-
res.end('
|
|
147
|
+
res.writeHead(400, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
148
|
+
res.end(oauthErrorHtml('The sign-in callback was invalid or did not match this login.'));
|
|
125
149
|
return;
|
|
126
150
|
}
|
|
127
|
-
res.writeHead(200, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
128
|
-
res.end(oauthSuccessHtml('Grok'));
|
|
129
151
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
await acceptCode(code);
|
|
153
|
+
res.writeHead(200, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
154
|
+
res.end(oauthSuccessHtml('Grok'));
|
|
155
|
+
} catch {
|
|
156
|
+
// The shared exchange reports its error through waitForCallback.
|
|
157
|
+
res.writeHead(500, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
|
|
158
|
+
res.end(oauthErrorHtml('Grok could not complete the token exchange.'));
|
|
134
159
|
}
|
|
135
160
|
});
|
|
136
161
|
timeout = setTimeout(() => finish(null), LOGIN_TIMEOUT_MS);
|
|
@@ -161,9 +186,8 @@ export async function beginOAuthLogin({ openBrowser = true } = {}) {
|
|
|
161
186
|
completeCode: async (input) => {
|
|
162
187
|
const parsed = parseOAuthCodeInput(input, { allowHashState: true });
|
|
163
188
|
if (parsed.state && parsed.state !== state) throw new Error('[grok-oauth] OAuth state mismatch');
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return tokens;
|
|
189
|
+
if (!parsed.code) throw new Error('[grok-oauth] authorization code is required');
|
|
190
|
+
return await acceptCode(parsed.code);
|
|
167
191
|
},
|
|
168
192
|
cancel: () => {
|
|
169
193
|
finish?.(null);
|
|
@@ -31,17 +31,61 @@ function escapeHtml(value) {
|
|
|
31
31
|
.replaceAll("'", ''');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// The callback servers do not know the app language, so the default is
|
|
35
|
+
// bilingual (Korean first). A known language picks one copy.
|
|
36
|
+
const COPY = {
|
|
37
|
+
ko: {
|
|
38
|
+
successTitle: '로그인 완료',
|
|
39
|
+
successBody: (name) => `${name} 로그인이 완료되었습니다. 이 탭을 닫고 Mixdog로 돌아가 주세요.`,
|
|
40
|
+
failureTitle: '로그인 실패',
|
|
41
|
+
failureBody: 'Mixdog로 돌아가 다시 시도해 주세요.',
|
|
42
|
+
reason: '사유',
|
|
43
|
+
close: '닫기',
|
|
44
|
+
},
|
|
45
|
+
en: {
|
|
46
|
+
successTitle: 'Sign-in complete',
|
|
47
|
+
successBody: (name) => `You are signed in to ${name}. You can close this tab and return to Mixdog.`,
|
|
48
|
+
failureTitle: 'Sign-in failed',
|
|
49
|
+
failureBody: 'Return to Mixdog and try again.',
|
|
50
|
+
reason: 'Reason',
|
|
51
|
+
close: 'Close',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function renderPage({ kind, providerName, reason, lang }) {
|
|
56
|
+
const langs = lang === 'ko' || lang === 'en' ? [lang] : ['ko', 'en'];
|
|
57
|
+
const blocks = langs
|
|
58
|
+
.map((code, index) => {
|
|
59
|
+
const copy = COPY[code];
|
|
60
|
+
const title = kind === 'success' ? copy.successTitle : copy.failureTitle;
|
|
61
|
+
const body = kind === 'success' ? copy.successBody(providerName) : copy.failureBody;
|
|
62
|
+
const tag = index === 0 ? 'h1' : 'h2';
|
|
63
|
+
return `<section lang="${code}" class="${index === 0 ? 'primary' : 'secondary'}">
|
|
64
|
+
<${tag}>${escapeHtml(title)}</${tag}>
|
|
65
|
+
<p>${escapeHtml(body)}</p>
|
|
66
|
+
</section>`;
|
|
67
|
+
})
|
|
68
|
+
.join('\n ');
|
|
69
|
+
const reasonBlock = reason
|
|
70
|
+
? `<div class="reason"><span>${langs.map((code) => COPY[code].reason).join(' / ')}</span>${escapeHtml(reason)}</div>`
|
|
71
|
+
: '';
|
|
72
|
+
const close = langs.map((code) => COPY[code].close).join(' · ');
|
|
73
|
+
const documentTitle = escapeHtml(
|
|
74
|
+
`${langs.map((code) => COPY[code][kind === 'success' ? 'successTitle' : 'failureTitle']).join(' / ')} · Mixdog`
|
|
75
|
+
);
|
|
36
76
|
return `<!doctype html>
|
|
37
|
-
<html lang="
|
|
77
|
+
<html lang="${langs[0]}">
|
|
38
78
|
<head>
|
|
39
79
|
<meta charset="utf-8" />
|
|
40
80
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
41
|
-
<
|
|
81
|
+
<meta name="color-scheme" content="dark light" />
|
|
82
|
+
<title>${documentTitle}</title>
|
|
42
83
|
<style>
|
|
84
|
+
:root { --bg: #f6f7f9; --fg: #0f172a; --muted: #475569; --card: #ffffff; --line: #d9dee6; --ok: #15803d; --bad: #b91c1c; --link: #1d4ed8; }
|
|
85
|
+
@media (prefers-color-scheme: dark) {
|
|
86
|
+
:root { --bg: #07080b; --fg: #f8fafc; --muted: #94a3b8; --card: #0f1116; --line: #232733; --ok: #4ade80; --bad: #f87171; --link: #93c5fd; }
|
|
87
|
+
}
|
|
43
88
|
* { box-sizing: border-box; }
|
|
44
|
-
html { color-scheme: dark; }
|
|
45
89
|
body {
|
|
46
90
|
margin: 0;
|
|
47
91
|
min-height: 100vh;
|
|
@@ -49,32 +93,65 @@ function renderPage({ heading, message, details }) {
|
|
|
49
93
|
align-items: center;
|
|
50
94
|
justify-content: center;
|
|
51
95
|
padding: 24px;
|
|
52
|
-
background:
|
|
53
|
-
color:
|
|
54
|
-
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
96
|
+
background: var(--bg);
|
|
97
|
+
color: var(--fg);
|
|
98
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Malgun Gothic", "Apple SD Gothic Neo", "Noto Sans KR", Roboto, Arial, sans-serif;
|
|
55
99
|
text-align: center;
|
|
56
100
|
}
|
|
57
|
-
main {
|
|
58
|
-
|
|
101
|
+
main {
|
|
102
|
+
width: 100%;
|
|
103
|
+
max-width: 520px;
|
|
104
|
+
padding: 40px 32px 32px;
|
|
105
|
+
background: var(--card);
|
|
106
|
+
border: 1px solid var(--line);
|
|
107
|
+
border-radius: 16px;
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
align-items: center;
|
|
111
|
+
}
|
|
112
|
+
.logo { width: 72px; height: 72px; margin-bottom: 20px; }
|
|
59
113
|
.logo svg { width: 100%; height: 100%; display: block; }
|
|
60
|
-
h1 { margin: 0 0
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
114
|
+
h1, h2 { margin: 0 0 6px; line-height: 1.2; font-weight: 650; }
|
|
115
|
+
h1 { font-size: 28px; color: ${kind === 'success' ? 'var(--ok)' : 'var(--bad)'}; }
|
|
116
|
+
h2 { font-size: 16px; color: var(--muted); font-weight: 600; }
|
|
117
|
+
p { margin: 0; line-height: 1.7; color: var(--muted); font-size: 15px; }
|
|
118
|
+
section.secondary { margin-top: 18px; }
|
|
119
|
+
.reason {
|
|
120
|
+
margin-top: 20px;
|
|
121
|
+
width: 100%;
|
|
122
|
+
padding: 12px 14px;
|
|
123
|
+
border: 1px solid var(--line);
|
|
124
|
+
border-radius: 10px;
|
|
125
|
+
text-align: left;
|
|
126
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
65
127
|
font-size: 13px;
|
|
66
|
-
color:
|
|
128
|
+
color: var(--fg);
|
|
67
129
|
white-space: pre-wrap;
|
|
68
130
|
word-break: break-word;
|
|
69
131
|
}
|
|
132
|
+
.reason span { display: block; margin-bottom: 4px; color: var(--muted); font-family: inherit; font-size: 12px; }
|
|
133
|
+
button {
|
|
134
|
+
margin-top: 24px;
|
|
135
|
+
min-width: 120px;
|
|
136
|
+
height: 40px;
|
|
137
|
+
padding: 0 20px;
|
|
138
|
+
border: 1px solid var(--line);
|
|
139
|
+
border-radius: 10px;
|
|
140
|
+
background: transparent;
|
|
141
|
+
color: var(--fg);
|
|
142
|
+
font: inherit;
|
|
143
|
+
font-size: 14px;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
}
|
|
146
|
+
button:hover { border-color: var(--muted); }
|
|
70
147
|
</style>
|
|
71
148
|
</head>
|
|
72
149
|
<body>
|
|
73
150
|
<main>
|
|
74
151
|
<div class="logo">${LOGO_SVG}</div>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
152
|
+
${blocks}
|
|
153
|
+
${reasonBlock}
|
|
154
|
+
<button type="button" onclick="window.close()">${escapeHtml(close)}</button>
|
|
78
155
|
</main>
|
|
79
156
|
</body>
|
|
80
157
|
</html>`;
|
|
@@ -82,13 +159,10 @@ function renderPage({ heading, message, details }) {
|
|
|
82
159
|
|
|
83
160
|
export const OAUTH_PAGE_CONTENT_TYPE = 'text/html; charset=utf-8';
|
|
84
161
|
|
|
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
|
-
});
|
|
162
|
+
export function oauthSuccessHtml(providerName, { lang } = {}) {
|
|
163
|
+
return renderPage({ kind: 'success', providerName, lang });
|
|
90
164
|
}
|
|
91
165
|
|
|
92
|
-
export function oauthErrorHtml(
|
|
93
|
-
return renderPage({
|
|
166
|
+
export function oauthErrorHtml(reason, { lang } = {}) {
|
|
167
|
+
return renderPage({ kind: 'failure', reason, lang });
|
|
94
168
|
}
|
|
@@ -8,5 +8,29 @@ export function streamCallbacks(opts) {
|
|
|
8
8
|
onToolCall: functionOrNull(opts.onToolCall),
|
|
9
9
|
onTextDelta: functionOrNull(opts.onTextDelta),
|
|
10
10
|
onTextReset: functionOrNull(opts.onTextReset),
|
|
11
|
+
onStreamItemStart: functionOrNull(opts.onStreamItemStart),
|
|
11
12
|
};
|
|
12
13
|
}
|
|
14
|
+
|
|
15
|
+
/** Tell the caller a provider output item started streaming: `{ kind: 'tool' }`
|
|
16
|
+
* for a client tool call whose arguments are still on the way, `{ kind:
|
|
17
|
+
* 'text', phase }` for an assistant message that names its phase
|
|
18
|
+
* (`commentary` / `final_answer`). A listener failure never reaches the
|
|
19
|
+
* stream. */
|
|
20
|
+
export function reportStreamItemStart(onStreamItemStart, detail) {
|
|
21
|
+
if (typeof onStreamItemStart !== 'function' || !detail) return;
|
|
22
|
+
try {
|
|
23
|
+
onStreamItemStart(detail);
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ASSISTANT_PHASES = new Set(['commentary', 'final_answer']);
|
|
28
|
+
|
|
29
|
+
/** What a Responses output item tells `reportStreamItemStart`: a client tool
|
|
30
|
+
* call, an assistant message carrying its phase, or nothing (null). */
|
|
31
|
+
export function responsesItemStart(item) {
|
|
32
|
+
const type = item?.type;
|
|
33
|
+
if (type === 'function_call' || type === 'custom_tool_call' || type === 'tool_search_call') return { kind: 'tool' };
|
|
34
|
+
if (type === 'message' && ASSISTANT_PHASES.has(item.phase)) return { kind: 'text', phase: item.phase };
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
@@ -5,6 +5,7 @@ import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
|
5
5
|
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
6
6
|
import { cursorTokenExpiry } from './cursor-auth.mjs';
|
|
7
7
|
import { isValidMuseCredentialDocument } from './muse-code-oauth-tokens.mjs';
|
|
8
|
+
import { isValidOpenCodeGoCredentialDocument } from './opencode-go-oauth-tokens.mjs';
|
|
8
9
|
import { developerOptionEnabled, developerOptionForProvider } from '../../../shared/developer-options.mjs';
|
|
9
10
|
import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
|
|
10
11
|
|
|
@@ -198,6 +199,17 @@ function museCodeOAuthState() {
|
|
|
198
199
|
);
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
function openCodeGoOAuthState() {
|
|
203
|
+
const paths = [
|
|
204
|
+
boundProviderAuthPath('opencode-go-oauth') ||
|
|
205
|
+
process.env.OPENCODE_GO_OAUTH_CREDENTIALS_PATH ||
|
|
206
|
+
join(resolvePluginData(), 'opencode-go-oauth.json'),
|
|
207
|
+
];
|
|
208
|
+
return memoProbe('opencode-go-oauth', paths, () =>
|
|
209
|
+
resolveProbeState(paths, (docs) => docs.some(isValidOpenCodeGoCredentialDocument))
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
201
213
|
// Antigravity needs a resolved Cloud project alongside the tokens: without it
|
|
202
214
|
// every request 400s, so a half-finished login must not enable the provider.
|
|
203
215
|
function antigravityOAuthState() {
|
|
@@ -270,6 +282,7 @@ const OAUTH_PROBE_STATES = new Map([
|
|
|
270
282
|
['mimo-oauth', mimoOAuthState],
|
|
271
283
|
['cursor-oauth', cursorOAuthState],
|
|
272
284
|
['antigravity-oauth', antigravityOAuthState],
|
|
285
|
+
['opencode-go-oauth', openCodeGoOAuthState],
|
|
273
286
|
]);
|
|
274
287
|
|
|
275
288
|
// Dev-only OAuth providers (Antigravity, Cursor). Each is hidden and treated as
|
|
@@ -869,7 +869,8 @@ async function measureUsage(provider, providerObj, routeInfo) {
|
|
|
869
869
|
provider === 'cursor-api' ||
|
|
870
870
|
provider === 'antigravity-oauth' ||
|
|
871
871
|
provider === 'muse-code-oauth' ||
|
|
872
|
-
provider === 'command-code-oauth'
|
|
872
|
+
provider === 'command-code-oauth' ||
|
|
873
|
+
provider === 'opencode-go-oauth') &&
|
|
873
874
|
typeof providerObj?.getUsageSnapshot === 'function'
|
|
874
875
|
) {
|
|
875
876
|
return await providerObj.getUsageSnapshot();
|
|
@@ -187,6 +187,7 @@ export async function sendCompatChat(provider, messages, useModel, tools, opts)
|
|
|
187
187
|
onStreamDelta: opts.onStreamDelta,
|
|
188
188
|
onToolCall: opts.onToolCall,
|
|
189
189
|
onTextDelta: opts.onTextDelta,
|
|
190
|
+
onStreamItemStart: opts.onStreamItemStart,
|
|
190
191
|
parseToolCalls,
|
|
191
192
|
// Known tool names for the leaked-tool-call guard:
|
|
192
193
|
// recovered leaked calls only synthesize when they name
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* reasoning aliases, tool_calls deltas, finish reason and usage.
|
|
5
5
|
*/
|
|
6
6
|
import { mergeToolCallDelta } from './tool-call-acc.mjs';
|
|
7
|
+
import { reportStreamItemStart } from '../lib/send-callbacks.mjs';
|
|
7
8
|
import { appendText, drainLeakedCalls, relayText, reportProgress, reportTransport } from './stream-state.mjs';
|
|
8
9
|
import { chatContentParts } from '../openai-compat-reasoning-field.mjs';
|
|
9
10
|
|
|
@@ -58,7 +59,10 @@ export function applyCompatChunk(state, chunk) {
|
|
|
58
59
|
if (Array.isArray(delta?.tool_calls) && delta.tool_calls.length) {
|
|
59
60
|
reportProgress(state, 'tool');
|
|
60
61
|
}
|
|
62
|
+
const toolsBefore = state.toolAcc.byKey.size;
|
|
61
63
|
mergeToolCallDelta(state.toolAcc, delta?.tool_calls);
|
|
64
|
+
// A tool call's first delta: its arguments are still streaming.
|
|
65
|
+
if (state.toolAcc.byKey.size > toolsBefore) reportStreamItemStart(state.onStreamItemStart, { kind: 'tool' });
|
|
62
66
|
if (choice?.finish_reason) {
|
|
63
67
|
state.stopReason = choice.finish_reason;
|
|
64
68
|
drainLeakedCalls(state);
|
|
@@ -22,6 +22,7 @@ export function createCompatStreamState({
|
|
|
22
22
|
onStreamDelta,
|
|
23
23
|
onToolCall,
|
|
24
24
|
onTextDelta,
|
|
25
|
+
onStreamItemStart,
|
|
25
26
|
}) {
|
|
26
27
|
const toolDedupe = createToolCallDedupe();
|
|
27
28
|
return {
|
|
@@ -30,6 +31,7 @@ export function createCompatStreamState({
|
|
|
30
31
|
onStreamDelta,
|
|
31
32
|
onToolCall,
|
|
32
33
|
onTextDelta,
|
|
34
|
+
onStreamItemStart,
|
|
33
35
|
semanticIdleDeadlineAt: 0,
|
|
34
36
|
sawFirstEvent: false,
|
|
35
37
|
content: '',
|
|
@@ -86,7 +88,7 @@ export function isIncompleteFinishReason(finishReason) {
|
|
|
86
88
|
|
|
87
89
|
/** Whether `finish_reason` confirms a normal end of the turn: present, not an
|
|
88
90
|
* incomplete outcome and not a gateway-reported failure (network_error/error). */
|
|
89
|
-
|
|
91
|
+
function isNormalFinishReason(finishReason) {
|
|
90
92
|
return (
|
|
91
93
|
Boolean(finishReason) && !isIncompleteFinishReason(finishReason) && !failingFinishReasonError(finishReason, '')
|
|
92
94
|
);
|
|
@@ -39,6 +39,7 @@ export async function consumeCompatChatCompletionStream(
|
|
|
39
39
|
onStreamDelta,
|
|
40
40
|
onToolCall,
|
|
41
41
|
onTextDelta,
|
|
42
|
+
onStreamItemStart,
|
|
42
43
|
parseToolCalls,
|
|
43
44
|
knownToolNames,
|
|
44
45
|
semanticIdleTimeoutMs,
|
|
@@ -55,6 +56,7 @@ export async function consumeCompatChatCompletionStream(
|
|
|
55
56
|
onStreamDelta,
|
|
56
57
|
onToolCall,
|
|
57
58
|
onTextDelta,
|
|
59
|
+
onStreamItemStart,
|
|
58
60
|
});
|
|
59
61
|
reportTransport(state);
|
|
60
62
|
const iterator = stream[Symbol.asyncIterator]();
|