mixdog 0.9.151 → 0.9.153
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/LICENSES/browser-import-NOTICE.txt +11 -0
- package/NOTICE.md +11 -0
- package/README.md +108 -68
- package/package.json +17 -4
- package/scripts/computer-task-cost-report.mjs +144 -0
- package/scripts/run-office-live-tests.mjs +32 -0
- package/scripts/tool-stress.mjs +0 -2
- package/src/agents/front-worker/AGENT.md +13 -0
- package/src/agents/front-worker/agent.json +6 -0
- package/src/agents/heavy-worker/AGENT.md +10 -11
- package/src/agents/heavy-worker/agent.json +1 -1
- package/src/agents/maintainer/AGENT.md +0 -2
- package/src/agents/reviewer/AGENT.md +9 -13
- package/src/agents/reviewer/agent.json +1 -1
- package/src/agents/security/AGENT.md +14 -0
- package/src/agents/security/agent.json +6 -0
- package/src/agents/worker/AGENT.md +8 -8
- package/src/agents/worker/agent.json +1 -1
- package/src/agents/writer/AGENT.md +14 -0
- package/src/agents/writer/agent.json +6 -0
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/rules/agent/00-common.md +4 -7
- package/src/rules/lead/01-general.md +0 -3
- package/src/rules/shared/10-tool-workflow.md +10 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
- package/src/runtime/agent/orchestrator/config.mjs +13 -68
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
- package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
- package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/attachments/pdf-extract.mjs +3 -0
- package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
- package/src/runtime/bridge-clients.test.mjs +827 -0
- package/src/runtime/browser-bridge/action-schema.mjs +251 -0
- package/src/runtime/browser-bridge/client.mjs +152 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
- package/src/runtime/computer-bridge/action-schema.mjs +653 -0
- package/src/runtime/computer-bridge/client.mjs +231 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/office/assurance-benchmark.mjs +358 -0
- package/src/runtime/office/assurance.mjs +833 -0
- package/src/runtime/office/benchmark.mjs +496 -0
- package/src/runtime/office/capabilities.mjs +551 -0
- package/src/runtime/office/com-adapter.mjs +283 -0
- package/src/runtime/office/content-model.mjs +183 -0
- package/src/runtime/office/contract-benchmark.mjs +105 -0
- package/src/runtime/office/design-library.mjs +1562 -0
- package/src/runtime/office/design-system.mjs +1966 -0
- package/src/runtime/office/index.mjs +2413 -0
- package/src/runtime/office/journal.mjs +82 -0
- package/src/runtime/office/office-assurance.test.mjs +371 -0
- package/src/runtime/office/office-com-host.ps1 +3781 -0
- package/src/runtime/office/office-com-session-host.ps1 +855 -0
- package/src/runtime/office/office-live-runtime.test.mjs +819 -0
- package/src/runtime/office/office-runtime.test.mjs +2070 -0
- package/src/runtime/office/ooxml-validator.mjs +271 -0
- package/src/runtime/office/pagination.mjs +132 -0
- package/src/runtime/office/pdf-adapter.mjs +591 -0
- package/src/runtime/office/pdf-analysis.mjs +372 -0
- package/src/runtime/office/pdf-render.mjs +167 -0
- package/src/runtime/office/pdf-security.mjs +63 -0
- package/src/runtime/office/polish-benchmark.mjs +190 -0
- package/src/runtime/office/portable-ooxml.mjs +1867 -0
- package/src/runtime/office/quality-live-benchmark.mjs +486 -0
- package/src/runtime/office/quality-pipeline.mjs +136 -0
- package/src/runtime/office/tabular.mjs +302 -0
- package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
- package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
- package/src/runtime/office/tool-defs.mjs +113 -0
- package/src/runtime/office/visual-diff.mjs +89 -0
- package/src/runtime/office/xlsx-assertions.mjs +172 -0
- package/src/runtime/office/xlsx-contract.mjs +147 -0
- package/src/runtime/shared/agent-route-config.mjs +9 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-execution-contract.mjs +16 -0
- package/src/runtime/shared/tool-surface.mjs +27 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/agent-disable.test.mjs +20 -1
- package/src/session-runtime/context-status.mjs +9 -3
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/deferred-tool-delta.mjs +104 -0
- package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
- package/src/session-runtime/goal-runtime.mjs +1141 -0
- package/src/session-runtime/goal-runtime.test.mjs +645 -0
- package/src/session-runtime/lifecycle-api.mjs +58 -11
- package/src/session-runtime/lifecycle-api.test.mjs +183 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/provider-request-snapshot.mjs +33 -9
- package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
- package/src/session-runtime/resource-api-global.test.mjs +109 -0
- package/src/session-runtime/resource-api.mjs +168 -116
- package/src/session-runtime/runtime-core.mjs +208 -29
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-lifecycle.mjs +34 -12
- package/src/session-runtime/session-title.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +37 -25
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-data.mjs +1 -1
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +42 -112
- package/src/session-runtime/tool-defs.mjs +6 -1
- package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
- package/src/session-runtime/tool-surface.mjs +30 -8
- package/src/session-runtime/workflow-agents-api.mjs +8 -25
- package/src/session-runtime/workflow.mjs +8 -6
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/job-views.mjs +3 -1
- package/src/standalone/agent-tool/notify.mjs +11 -9
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/agent-tool/render.mjs +3 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
- package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/agent-tool/worker-index.mjs +123 -0
- package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +57 -10
- package/src/standalone/daemon.mjs +113 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +709 -58
- package/src/standalone/session-runtime-inline-host.mjs +258 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +302 -26
- package/src/standalone/session-service-agent-tree.test.mjs +984 -0
- package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
- package/src/standalone/session-service.mjs +711 -11
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +187 -45
- package/src/tui/lib/voice-setup.mjs +4 -1
- package/src/tui/session/agent-message-source.test.mjs +37 -0
- package/src/tui/session/completion-card-restore.test.mjs +50 -0
- package/src/tui/session/context-state.mjs +3 -13
- package/src/tui/session/goal-continuation.mjs +141 -0
- package/src/tui/session/goal-continuation.test.mjs +203 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +190 -0
- package/src/tui/session/session-api-ext.mjs +48 -8
- package/src/tui/session/session-api.mjs +73 -1
- package/src/tui/session/session-flow.mjs +31 -1
- package/src/tui/session/turn.mjs +88 -15
- package/src/tui/session-local.mjs +39 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
grokCacheChainTraceFields,
|
|
33
33
|
appendAgentTrace,
|
|
34
34
|
} from '../agent-trace.mjs';
|
|
35
|
+
import { traceCacheBreak } from '../cache-break-trace.mjs';
|
|
35
36
|
import {
|
|
36
37
|
classifyHandshakeError,
|
|
37
38
|
classifyMidstreamError,
|
|
@@ -50,7 +51,6 @@ import {
|
|
|
50
51
|
acquireWebSocket,
|
|
51
52
|
releaseWebSocket,
|
|
52
53
|
_sendFrame,
|
|
53
|
-
_closeAllPooledSockets,
|
|
54
54
|
drainOpenaiWsPool,
|
|
55
55
|
} from './openai-ws-pool.mjs';
|
|
56
56
|
import {
|
|
@@ -66,7 +66,10 @@ import {
|
|
|
66
66
|
parseToolSearchArgs,
|
|
67
67
|
_streamResponse,
|
|
68
68
|
} from './openai-ws-stream.mjs';
|
|
69
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
_buildResponseCreateFrame,
|
|
71
|
+
_requestInputMismatchDiagnostics,
|
|
72
|
+
} from './openai-ws-delta.mjs';
|
|
70
73
|
import { resolveOpenAiTransportPolicy } from './openai-transport-policy.mjs';
|
|
71
74
|
import { envPositiveInt } from './lib/env-utils.mjs';
|
|
72
75
|
|
|
@@ -74,7 +77,6 @@ import { envPositiveInt } from './lib/env-utils.mjs';
|
|
|
74
77
|
// (drainOpenaiWsPool), scripts/provider-toolcall-test.mjs (parseToolSearchArgs,
|
|
75
78
|
// _logicalResponseItemMatch, _streamResponse) and other external callers.
|
|
76
79
|
export {
|
|
77
|
-
_closeAllPooledSockets,
|
|
78
80
|
drainOpenaiWsPool,
|
|
79
81
|
_logicalResponseItemMatch,
|
|
80
82
|
parseToolSearchArgs,
|
|
@@ -591,6 +593,10 @@ export async function sendViaWebSocket({
|
|
|
591
593
|
// One compact row per logical iteration. Values aggregate all handshake
|
|
592
594
|
// and mid-stream attempts, including warmup, without retaining request data.
|
|
593
595
|
const sendSpan = {
|
|
596
|
+
admissionQueueWaitMs: Math.max(0, Number(sendOpts?._providerAdmission?.queueWaitMs) || 0),
|
|
597
|
+
providerConcurrentRequests: Math.max(1, Number(sendOpts?._providerAdmission?.active) || 1),
|
|
598
|
+
providerQueuedRequests: Math.max(0, Number(sendOpts?._providerAdmission?.queued) || 0),
|
|
599
|
+
poolOwnerWaitMs: 0,
|
|
594
600
|
poolAcquireMs: 0,
|
|
595
601
|
requestBuildSerializationMs: 0,
|
|
596
602
|
preResponseCreatedMs: 0,
|
|
@@ -600,14 +606,44 @@ export async function sendViaWebSocket({
|
|
|
600
606
|
acquireAttempts: 0,
|
|
601
607
|
acquireMode: null,
|
|
602
608
|
emitted: false,
|
|
609
|
+
timing: null,
|
|
603
610
|
};
|
|
604
|
-
const emitSendSpan = (outcome) => {
|
|
605
|
-
if (sendSpan.emitted)
|
|
611
|
+
const emitSendSpan = (outcome, target = null) => {
|
|
612
|
+
if (sendSpan.emitted) {
|
|
613
|
+
if (target && sendSpan.timing) {
|
|
614
|
+
try { target.transportTiming = sendSpan.timing; } catch {}
|
|
615
|
+
}
|
|
616
|
+
return sendSpan.timing;
|
|
617
|
+
}
|
|
606
618
|
sendSpan.emitted = true;
|
|
619
|
+
const socketAcquireMs = Math.max(0, sendSpan.poolAcquireMs - sendSpan.poolOwnerWaitMs);
|
|
620
|
+
const timing = {
|
|
621
|
+
transport: 'websocket',
|
|
622
|
+
outcome,
|
|
623
|
+
admissionQueueWaitMs: sendSpan.admissionQueueWaitMs,
|
|
624
|
+
providerConcurrentRequests: sendSpan.providerConcurrentRequests,
|
|
625
|
+
providerQueuedRequests: sendSpan.providerQueuedRequests,
|
|
626
|
+
poolOwnerWaitMs: sendSpan.poolOwnerWaitMs,
|
|
627
|
+
socketAcquireMs,
|
|
628
|
+
poolAcquireMs: sendSpan.poolAcquireMs,
|
|
629
|
+
requestBuildSerializationMs: sendSpan.requestBuildSerializationMs,
|
|
630
|
+
preResponseCreatedMs: sendSpan.preResponseCreatedMs,
|
|
631
|
+
providerFirstEventMs: sendSpan.firstEventMs,
|
|
632
|
+
retryBackoffMs: sendSpan.retryBackoffMs,
|
|
633
|
+
handshakeRetries: sendSpan.handshakeRetries,
|
|
634
|
+
acquireAttempts: sendSpan.acquireAttempts,
|
|
635
|
+
acquireMode: sendSpan.acquireMode || 'failed',
|
|
636
|
+
};
|
|
637
|
+
sendSpan.timing = timing;
|
|
607
638
|
const payload = {
|
|
608
639
|
provider: traceProvider,
|
|
609
640
|
model: useModel,
|
|
610
641
|
transport: 'websocket',
|
|
642
|
+
admission_queue_wait_ms: timing.admissionQueueWaitMs,
|
|
643
|
+
provider_concurrent_requests: timing.providerConcurrentRequests,
|
|
644
|
+
provider_queued_requests: timing.providerQueuedRequests,
|
|
645
|
+
pool_owner_wait_ms: timing.poolOwnerWaitMs,
|
|
646
|
+
socket_acquire_ms: timing.socketAcquireMs,
|
|
611
647
|
acquire_mode: sendSpan.acquireMode || 'failed',
|
|
612
648
|
acquire_attempts: sendSpan.acquireAttempts,
|
|
613
649
|
handshake_retries: sendSpan.handshakeRetries,
|
|
@@ -627,6 +663,10 @@ export async function sendViaWebSocket({
|
|
|
627
663
|
payload,
|
|
628
664
|
});
|
|
629
665
|
} catch {}
|
|
666
|
+
if (target) {
|
|
667
|
+
try { target.transportTiming = timing; } catch {}
|
|
668
|
+
}
|
|
669
|
+
return timing;
|
|
630
670
|
};
|
|
631
671
|
// Single caller-visible recovery path for both handshake/acquire retries
|
|
632
672
|
// and retryable stream failures. The session/TUI stage bridge renders this
|
|
@@ -701,6 +741,7 @@ export async function sendViaWebSocket({
|
|
|
701
741
|
} catch (err) {
|
|
702
742
|
_stampWarmup(err);
|
|
703
743
|
sendSpan.poolAcquireMs += performance.now() - handshakeStart;
|
|
744
|
+
sendSpan.poolOwnerWaitMs += Math.max(0, Number(err?.ownerWaitMs) || 0);
|
|
704
745
|
// Provenance only; policy remains provider-owned below. This lets
|
|
705
746
|
// the direct wrapper distinguish an upgrade rejection from an
|
|
706
747
|
// application error carrying the same HTTP status.
|
|
@@ -746,6 +787,7 @@ export async function sendViaWebSocket({
|
|
|
746
787
|
const retryable = classifier
|
|
747
788
|
&& Number(err?.httpStatus || 0) !== 401
|
|
748
789
|
&& Number(err?.httpStatus || 0) !== 426
|
|
790
|
+
&& err?.unsafeToRetry !== true
|
|
749
791
|
&& !externalSignal?.aborted;
|
|
750
792
|
if (retryable && attemptIndex < MAX_MIDSTREAM_RETRIES) {
|
|
751
793
|
if (!firstAttemptError) {
|
|
@@ -779,7 +821,7 @@ export async function sendViaWebSocket({
|
|
|
779
821
|
try { err.midstreamClassifier = classifier; } catch {}
|
|
780
822
|
try { err.wsRetriesExhausted = true; } catch {}
|
|
781
823
|
}
|
|
782
|
-
emitSendSpan('error');
|
|
824
|
+
emitSendSpan('error', err);
|
|
783
825
|
throw _stampTool(_stampLiveText(err));
|
|
784
826
|
}
|
|
785
827
|
if (attemptIndex > 0 && firstAttemptError) {
|
|
@@ -796,6 +838,7 @@ export async function sendViaWebSocket({
|
|
|
796
838
|
}
|
|
797
839
|
const { entry, reused } = acquired;
|
|
798
840
|
sendSpan.poolAcquireMs += performance.now() - handshakeStart;
|
|
841
|
+
sendSpan.poolOwnerWaitMs += Math.max(0, Number(acquired?.ownerWaitMs) || 0);
|
|
799
842
|
sendSpan.acquireMode = acquired.prewarmed
|
|
800
843
|
? 'prewarmed'
|
|
801
844
|
: entry?.ephemeral ? 'ephemeral' : (reused ? 'reused' : 'new');
|
|
@@ -865,6 +908,7 @@ export async function sendViaWebSocket({
|
|
|
865
908
|
let strippedResponseItems = 0;
|
|
866
909
|
let skippedResponseItems = 0;
|
|
867
910
|
let responseOutputMismatch = null;
|
|
911
|
+
let requestInputMismatch = null;
|
|
868
912
|
let wireFrameHadTurnState = false;
|
|
869
913
|
let wireFrameMetadataTrace = _metadataTrace(null);
|
|
870
914
|
let framePrefixHash = null;
|
|
@@ -952,7 +996,6 @@ export async function sendViaWebSocket({
|
|
|
952
996
|
entry.lastInputPrefixHash = createHash('sha256')
|
|
953
997
|
.update(JSON.stringify(warmupInputArr))
|
|
954
998
|
.digest('hex');
|
|
955
|
-
entry.turnStateFromPrewarm = true;
|
|
956
999
|
try {
|
|
957
1000
|
const warmupPayload = {
|
|
958
1001
|
provider: traceProvider,
|
|
@@ -978,7 +1021,7 @@ export async function sendViaWebSocket({
|
|
|
978
1021
|
// Codex performs generate:false during session startup, then hands
|
|
979
1022
|
// this live client session to the first real turn. Startup callers
|
|
980
1023
|
// stop here; the pooled entry retains its response id, request
|
|
981
|
-
// snapshot
|
|
1024
|
+
// snapshot and socket for the later real send.
|
|
982
1025
|
if (sendOpts?._startupPrewarmOnly === true) {
|
|
983
1026
|
const responseId = warmupResult?.responseId
|
|
984
1027
|
|| startupWarmupResponseId
|
|
@@ -986,8 +1029,7 @@ export async function sendViaWebSocket({
|
|
|
986
1029
|
|| null;
|
|
987
1030
|
if (responseId) entry.startupWarmupResponseId = responseId;
|
|
988
1031
|
else releaseWebSocket({ entry, poolKey, keep: false });
|
|
989
|
-
|
|
990
|
-
return {
|
|
1032
|
+
const out = {
|
|
991
1033
|
content: '',
|
|
992
1034
|
model: warmupResult?.model || useModel,
|
|
993
1035
|
toolCalls: [],
|
|
@@ -1003,6 +1045,8 @@ export async function sendViaWebSocket({
|
|
|
1003
1045
|
? { entry, poolKey, cacheKey }
|
|
1004
1046
|
: null,
|
|
1005
1047
|
};
|
|
1048
|
+
out.transportTiming = emitSendSpan('ok');
|
|
1049
|
+
return out;
|
|
1006
1050
|
}
|
|
1007
1051
|
|
|
1008
1052
|
// A completed generate:false prewarm is a valid continuation
|
|
@@ -1017,6 +1061,7 @@ export async function sendViaWebSocket({
|
|
|
1017
1061
|
strippedResponseItems = delta.strippedResponseItems || 0;
|
|
1018
1062
|
skippedResponseItems = delta.skippedResponseItems || 0;
|
|
1019
1063
|
responseOutputMismatch = delta.responseOutputMismatch || null;
|
|
1064
|
+
requestInputMismatch = delta.requestInputMismatch || null;
|
|
1020
1065
|
const wireFrame = _withCodexWsClientMetadata(frame, entry, useCodexWsClientMetadata, codexMetadataContext);
|
|
1021
1066
|
wireFrameHadTurnState = !!wireFrame?.client_metadata?.['x-codex-turn-state'];
|
|
1022
1067
|
wireFrameMetadataTrace = _metadataTrace(wireFrame?.client_metadata);
|
|
@@ -1314,6 +1359,12 @@ export async function sendViaWebSocket({
|
|
|
1314
1359
|
body: requestBody,
|
|
1315
1360
|
traceProvider,
|
|
1316
1361
|
});
|
|
1362
|
+
if (cacheContinuityResetReason === 'input_prefix_mismatch' && !requestInputMismatch) {
|
|
1363
|
+
requestInputMismatch = _requestInputMismatchDiagnostics(
|
|
1364
|
+
requestBody?.input,
|
|
1365
|
+
entry?.lastRequestInput,
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1317
1368
|
if (result.responseId && keepResponseChain) {
|
|
1318
1369
|
entry.lastResponseId = result.responseId;
|
|
1319
1370
|
entry.lastRequestSansInput = _stableStringify(_sansInput(requestBody, {
|
|
@@ -1433,6 +1484,23 @@ export async function sendViaWebSocket({
|
|
|
1433
1484
|
});
|
|
1434
1485
|
} catch {}
|
|
1435
1486
|
}
|
|
1487
|
+
if (cacheObservation.actualMiss) {
|
|
1488
|
+
traceCacheBreak({
|
|
1489
|
+
sessionId: poolKey,
|
|
1490
|
+
iteration,
|
|
1491
|
+
classification: 'provider_miss',
|
|
1492
|
+
reason: cacheObservation.missReason || 'warm_session_cache_miss',
|
|
1493
|
+
source: 'provider_usage',
|
|
1494
|
+
provider: traceProvider,
|
|
1495
|
+
model: liveModel,
|
|
1496
|
+
transport: 'websocket',
|
|
1497
|
+
cachedTokens: cacheObservation.cachedTokens,
|
|
1498
|
+
promptTokens: cacheObservation.promptTokens,
|
|
1499
|
+
uncachedTokens: cacheObservation.uncachedTokens,
|
|
1500
|
+
cacheRatio: cacheObservation.cacheRatio,
|
|
1501
|
+
actualCacheMiss: true,
|
|
1502
|
+
}, { traceFn: _agentTraceFn });
|
|
1503
|
+
}
|
|
1436
1504
|
// Rebase after a genuine provider retreat so one eviction produces one
|
|
1437
1505
|
// diagnostic instead of a long run of duplicate "dropped" rows. The
|
|
1438
1506
|
// request that exposed the retreat has already rebuilt the prefix; its
|
|
@@ -1496,7 +1564,7 @@ export async function sendViaWebSocket({
|
|
|
1496
1564
|
ws_client_metadata_has_turn_state: wireFrameHadTurnState,
|
|
1497
1565
|
ws_entry_turn_state_available: useCodexWsClientMetadata && !!entry.turnState,
|
|
1498
1566
|
// Fingerprint only. The token is a routing credential, so it is
|
|
1499
|
-
// hashed
|
|
1567
|
+
// hashed rather than logged; the length still
|
|
1500
1568
|
// distinguishes a real server token from a stub value, and the
|
|
1501
1569
|
// hash shows whether one session keeps a single pin or is
|
|
1502
1570
|
// re-issued (reconnect / turn rollover).
|
|
@@ -1507,6 +1575,7 @@ export async function sendViaWebSocket({
|
|
|
1507
1575
|
chain_stripped_response_items: strippedResponseItems,
|
|
1508
1576
|
chain_skipped_response_items: skippedResponseItems,
|
|
1509
1577
|
...(responseOutputMismatch || {}),
|
|
1578
|
+
...(requestInputMismatch || {}),
|
|
1510
1579
|
chain_response_items: Array.isArray(result.responseItems) ? result.responseItems.length : 0,
|
|
1511
1580
|
body_input_items: Array.isArray(requestBody.input) ? requestBody.input.length : null,
|
|
1512
1581
|
frame_input_items: Array.isArray(frame.input) ? frame.input.length : null,
|
|
@@ -1534,10 +1603,29 @@ export async function sendViaWebSocket({
|
|
|
1534
1603
|
const intentionalTransition = typeof sendOpts?.cacheBreakIntent === 'string'
|
|
1535
1604
|
? sendOpts.cacheBreakIntent
|
|
1536
1605
|
: null;
|
|
1606
|
+
traceCacheBreak({
|
|
1607
|
+
sessionId: poolKey,
|
|
1608
|
+
iteration,
|
|
1609
|
+
classification: intentionalTransition ? 'intentional' : 'provider_transition',
|
|
1610
|
+
reason: mode === 'delta' ? deltaReason : (deltaReason || 'full_frame'),
|
|
1611
|
+
source: 'openai_ws_delta',
|
|
1612
|
+
provider: traceProvider,
|
|
1613
|
+
model: liveModel,
|
|
1614
|
+
transport: 'websocket',
|
|
1615
|
+
intentionalTransition,
|
|
1616
|
+
cachedTokens: cacheObservation.cachedTokens,
|
|
1617
|
+
promptTokens: cacheObservation.promptTokens,
|
|
1618
|
+
uncachedTokens: cacheObservation.uncachedTokens,
|
|
1619
|
+
cacheRatio: cacheObservation.cacheRatio,
|
|
1620
|
+
actualCacheMiss: cacheObservation.actualMiss,
|
|
1621
|
+
...(requestInputMismatch || {}),
|
|
1622
|
+
}, { traceFn: null });
|
|
1537
1623
|
_agentTraceFn({
|
|
1538
1624
|
sessionId: poolKey,
|
|
1539
1625
|
iteration,
|
|
1540
1626
|
kind: 'cache_break',
|
|
1627
|
+
classification: intentionalTransition ? 'intentional' : 'provider_transition',
|
|
1628
|
+
source: 'openai_ws_delta',
|
|
1541
1629
|
provider: traceProvider,
|
|
1542
1630
|
model: liveModel,
|
|
1543
1631
|
payload: {
|
|
@@ -1546,6 +1634,8 @@ export async function sendViaWebSocket({
|
|
|
1546
1634
|
transport: 'websocket',
|
|
1547
1635
|
ws_mode: mode,
|
|
1548
1636
|
reason: mode === 'delta' ? deltaReason : (deltaReason || 'full_frame'),
|
|
1637
|
+
classification: intentionalTransition ? 'intentional' : 'provider_transition',
|
|
1638
|
+
source: 'openai_ws_delta',
|
|
1549
1639
|
intentional_transition: intentionalTransition,
|
|
1550
1640
|
request_tool_choice: requestBody.tool_choice ?? null,
|
|
1551
1641
|
cache_key_hash: transportCacheKeyHash,
|
|
@@ -1558,6 +1648,7 @@ export async function sendViaWebSocket({
|
|
|
1558
1648
|
chain_stripped_response_items: strippedResponseItems,
|
|
1559
1649
|
chain_skipped_response_items: skippedResponseItems,
|
|
1560
1650
|
...(responseOutputMismatch || {}),
|
|
1651
|
+
...(requestInputMismatch || {}),
|
|
1561
1652
|
chain_response_items: Array.isArray(result.responseItems) ? result.responseItems.length : 0,
|
|
1562
1653
|
body_input_items: Array.isArray(requestBody.input) ? requestBody.input.length : null,
|
|
1563
1654
|
frame_input_items: Array.isArray(frame.input) ? frame.input.length : null,
|
|
@@ -1586,7 +1677,7 @@ export async function sendViaWebSocket({
|
|
|
1586
1677
|
// Leave a breadcrumb on the result so downstream callers can observe
|
|
1587
1678
|
// that a retry was used (0 = first-try success, up to 2 for ws_1006/1011).
|
|
1588
1679
|
try { Object.defineProperty(out, '__midstreamRetries', { value: attemptIndex, enumerable: false }); } catch {}
|
|
1589
|
-
emitSendSpan('ok');
|
|
1680
|
+
out.transportTiming = emitSendSpan('ok');
|
|
1590
1681
|
return out;
|
|
1591
1682
|
}
|
|
1592
1683
|
// Unreachable — the loop either returns or throws above.
|
|
@@ -17,6 +17,7 @@ import { sanitizeModelList } from './model-list-sanitize.mjs';
|
|
|
17
17
|
import { writeJsonAtomicSync, withFileLock } from '../../../shared/atomic-file.mjs';
|
|
18
18
|
import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
|
|
19
19
|
import { makeModelCache } from './model-cache.mjs';
|
|
20
|
+
import { providerReplayItems } from './lib/provider-replay.mjs';
|
|
20
21
|
|
|
21
22
|
import { sendViaWebSocket } from './openai-oauth-ws.mjs';
|
|
22
23
|
import { _combineUsageWithWarmup } from './openai-ws-events.mjs';
|
|
@@ -175,6 +176,18 @@ export function convertMessagesToResponsesInput(messages, opts = {}) {
|
|
|
175
176
|
continue;
|
|
176
177
|
}
|
|
177
178
|
flushToolMedia();
|
|
179
|
+
const orderedReplay = replayEncryptedReasoning && m.role === 'assistant'
|
|
180
|
+
? providerReplayItems(m, 'openai-responses')
|
|
181
|
+
: undefined;
|
|
182
|
+
if (orderedReplay?.length) {
|
|
183
|
+
for (const item of orderedReplay) {
|
|
184
|
+
if (item?.type === 'custom_tool_call' && item.call_id) {
|
|
185
|
+
customToolCallNameById.set(item.call_id, item.name || '');
|
|
186
|
+
}
|
|
187
|
+
out.push(item);
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
178
191
|
pushReasoningItems(m, 'before');
|
|
179
192
|
if (m.role === 'assistant' && Array.isArray(m.toolCalls) && m.toolCalls.length) {
|
|
180
193
|
// Default path deliberately omits reasoning replay: openai-oauth
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const MAX_TURN_STATE_SCOPES = 4096;
|
|
2
|
+
const _turnStateByScope = new Map();
|
|
3
|
+
|
|
4
|
+
function _clean(value) {
|
|
5
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function _ensureCapacity(scopeKey) {
|
|
9
|
+
if (_turnStateByScope.has(scopeKey) || _turnStateByScope.size < MAX_TURN_STATE_SCOPES) return;
|
|
10
|
+
const oldest = _turnStateByScope.keys().next().value;
|
|
11
|
+
if (oldest !== undefined) _turnStateByScope.delete(oldest);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A turn-state token belongs to one logical turn, not to the physical
|
|
15
|
+
// connection that happened to carry it. Activating a new turn clears the prior
|
|
16
|
+
// value even when the caller reuses the same WebSocket or HTTP session.
|
|
17
|
+
export function activateCodexTurnState(scopeKey, turnId, owner = null) {
|
|
18
|
+
const scope = _clean(scopeKey);
|
|
19
|
+
const turn = _clean(turnId);
|
|
20
|
+
if (!scope || !turn) return null;
|
|
21
|
+
const current = _turnStateByScope.get(scope);
|
|
22
|
+
if (current?.turnId === turn) {
|
|
23
|
+
// One physical WS connection owns the token while it is live. HTTP
|
|
24
|
+
// fallback has no WS owner and may read the same turn-scoped value.
|
|
25
|
+
if (owner && current.owner && current.owner !== owner) return null;
|
|
26
|
+
if (owner && !current.owner) current.owner = owner;
|
|
27
|
+
return current.value || null;
|
|
28
|
+
}
|
|
29
|
+
_ensureCapacity(scope);
|
|
30
|
+
_turnStateByScope.set(scope, { turnId: turn, value: null, owner: owner || null });
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The first server value wins for the whole logical turn.
|
|
35
|
+
export function captureCodexTurnState(scopeKey, turnId, value, owner = null) {
|
|
36
|
+
const scope = _clean(scopeKey);
|
|
37
|
+
const turn = _clean(turnId);
|
|
38
|
+
const token = _clean(value);
|
|
39
|
+
if (!scope || !turn || !token) return null;
|
|
40
|
+
activateCodexTurnState(scope, turn, owner);
|
|
41
|
+
const current = _turnStateByScope.get(scope);
|
|
42
|
+
if (!current || current.turnId !== turn) return null;
|
|
43
|
+
if (owner && current.owner && current.owner !== owner) return null;
|
|
44
|
+
if (owner && !current.owner) current.owner = owner;
|
|
45
|
+
if (current.value) return current.value;
|
|
46
|
+
current.value = token;
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// A replacement connection may adopt the turn token only after the physical
|
|
51
|
+
// connection that owned it has actually closed.
|
|
52
|
+
export function retireCodexTurnStateOwner(scopeKey, owner) {
|
|
53
|
+
const scope = _clean(scopeKey);
|
|
54
|
+
if (!scope || !owner) return;
|
|
55
|
+
const current = _turnStateByScope.get(scope);
|
|
56
|
+
if (current?.owner === owner) current.owner = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function clearCodexTurnStateScope(scopeKey) {
|
|
60
|
+
const scope = _clean(scopeKey);
|
|
61
|
+
if (scope) _turnStateByScope.delete(scope);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function clearAllCodexTurnStates() {
|
|
65
|
+
_turnStateByScope.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const _clearCodexTurnStatesForTest = clearAllCodexTurnStates;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
2
3
|
import { test } from 'node:test';
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -9,6 +10,12 @@ import {
|
|
|
9
10
|
import { _applyReasoningReplayPolicy } from './openai-oauth-ws.mjs';
|
|
10
11
|
import { _captureTurnStateFromEvent } from './openai-ws-stream.mjs';
|
|
11
12
|
import { _convertMessagesToResponsesInputForTest } from './openai-responses-payload.mjs';
|
|
13
|
+
import {
|
|
14
|
+
acquireWebSocket,
|
|
15
|
+
releaseWebSocket,
|
|
16
|
+
_clearWebSocketPoolForTest,
|
|
17
|
+
_setOpenSocketForTest,
|
|
18
|
+
} from './openai-ws-pool.mjs';
|
|
12
19
|
|
|
13
20
|
// A turn may reason, speak, then reason again before calling a tool. Replaying
|
|
14
21
|
// every retained item in front of the text invents an order the response never
|
|
@@ -41,19 +48,18 @@ test('a turn that reasons, speaks, then reasons again replays in that order', ()
|
|
|
41
48
|
assert.equal(input[3].afterText, undefined);
|
|
42
49
|
});
|
|
43
50
|
|
|
44
|
-
// A
|
|
45
|
-
// entry holds it write-once, whichever path first supplied it.
|
|
51
|
+
// A logical turn's sticky-routing token is first-value-wins.
|
|
46
52
|
test('a live turn-state token is never replaced by a later event token', () => {
|
|
47
53
|
const entry = {};
|
|
48
54
|
_captureTurnStateFromEvent(entry, {
|
|
49
|
-
type: 'response.
|
|
50
|
-
headers: { 'x-codex-turn-state': '
|
|
55
|
+
type: 'response.metadata',
|
|
56
|
+
headers: { 'x-codex-turn-state': 'first-token' },
|
|
51
57
|
});
|
|
52
58
|
_captureTurnStateFromEvent(entry, {
|
|
53
|
-
type: 'response.
|
|
59
|
+
type: 'response.metadata',
|
|
54
60
|
headers: { 'x-codex-turn-state': 'later-token' },
|
|
55
61
|
});
|
|
56
|
-
assert.equal(entry.turnState, '
|
|
62
|
+
assert.equal(entry.turnState, 'first-token');
|
|
57
63
|
});
|
|
58
64
|
|
|
59
65
|
// The logical request history always carries retained reasoning — a pooled
|
|
@@ -136,6 +142,48 @@ test('Codex delta parity falls back when a previous response item is absent', (t
|
|
|
136
142
|
assert.deepEqual(delta.frame.input, body.input);
|
|
137
143
|
});
|
|
138
144
|
|
|
145
|
+
test('input-prefix fallback reports only the first changed item type and hashes', (t) => {
|
|
146
|
+
const priorTransport = process.env.MIXDOG_OAI_TRANSPORT;
|
|
147
|
+
process.env.MIXDOG_OAI_TRANSPORT = 'ws-delta';
|
|
148
|
+
t.after(() => {
|
|
149
|
+
if (priorTransport == null) delete process.env.MIXDOG_OAI_TRANSPORT;
|
|
150
|
+
else process.env.MIXDOG_OAI_TRANSPORT = priorTransport;
|
|
151
|
+
});
|
|
152
|
+
const previousInput = [{
|
|
153
|
+
type: 'message',
|
|
154
|
+
role: 'user',
|
|
155
|
+
content: [{ type: 'input_text', text: 'before secret' }],
|
|
156
|
+
}];
|
|
157
|
+
const body = {
|
|
158
|
+
model: 'gpt-5.6-sol',
|
|
159
|
+
input: [{
|
|
160
|
+
type: 'message',
|
|
161
|
+
role: 'user',
|
|
162
|
+
content: [{ type: 'input_text', text: 'after secret' }],
|
|
163
|
+
}],
|
|
164
|
+
};
|
|
165
|
+
const entry = {
|
|
166
|
+
lastRequestSansInput: _stableStringify(_sansInput({
|
|
167
|
+
model: body.model,
|
|
168
|
+
input: previousInput,
|
|
169
|
+
})),
|
|
170
|
+
lastRequestInput: previousInput,
|
|
171
|
+
lastResponseId: 'resp-1',
|
|
172
|
+
lastResponseItems: [],
|
|
173
|
+
turnState: 'sticky',
|
|
174
|
+
};
|
|
175
|
+
const delta = _computeDelta({ entry, body, traceProvider: 'openai-oauth' });
|
|
176
|
+
assert.equal(delta.reason, 'input_prefix_mismatch');
|
|
177
|
+
assert.equal(delta.requestInputMismatch.inputPrefixMismatchIndex, 0);
|
|
178
|
+
assert.equal(delta.requestInputMismatch.inputPrefixMismatchExpectedType, 'message');
|
|
179
|
+
assert.equal(delta.requestInputMismatch.inputPrefixMismatchActualType, 'message');
|
|
180
|
+
assert.notEqual(
|
|
181
|
+
delta.requestInputMismatch.inputPrefixMismatchExpectedHash,
|
|
182
|
+
delta.requestInputMismatch.inputPrefixMismatchActualHash,
|
|
183
|
+
);
|
|
184
|
+
assert.doesNotMatch(JSON.stringify(delta.requestInputMismatch), /before secret|after secret/);
|
|
185
|
+
});
|
|
186
|
+
|
|
139
187
|
// Retained reasoning rides the logical history, and the delta builder is what
|
|
140
188
|
// keeps it off the wire: it belongs to the anchored previous response, so the
|
|
141
189
|
// frame carries only what is genuinely new. This is the whole reason the
|
|
@@ -188,3 +236,71 @@ test('the delta frame drops the anchored reasoning item from the wire tail', (t)
|
|
|
188
236
|
assert.deepEqual(delta.frame.input, [toolOutput]);
|
|
189
237
|
assert.equal(delta.strippedResponseItems, 2);
|
|
190
238
|
});
|
|
239
|
+
|
|
240
|
+
test('one pool key serializes live owners and reconnects only after close', async (t) => {
|
|
241
|
+
class FakeSocket extends EventEmitter {
|
|
242
|
+
constructor() {
|
|
243
|
+
super();
|
|
244
|
+
this.readyState = 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
close() {
|
|
248
|
+
if (this.readyState === 3) return;
|
|
249
|
+
this.readyState = 3;
|
|
250
|
+
this.emit('close');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
terminate() {
|
|
254
|
+
this.close();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
ref() {}
|
|
258
|
+
unref() {}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const handshakeArgs = [];
|
|
262
|
+
_clearWebSocketPoolForTest();
|
|
263
|
+
_setOpenSocketForTest(async (args) => {
|
|
264
|
+
handshakeArgs.push(args);
|
|
265
|
+
return { socket: new FakeSocket(), turnState: 'upgrade-token-must-be-ignored' };
|
|
266
|
+
});
|
|
267
|
+
t.after(() => {
|
|
268
|
+
_clearWebSocketPoolForTest();
|
|
269
|
+
_setOpenSocketForTest();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const args = {
|
|
273
|
+
auth: { type: 'openai-oauth', account_id: 'race-test' },
|
|
274
|
+
poolKey: 'race-pool',
|
|
275
|
+
cacheKey: 'race-cache',
|
|
276
|
+
codexHeaders: {},
|
|
277
|
+
};
|
|
278
|
+
const first = await acquireWebSocket(args);
|
|
279
|
+
assert.equal(first.entry.turnState, null);
|
|
280
|
+
let secondResolved = false;
|
|
281
|
+
const secondPromise = acquireWebSocket(args).then((value) => {
|
|
282
|
+
secondResolved = true;
|
|
283
|
+
return value;
|
|
284
|
+
});
|
|
285
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
286
|
+
assert.equal(secondResolved, false);
|
|
287
|
+
assert.equal(handshakeArgs.length, 1);
|
|
288
|
+
|
|
289
|
+
releaseWebSocket({ entry: first.entry, poolKey: args.poolKey, keep: true });
|
|
290
|
+
const second = await secondPromise;
|
|
291
|
+
assert.equal(second.entry, first.entry);
|
|
292
|
+
assert.equal(second.reused, true);
|
|
293
|
+
assert.ok(second.ownerWaitMs >= 0);
|
|
294
|
+
assert.equal(handshakeArgs.length, 1);
|
|
295
|
+
|
|
296
|
+
releaseWebSocket({ entry: second.entry, poolKey: args.poolKey, keep: false });
|
|
297
|
+
const replacement = await acquireWebSocket(args);
|
|
298
|
+
assert.notEqual(replacement.entry, first.entry);
|
|
299
|
+
assert.equal(handshakeArgs.length, 2);
|
|
300
|
+
releaseWebSocket({ entry: replacement.entry, poolKey: args.poolKey, keep: false });
|
|
301
|
+
|
|
302
|
+
assert.deepEqual(
|
|
303
|
+
handshakeArgs.map((call) => call.turnState),
|
|
304
|
+
[undefined, undefined],
|
|
305
|
+
);
|
|
306
|
+
});
|
|
@@ -196,6 +196,31 @@ function _normalizedResponseItemDiagnosticHash(item) {
|
|
|
196
196
|
.digest('hex');
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
export function _requestInputMismatchDiagnostics(currentInput, previousInput) {
|
|
200
|
+
const current = Array.isArray(currentInput) ? currentInput : [];
|
|
201
|
+
const previous = Array.isArray(previousInput) ? previousInput : [];
|
|
202
|
+
let index = Math.min(current.length, previous.length);
|
|
203
|
+
const limit = Math.min(current.length, previous.length);
|
|
204
|
+
for (let cursor = 0; cursor < limit; cursor += 1) {
|
|
205
|
+
if (_requestInputItemsMatch(current[cursor], previous[cursor])) continue;
|
|
206
|
+
index = cursor;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
const expected = previous[index];
|
|
210
|
+
const actual = current[index];
|
|
211
|
+
return {
|
|
212
|
+
inputPrefixMismatchIndex: index,
|
|
213
|
+
inputPrefixMismatchPreviousCount: previous.length,
|
|
214
|
+
inputPrefixMismatchCurrentCount: current.length,
|
|
215
|
+
inputPrefixMismatchExpectedType: expected?.type
|
|
216
|
+
|| (expected?.role === 'assistant' ? 'message' : (expected ? 'unknown' : 'missing')),
|
|
217
|
+
inputPrefixMismatchExpectedHash: _normalizedResponseItemDiagnosticHash(expected),
|
|
218
|
+
inputPrefixMismatchActualType: actual?.type
|
|
219
|
+
|| (actual?.role === 'assistant' ? 'message' : (actual ? 'unknown' : 'missing')),
|
|
220
|
+
inputPrefixMismatchActualHash: _normalizedResponseItemDiagnosticHash(actual),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
199
224
|
function _responseOutputMismatchDiagnostics(inputItem, responseItem, replayItemCount, responseItemCount) {
|
|
200
225
|
return {
|
|
201
226
|
response_output_mismatch_expected_type: responseItem?.type || 'unknown',
|
|
@@ -312,16 +337,11 @@ export function _buildResponseCreateFrame(body, { previousResponseId = null, inp
|
|
|
312
337
|
|
|
313
338
|
export function _computeDelta({ entry, body, traceProvider }) {
|
|
314
339
|
// DEFAULT: full-frame sends. codex's delta path is only cache-safe with
|
|
315
|
-
// the x-codex-turn-state sticky-routing token.
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
// arrives on the handshake 101 response instead (2026-08-21 bench: 81 of 88
|
|
321
|
-
// calls carried it from iteration 1, before any response event existed), so
|
|
322
|
-
// the pool captures it at upgrade and the frame metadata replays it.
|
|
323
|
-
// Without the sticky token, previous_response_id requests hop cache nodes
|
|
324
|
-
// and only the first prefix blocks hit.
|
|
340
|
+
// the x-codex-turn-state sticky-routing token. The token is request-scoped:
|
|
341
|
+
// response metadata establishes it, later response.create frames replay it
|
|
342
|
+
// for the same logical turn, and a new turn starts empty even when it reuses
|
|
343
|
+
// the physical connection. Handshake state is deliberately ignored because
|
|
344
|
+
// a connection can outlive the turn that created it.
|
|
325
345
|
// Delta gating flows through the single transport-policy switch
|
|
326
346
|
// (MIXDOG_OAI_TRANSPORT: ws-full | ws-delta | http-sse). Default ws-delta
|
|
327
347
|
// selects the refs-compatible safe delta; 'ws-full'/'http-sse' force full
|
|
@@ -379,7 +399,12 @@ export function _computeDelta({ entry, body, traceProvider }) {
|
|
|
379
399
|
const curInput = Array.isArray(body.input) ? body.input : [];
|
|
380
400
|
const afterPreviousInput = _stripRequestPrefix(curInput, entry.lastRequestInput);
|
|
381
401
|
if (!afterPreviousInput) {
|
|
382
|
-
return {
|
|
402
|
+
return {
|
|
403
|
+
mode: 'full',
|
|
404
|
+
reason: 'input_prefix_mismatch',
|
|
405
|
+
requestInputMismatch: _requestInputMismatchDiagnostics(curInput, entry.lastRequestInput),
|
|
406
|
+
frame: buildFrame(body),
|
|
407
|
+
};
|
|
383
408
|
}
|
|
384
409
|
const stripped = _stripResponseItemsFromHead(afterPreviousInput, entry.lastResponseItems);
|
|
385
410
|
if (!stripped.ok) {
|