visionclaw 0.1.193 → 0.1.194-dev.feat-backup-progress-reporting.1
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/dist/agent/backup-uploader.d.ts +44 -0
- package/dist/agent/backup-uploader.d.ts.map +1 -0
- package/dist/agent/backup-uploader.js +83 -0
- package/dist/agent/backup-uploader.js.map +1 -0
- package/dist/agent/backup.d.ts +7 -1
- package/dist/agent/backup.d.ts.map +1 -1
- package/dist/agent/backup.js +337 -52
- package/dist/agent/backup.js.map +1 -1
- package/dist/agent/command-handlers.d.ts +2 -0
- package/dist/agent/command-handlers.d.ts.map +1 -1
- package/dist/agent/command-handlers.js +29 -3
- package/dist/agent/command-handlers.js.map +1 -1
- package/dist/agent/context.d.ts +2 -0
- package/dist/agent/context.d.ts.map +1 -1
- package/dist/agent/context.js +11 -1
- package/dist/agent/context.js.map +1 -1
- package/dist/agent/data-collector.d.ts.map +1 -1
- package/dist/agent/data-collector.js +23 -38
- package/dist/agent/data-collector.js.map +1 -1
- package/dist/agent/loop.d.ts +3 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +48 -0
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/message-format.d.ts.map +1 -1
- package/dist/agent/message-format.js +3 -0
- package/dist/agent/message-format.js.map +1 -1
- package/dist/agent/status.d.ts.map +1 -1
- package/dist/agent/status.js +2 -1
- package/dist/agent/status.js.map +1 -1
- package/dist/backup.d.ts +4 -1
- package/dist/backup.d.ts.map +1 -1
- package/dist/backup.js +86 -3
- package/dist/backup.js.map +1 -1
- package/dist/calendar/google-calendar.d.ts +2 -0
- package/dist/calendar/google-calendar.d.ts.map +1 -1
- package/dist/calendar/google-calendar.js +3 -0
- package/dist/calendar/google-calendar.js.map +1 -1
- package/dist/channels/interface.d.ts +9 -0
- package/dist/channels/interface.d.ts.map +1 -1
- package/dist/channels/manager.d.ts +7 -1
- package/dist/channels/manager.d.ts.map +1 -1
- package/dist/channels/manager.js +12 -0
- package/dist/channels/manager.js.map +1 -1
- package/dist/channels/telegram.d.ts.map +1 -1
- package/dist/channels/telegram.js +5 -0
- package/dist/channels/telegram.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +42 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/config/types.d.ts +9 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -1
- package/dist/drive/google-drive.d.ts +1 -0
- package/dist/drive/google-drive.d.ts.map +1 -1
- package/dist/drive/google-drive.js +5 -0
- package/dist/drive/google-drive.js.map +1 -1
- package/dist/i18n/messages.d.ts +4 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/messages.js +7 -2
- package/dist/i18n/messages.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/obs/server.d.ts +7 -0
- package/dist/obs/server.d.ts.map +1 -1
- package/dist/obs/server.js +22 -0
- package/dist/obs/server.js.map +1 -1
- package/dist/onboarding/bot-profile.d.ts.map +1 -1
- package/dist/onboarding/bot-profile.js +1 -0
- package/dist/onboarding/bot-profile.js.map +1 -1
- package/dist/onboarding/index.d.ts.map +1 -1
- package/dist/onboarding/index.js +5 -0
- package/dist/onboarding/index.js.map +1 -1
- package/dist/onboarding/prepare-mac.d.ts +1 -0
- package/dist/onboarding/prepare-mac.d.ts.map +1 -1
- package/dist/onboarding/prepare-mac.js +32 -15
- package/dist/onboarding/prepare-mac.js.map +1 -1
- package/dist/onboarding/setup-shared.d.ts.map +1 -1
- package/dist/onboarding/setup-shared.js +6 -0
- package/dist/onboarding/setup-shared.js.map +1 -1
- package/dist/onboarding/setup-steps.d.ts.map +1 -1
- package/dist/onboarding/setup-steps.js +8 -0
- package/dist/onboarding/setup-steps.js.map +1 -1
- package/dist/realtime/agent-bridge.d.ts +7 -0
- package/dist/realtime/agent-bridge.d.ts.map +1 -0
- package/dist/realtime/agent-bridge.js +31 -0
- package/dist/realtime/agent-bridge.js.map +1 -0
- package/dist/realtime/assets/index.html +1058 -0
- package/dist/realtime/assets/samples/alloy.mp3 +0 -0
- package/dist/realtime/assets/samples/ash.mp3 +0 -0
- package/dist/realtime/assets/samples/ballad.mp3 +0 -0
- package/dist/realtime/assets/samples/cedar.mp3 +0 -0
- package/dist/realtime/assets/samples/coral.mp3 +0 -0
- package/dist/realtime/assets/samples/echo.mp3 +0 -0
- package/dist/realtime/assets/samples/marin.mp3 +0 -0
- package/dist/realtime/assets/samples/sage.mp3 +0 -0
- package/dist/realtime/assets/samples/shimmer.mp3 +0 -0
- package/dist/realtime/assets/samples/verse.mp3 +0 -0
- package/dist/realtime/context.d.ts +14 -0
- package/dist/realtime/context.d.ts.map +1 -0
- package/dist/realtime/context.js +153 -0
- package/dist/realtime/context.js.map +1 -0
- package/dist/realtime/http-helpers.d.ts +5 -0
- package/dist/realtime/http-helpers.d.ts.map +1 -0
- package/dist/realtime/http-helpers.js +29 -0
- package/dist/realtime/http-helpers.js.map +1 -0
- package/dist/realtime/index.d.ts +62 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +94 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/server.d.ts +6 -0
- package/dist/realtime/server.d.ts.map +1 -0
- package/dist/realtime/server.js +476 -0
- package/dist/realtime/server.js.map +1 -0
- package/dist/realtime/telegram-auth.d.ts +2 -0
- package/dist/realtime/telegram-auth.d.ts.map +1 -0
- package/dist/realtime/telegram-auth.js +24 -0
- package/dist/realtime/telegram-auth.js.map +1 -0
- package/dist/realtime/tools.d.ts +829 -0
- package/dist/realtime/tools.d.ts.map +1 -0
- package/dist/realtime/tools.js +630 -0
- package/dist/realtime/tools.js.map +1 -0
- package/dist/realtime/types.d.ts +62 -0
- package/dist/realtime/types.d.ts.map +1 -0
- package/dist/realtime/types.js +3 -0
- package/dist/realtime/types.js.map +1 -0
- package/dist/realtime/voice-summarizer.d.ts +4 -0
- package/dist/realtime/voice-summarizer.d.ts.map +1 -0
- package/dist/realtime/voice-summarizer.js +129 -0
- package/dist/realtime/voice-summarizer.js.map +1 -0
- package/dist/restore.d.ts +5 -0
- package/dist/restore.d.ts.map +1 -1
- package/dist/restore.js +132 -16
- package/dist/restore.js.map +1 -1
- package/dist/tools/email.d.ts +1 -1
- package/dist/tools/memory.d.ts +13 -0
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +43 -43
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/stock-data.d.ts +16 -0
- package/dist/tools/stock-data.d.ts.map +1 -1
- package/dist/tools/stock-data.js +36 -38
- package/dist/tools/stock-data.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +4 -0
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +95 -23
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tos-storage.d.ts +45 -0
- package/dist/tos-storage.d.ts.map +1 -0
- package/dist/tos-storage.js +134 -0
- package/dist/tos-storage.js.map +1 -0
- package/dist-agent/bundle.cjs +170934 -167547
- package/package.json +3 -2
- package/dist/agent/applied-credential-signature.d.ts +0 -53
- package/dist/agent/applied-credential-signature.d.ts.map +0 -1
- package/dist/agent/applied-credential-signature.js +0 -137
- package/dist/agent/applied-credential-signature.js.map +0 -1
- package/dist/agent/engines/claude/cli-resolver.d.ts +0 -16
- package/dist/agent/engines/claude/cli-resolver.d.ts.map +0 -1
- package/dist/agent/engines/claude/cli-resolver.js +0 -83
- package/dist/agent/engines/claude/cli-resolver.js.map +0 -1
- package/dist/agent/engines/claude/session-browser-policy.d.ts +0 -9
- package/dist/agent/engines/claude/session-browser-policy.d.ts.map +0 -1
- package/dist/agent/engines/claude/session-browser-policy.js +0 -49
- package/dist/agent/engines/claude/session-browser-policy.js.map +0 -1
- package/dist/agent/engines/claude/session.d.ts +0 -291
- package/dist/agent/engines/claude/session.d.ts.map +0 -1
- package/dist/agent/engines/claude/session.js +0 -1177
- package/dist/agent/engines/claude/session.js.map +0 -1
- package/dist/agent/engines/client-factory.d.ts +0 -63
- package/dist/agent/engines/client-factory.d.ts.map +0 -1
- package/dist/agent/engines/client-factory.js +0 -382
- package/dist/agent/engines/client-factory.js.map +0 -1
- package/dist/agent/engines/engine.d.ts +0 -8
- package/dist/agent/engines/engine.d.ts.map +0 -1
- package/dist/agent/engines/engine.js +0 -15
- package/dist/agent/engines/engine.js.map +0 -1
- package/dist/agent/engines/openai/file-session.d.ts +0 -49
- package/dist/agent/engines/openai/file-session.d.ts.map +0 -1
- package/dist/agent/engines/openai/file-session.js +0 -108
- package/dist/agent/engines/openai/file-session.js.map +0 -1
- package/dist/agent/engines/openai/file-tools.d.ts +0 -35
- package/dist/agent/engines/openai/file-tools.d.ts.map +0 -1
- package/dist/agent/engines/openai/file-tools.js +0 -194
- package/dist/agent/engines/openai/file-tools.js.map +0 -1
- package/dist/agent/engines/openai/session.d.ts +0 -190
- package/dist/agent/engines/openai/session.d.ts.map +0 -1
- package/dist/agent/engines/openai/session.js +0 -1066
- package/dist/agent/engines/openai/session.js.map +0 -1
- package/dist/agent/engines/openai/tools.d.ts +0 -13
- package/dist/agent/engines/openai/tools.d.ts.map +0 -1
- package/dist/agent/engines/openai/tools.js +0 -248
- package/dist/agent/engines/openai/tools.js.map +0 -1
- package/dist/agent/engines/session-types.d.ts +0 -146
- package/dist/agent/engines/session-types.d.ts.map +0 -1
- package/dist/agent/engines/session-types.js +0 -2
- package/dist/agent/engines/session-types.js.map +0 -1
- package/dist/agent/engines/system-prompt-log.d.ts +0 -9
- package/dist/agent/engines/system-prompt-log.d.ts.map +0 -1
- package/dist/agent/engines/system-prompt-log.js +0 -46
- package/dist/agent/engines/system-prompt-log.js.map +0 -1
- package/dist/agent/transcript/transcript-backfill.d.ts +0 -54
- package/dist/agent/transcript/transcript-backfill.d.ts.map +0 -1
- package/dist/agent/transcript/transcript-backfill.js +0 -604
- package/dist/agent/transcript/transcript-backfill.js.map +0 -1
- package/dist/agent/transcript/transcript-indexer.d.ts +0 -273
- package/dist/agent/transcript/transcript-indexer.d.ts.map +0 -1
- package/dist/agent/transcript/transcript-indexer.js +0 -1217
- package/dist/agent/transcript/transcript-indexer.js.map +0 -1
- package/dist/agent/transcript/transcript-memory-migrations.d.ts +0 -25
- package/dist/agent/transcript/transcript-memory-migrations.d.ts.map +0 -1
- package/dist/agent/transcript/transcript-memory-migrations.js +0 -87
- package/dist/agent/transcript/transcript-memory-migrations.js.map +0 -1
- package/dist/agent/transcript-memory-migrations.d.ts +0 -25
- package/dist/agent/transcript-memory-migrations.d.ts.map +0 -1
- package/dist/agent/transcript-memory-migrations.js +0 -87
- package/dist/agent/transcript-memory-migrations.js.map +0 -1
- package/dist/agent/tunnel-credential-handler.d.ts +0 -90
- package/dist/agent/tunnel-credential-handler.d.ts.map +0 -1
- package/dist/agent/tunnel-credential-handler.js +0 -162
- package/dist/agent/tunnel-credential-handler.js.map +0 -1
- package/dist/agent/usage/usage-backfill-handler.d.ts +0 -18
- package/dist/agent/usage/usage-backfill-handler.d.ts.map +0 -1
- package/dist/agent/usage/usage-backfill-handler.js +0 -69
- package/dist/agent/usage/usage-backfill-handler.js.map +0 -1
- package/dist/agent/usage/usage-gate.d.ts +0 -25
- package/dist/agent/usage/usage-gate.d.ts.map +0 -1
- package/dist/agent/usage/usage-gate.js +0 -83
- package/dist/agent/usage/usage-gate.js.map +0 -1
- package/dist/agent/usage/usage-handler.d.ts +0 -7
- package/dist/agent/usage/usage-handler.d.ts.map +0 -1
- package/dist/agent/usage/usage-handler.js +0 -28
- package/dist/agent/usage/usage-handler.js.map +0 -1
- package/dist/agent/usage/usage-report-builder.d.ts +0 -26
- package/dist/agent/usage/usage-report-builder.d.ts.map +0 -1
- package/dist/agent/usage/usage-report-builder.js +0 -80
- package/dist/agent/usage/usage-report-builder.js.map +0 -1
- package/dist/agent/usage/usage-report-queue.d.ts +0 -26
- package/dist/agent/usage/usage-report-queue.d.ts.map +0 -1
- package/dist/agent/usage/usage-report-queue.js +0 -199
- package/dist/agent/usage/usage-report-queue.js.map +0 -1
- package/dist/agent/usage/usage-report-types.d.ts +0 -41
- package/dist/agent/usage/usage-report-types.d.ts.map +0 -1
- package/dist/agent/usage/usage-report-types.js +0 -2
- package/dist/agent/usage/usage-report-types.js.map +0 -1
- package/dist/agent/usage/usage-reporter.d.ts +0 -31
- package/dist/agent/usage/usage-reporter.d.ts.map +0 -1
- package/dist/agent/usage/usage-reporter.js +0 -102
- package/dist/agent/usage/usage-reporter.js.map +0 -1
- package/dist/agent/usage-backfill-handler.d.ts +0 -18
- package/dist/agent/usage-backfill-handler.d.ts.map +0 -1
- package/dist/agent/usage-backfill-handler.js +0 -69
- package/dist/agent/usage-backfill-handler.js.map +0 -1
- package/dist/agent/usage-gate.d.ts +0 -25
- package/dist/agent/usage-gate.d.ts.map +0 -1
- package/dist/agent/usage-gate.js +0 -83
- package/dist/agent/usage-gate.js.map +0 -1
- package/dist/agent/usage-report-builder.d.ts +0 -26
- package/dist/agent/usage-report-builder.d.ts.map +0 -1
- package/dist/agent/usage-report-builder.js +0 -80
- package/dist/agent/usage-report-builder.js.map +0 -1
- package/dist/agent/usage-report-queue.d.ts +0 -26
- package/dist/agent/usage-report-queue.d.ts.map +0 -1
- package/dist/agent/usage-report-queue.js +0 -199
- package/dist/agent/usage-report-queue.js.map +0 -1
- package/dist/agent/usage-report-types.d.ts +0 -41
- package/dist/agent/usage-report-types.d.ts.map +0 -1
- package/dist/agent/usage-report-types.js +0 -2
- package/dist/agent/usage-report-types.js.map +0 -1
- package/dist/agent/usage-reporter.d.ts +0 -31
- package/dist/agent/usage-reporter.d.ts.map +0 -1
- package/dist/agent/usage-reporter.js +0 -102
- package/dist/agent/usage-reporter.js.map +0 -1
- package/dist/agent/wake-cycle-tool-tracker.d.ts +0 -39
- package/dist/agent/wake-cycle-tool-tracker.d.ts.map +0 -1
- package/dist/agent/wake-cycle-tool-tracker.js +0 -72
- package/dist/agent/wake-cycle-tool-tracker.js.map +0 -1
- package/dist/billing/payg-handler.d.ts +0 -29
- package/dist/billing/payg-handler.d.ts.map +0 -1
- package/dist/billing/payg-handler.js +0 -92
- package/dist/billing/payg-handler.js.map +0 -1
- package/dist/billing/payment-handler.d.ts +0 -24
- package/dist/billing/payment-handler.d.ts.map +0 -1
- package/dist/billing/payment-handler.js +0 -101
- package/dist/billing/payment-handler.js.map +0 -1
- package/dist/builtin-skills/catalog/phone-adb-automation/SKILL.md +0 -412
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_input.sh +0 -132
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_launch.sh +0 -166
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_screenshot.sh +0 -87
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_security_kbd.py +0 -174
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_setup.sh +0 -274
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_swipe.sh +0 -111
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_tap.sh +0 -87
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_ui_parse.py +0 -176
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_wake_unlock.sh +0 -67
- package/dist/builtin-skills/transcribe-audio/SKILL.md +0 -122
- package/dist/data-processing/convert-demo-cli.d.ts +0 -7
- package/dist/data-processing/convert-demo-cli.d.ts.map +0 -1
- package/dist/data-processing/convert-demo-cli.js +0 -30
- package/dist/data-processing/convert-demo-cli.js.map +0 -1
- package/dist/data-processing/convert-demo.d.ts +0 -26
- package/dist/data-processing/convert-demo.d.ts.map +0 -1
- package/dist/data-processing/convert-demo.js +0 -233
- package/dist/data-processing/convert-demo.js.map +0 -1
- package/dist/obs/rdp/icons/icons/app_windows.svg +0 -4
- package/dist/obs/rdp/icons/icons/clip_get.svg +0 -4
- package/dist/obs/rdp/icons/icons/clip_send.svg +0 -4
- package/dist/obs/rdp/icons/icons/clip_shared.svg +0 -4
- package/dist/obs/rdp/icons/icons/clipboard.svg +0 -4
- package/dist/obs/rdp/icons/icons/clipboard_shared.svg +0 -4
- package/dist/obs/rdp/icons/icons/control.svg +0 -4
- package/dist/obs/rdp/icons/icons/desktop.svg +0 -4
- package/dist/obs/rdp/icons/icons/display.svg +0 -4
- package/dist/obs/rdp/icons/icons/launchpad.svg +0 -4
- package/dist/obs/rdp/icons/icons/mission_control.svg +0 -4
- package/dist/obs/rdp/icons/icons/screenshot.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_actual.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_fit.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_in.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_out.svg +0 -4
- package/dist/obs/tunnel-telemetry.d.ts +0 -46
- package/dist/obs/tunnel-telemetry.d.ts.map +0 -1
- package/dist/obs/tunnel-telemetry.js +0 -70
- package/dist/obs/tunnel-telemetry.js.map +0 -1
- package/dist/onboarding/cloudflared-cert.d.ts +0 -15
- package/dist/onboarding/cloudflared-cert.d.ts.map +0 -1
- package/dist/onboarding/cloudflared-cert.js +0 -57
- package/dist/onboarding/cloudflared-cert.js.map +0 -1
- package/dist/onboarding/playwriter-extension.d.ts +0 -19
- package/dist/onboarding/playwriter-extension.d.ts.map +0 -1
- package/dist/onboarding/playwriter-extension.js +0 -246
- package/dist/onboarding/playwriter-extension.js.map +0 -1
- package/dist/service/gbox-tun.d.ts +0 -14
- package/dist/service/gbox-tun.d.ts.map +0 -1
- package/dist/service/gbox-tun.js +0 -315
- package/dist/service/gbox-tun.js.map +0 -1
- package/dist/skills/installed.d.ts +0 -11
- package/dist/skills/installed.d.ts.map +0 -1
- package/dist/skills/installed.js +0 -35
- package/dist/skills/installed.js.map +0 -1
- package/dist/tools/coordinate-resolver.d.ts +0 -30
- package/dist/tools/coordinate-resolver.d.ts.map +0 -1
- package/dist/tools/coordinate-resolver.js +0 -104
- package/dist/tools/coordinate-resolver.js.map +0 -1
- package/dist/utils/playwriter-relay.d.ts +0 -9
- package/dist/utils/playwriter-relay.d.ts.map +0 -1
- package/dist/utils/playwriter-relay.js +0 -77
- package/dist/utils/playwriter-relay.js.map +0 -1
- package/dist/utils/wechat-monitor.d.ts +0 -21
- package/dist/utils/wechat-monitor.d.ts.map +0 -1
- package/dist/utils/wechat-monitor.js +0 -88
- package/dist/utils/wechat-monitor.js.map +0 -1
|
@@ -1,1066 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { Agent, OpenAIResponsesCompactionSession, Runner, setTracingDisabled } from "@openai/agents";
|
|
4
|
-
import { getOpenAISessionsDir, loadSessionId, loadUsageSnapshot, saveSessionId, saveUsageSnapshot, } from "../../../config/index.js";
|
|
5
|
-
import { logger } from "../../../logger.js";
|
|
6
|
-
import { configureOpenAIProvider, createOpenAIClient, getModelId } from "../client-factory.js";
|
|
7
|
-
import { logSystemPrompt } from "../system-prompt-log.js";
|
|
8
|
-
import { OpenAIFileSession } from "./file-session.js";
|
|
9
|
-
import { createOpenAIMcpServers, createOpenAITools } from "./tools.js";
|
|
10
|
-
const MAX_TURNS = 100;
|
|
11
|
-
// GPT-5.x family currently advertises a 1M-token context window.
|
|
12
|
-
// Used to compute usage `usedPct` for the OpenAI engine; keeping this
|
|
13
|
-
// in one place so usage rendering and compaction gates agree.
|
|
14
|
-
const OPENAI_CONTEXT_WINDOW = 1_000_000;
|
|
15
|
-
// Used-token threshold (fraction of context window) at which
|
|
16
|
-
// {@link OpenAIAgentSession.maybeCompact} triggers a compaction. Calibrated
|
|
17
|
-
// for the 1M-token window: 70% leaves comfortable headroom for the next
|
|
18
|
-
// turn's input + tool results without compacting too eagerly. Configurable
|
|
19
|
-
// per call via the `usedPctThreshold` option to `maybeCompact()`.
|
|
20
|
-
const OPENAI_COMPACT_USED_PCT_THRESHOLD = 0.7;
|
|
21
|
-
export class OpenAIAgentSession {
|
|
22
|
-
config;
|
|
23
|
-
buildSystemPrompt;
|
|
24
|
-
runtimeSurface;
|
|
25
|
-
getDualSessionEnabled;
|
|
26
|
-
getAndroidUseEnabled;
|
|
27
|
-
dynamicMcpServers;
|
|
28
|
-
client;
|
|
29
|
-
runner;
|
|
30
|
-
// The session id is invariant across compactions, so the underlying
|
|
31
|
-
// file-backed session and its compaction wrapper are constructed once
|
|
32
|
-
// in the constructor and never replaced.
|
|
33
|
-
underlyingSession;
|
|
34
|
-
session;
|
|
35
|
-
abortController = null;
|
|
36
|
-
inputClosed = true;
|
|
37
|
-
stopRequested = false;
|
|
38
|
-
lastUsageSnapshot = null;
|
|
39
|
-
currentRun = null;
|
|
40
|
-
sessionId;
|
|
41
|
-
// Compaction state — mirrors Claude's session. `compactInFlight` blocks
|
|
42
|
-
// re-entrancy; `lastCompactRequestAtMs` enforces a cooldown so we don't
|
|
43
|
-
// hammer compaction on every heartbeat once usage crosses the threshold.
|
|
44
|
-
compactInFlight = false;
|
|
45
|
-
lastCompactRequestAtMs = 0;
|
|
46
|
-
// OpenAI engine mid-run injection state.
|
|
47
|
-
//
|
|
48
|
-
// The OpenAI Agents SDK does not expose a mid-run input channel like the
|
|
49
|
-
// Claude SDK does, so we approximate it: when a message is injected while
|
|
50
|
-
// a run is active we *abort* the run at the next safe SDK boundary (a
|
|
51
|
-
// `tool_output` event — the tool call+result pair has already been
|
|
52
|
-
// persisted to the session by then) and immediately start a follow-up run
|
|
53
|
-
// that feeds the queued items as the next turn's input. The same
|
|
54
|
-
// `session` is reused so the model continues from the existing transcript
|
|
55
|
-
// without paying any cold-start tax.
|
|
56
|
-
//
|
|
57
|
-
// - `pendingInjections`: items to feed into the next run.
|
|
58
|
-
// - `interruptArmed`: set by `injectMessage` while a run is live; the
|
|
59
|
-
// stream loop checks this after each event and triggers the abort.
|
|
60
|
-
// - `resumeRequested`: tells the resume loop to loop one more time after
|
|
61
|
-
// the current run terminates (either via our abort or naturally).
|
|
62
|
-
pendingInjections = [];
|
|
63
|
-
interruptArmed = false;
|
|
64
|
-
resumeRequested = false;
|
|
65
|
-
finishDetected = false;
|
|
66
|
-
mode;
|
|
67
|
-
constructor({ config, buildSystemPrompt, runtimeSurface, sessionContext, }) {
|
|
68
|
-
configureOpenAIProvider(config);
|
|
69
|
-
setTracingDisabled(true);
|
|
70
|
-
this.config = config;
|
|
71
|
-
this.buildSystemPrompt = buildSystemPrompt;
|
|
72
|
-
this.runtimeSurface = runtimeSurface;
|
|
73
|
-
this.getDualSessionEnabled = sessionContext.getDualSessionEnabled;
|
|
74
|
-
this.getAndroidUseEnabled = sessionContext.getAndroidUseEnabled;
|
|
75
|
-
this.dynamicMcpServers = {};
|
|
76
|
-
this.mode = sessionContext.mode;
|
|
77
|
-
this.client = createOpenAIClient(config);
|
|
78
|
-
this.runner = new Runner({ model: getModelId(config) });
|
|
79
|
-
const persistedSessionId = loadSessionId(this.config, this.mode);
|
|
80
|
-
this.underlyingSession = new OpenAIFileSession({
|
|
81
|
-
sessionId: persistedSessionId,
|
|
82
|
-
});
|
|
83
|
-
this.sessionId = this.underlyingSession.getSessionIdSync();
|
|
84
|
-
saveSessionId(this.config, this.sessionId, this.mode);
|
|
85
|
-
this.session = this.buildCompactionSession(this.underlyingSession);
|
|
86
|
-
const persistedUsage = loadUsageSnapshot(this.config, this.mode);
|
|
87
|
-
if (persistedUsage) {
|
|
88
|
-
this.lastUsageSnapshot = {
|
|
89
|
-
...persistedUsage,
|
|
90
|
-
capturedAtMs: Date.now(),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async warmUp() {
|
|
95
|
-
// OpenAI sessions do not have an equivalent subprocess cold-start path.
|
|
96
|
-
}
|
|
97
|
-
discardWarmQuery() {
|
|
98
|
-
// OpenAI sessions hold no pre-warmed subprocess; nothing to discard.
|
|
99
|
-
}
|
|
100
|
-
sendAndStream(content, _options) {
|
|
101
|
-
// Log the resolved system prompt for training data capture. The console
|
|
102
|
-
// gets a compact head/tail preview; the full prompt goes into `data.prompt`
|
|
103
|
-
// of the JSONL entry so the log file remains complete.
|
|
104
|
-
const systemPrompt = this.buildSystemPrompt();
|
|
105
|
-
logSystemPrompt(systemPrompt);
|
|
106
|
-
const input = contentBlocksToInputItems(content);
|
|
107
|
-
this.inputClosed = false;
|
|
108
|
-
this.stopRequested = false;
|
|
109
|
-
this.pendingInjections = [];
|
|
110
|
-
this.interruptArmed = false;
|
|
111
|
-
this.resumeRequested = false;
|
|
112
|
-
this.finishDetected = false;
|
|
113
|
-
return this.streamMessages(input, this.sessionId);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Queue `content` as the next turn's input.
|
|
117
|
-
*
|
|
118
|
-
* If a run is active, this also *arms a soft interrupt*: the streaming
|
|
119
|
-
* loop in `streamMessages` watches for a safe SDK boundary (a
|
|
120
|
-
* `tool_output` event — at which point the tool call + tool result are
|
|
121
|
-
* already persisted to the session) and aborts the in-flight run as soon
|
|
122
|
-
* as one arrives. The resume loop then immediately starts a follow-up
|
|
123
|
-
* `runner.run()` with the queued items as input, against the same
|
|
124
|
-
* session, so the model perceives the new user message just like it
|
|
125
|
-
* would in the Claude path.
|
|
126
|
-
*
|
|
127
|
-
* If no run is active, the items sit in the queue until the next
|
|
128
|
-
* `sendAndStream` consumes them (or, if the session is already torn
|
|
129
|
-
* down, are reported as orphans by `hasOrphanedInjections`).
|
|
130
|
-
*
|
|
131
|
-
* Returns `true` whenever the message has been accepted (queued). Returns
|
|
132
|
-
* `false` only when the session is fully closed and no run is or will be
|
|
133
|
-
* active — in that case the caller should re-queue the message for the
|
|
134
|
-
* next wake cycle.
|
|
135
|
-
*/
|
|
136
|
-
injectMessage(content, _options) {
|
|
137
|
-
if (this.inputClosed && !this.abortController && !this.resumeRequested) {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
const items = contentBlocksToInputItems(content);
|
|
141
|
-
this.pendingInjections.push(...items);
|
|
142
|
-
if (this.abortController) {
|
|
143
|
-
// A run is live — let the stream loop tear it down at the next safe
|
|
144
|
-
// boundary so the queued items are picked up immediately rather than
|
|
145
|
-
// waiting for the model to finish its current chain of tool calls.
|
|
146
|
-
this.interruptArmed = true;
|
|
147
|
-
this.resumeRequested = true;
|
|
148
|
-
}
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Mark the session as no longer accepting fresh `sendAndStream` invocations
|
|
153
|
-
* and stop the resume loop from looping into another `runner.run()`.
|
|
154
|
-
*
|
|
155
|
-
* **We deliberately do NOT clear `pendingInjections` here.**
|
|
156
|
-
*
|
|
157
|
-
* `closeInput()` is called by the stream handler from many places, including
|
|
158
|
-
* mid-stream when it observes the `finish` tool block. At that point the
|
|
159
|
-
* resume loop hasn't yet had a chance to consume any messages that
|
|
160
|
-
* `injectMessage()` queued during the run. Dropping the buffer here would
|
|
161
|
-
* silently lose those user messages — exactly the bug we hit when an
|
|
162
|
-
* interrupt landed during a memory-view turn that ended in `finish`.
|
|
163
|
-
*
|
|
164
|
-
* Instead, leftover messages stay in `pendingInjections` until either:
|
|
165
|
-
* - the resume loop picks them up (when it's still allowed to run), or
|
|
166
|
-
* - the wake-cycle runner sees `hasOrphanedInjections` and re-queues the
|
|
167
|
-
* original `CommandMessage` batch for the next wake cycle.
|
|
168
|
-
*
|
|
169
|
-
* Hard teardowns that really do want the buffer wiped (e.g. `/stop`,
|
|
170
|
-
* watchdog timeout, owner-initiated shutdown) call `requestStop()` which
|
|
171
|
-
* clears it explicitly.
|
|
172
|
-
*/
|
|
173
|
-
/**
|
|
174
|
-
* Mark the session as no longer accepting fresh `sendAndStream` invocations
|
|
175
|
-
* and stop the resume loop from looping into another `runner.run()`.
|
|
176
|
-
*
|
|
177
|
-
* **We deliberately do NOT clear `pendingInjections` here.**
|
|
178
|
-
*
|
|
179
|
-
* `closeInput()` is called by the stream handler from many places, including
|
|
180
|
-
* mid-stream when it observes the `finish` tool block. At that point the
|
|
181
|
-
* resume loop hasn't yet had a chance to consume any messages that
|
|
182
|
-
* `injectMessage()` queued during the run. Dropping the buffer here would
|
|
183
|
-
* silently lose those user messages — exactly the bug we hit when an
|
|
184
|
-
* interrupt landed during a memory-view turn that ended in `finish`.
|
|
185
|
-
*
|
|
186
|
-
* Instead, leftover messages stay in `pendingInjections` until either:
|
|
187
|
-
* - the resume loop picks them up (when it's still allowed to run), or
|
|
188
|
-
* - the wake-cycle runner sees `hasOrphanedInjections` and re-queues the
|
|
189
|
-
* original `CommandMessage` batch for the next wake cycle.
|
|
190
|
-
*
|
|
191
|
-
* Hard teardowns that really do want the buffer wiped (e.g. `/stop`,
|
|
192
|
-
* watchdog timeout, owner-initiated shutdown) call `requestStop()` which
|
|
193
|
-
* clears it explicitly.
|
|
194
|
-
*/
|
|
195
|
-
closeInput() {
|
|
196
|
-
this.inputClosed = true;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Signal that the agent has called the `finish` tool. The stream loop
|
|
200
|
-
* will abort the run at the next safe `tool_output` boundary (where
|
|
201
|
-
* `stream.rawResponses` is already populated) instead of waiting for
|
|
202
|
-
* `stream.completed` — which can idle for 30s+ in the OpenAI SDK.
|
|
203
|
-
*/
|
|
204
|
-
signalFinish() {
|
|
205
|
-
this.finishDetected = true;
|
|
206
|
-
}
|
|
207
|
-
requestStop() {
|
|
208
|
-
this.stopRequested = true;
|
|
209
|
-
this.inputClosed = true;
|
|
210
|
-
this.pendingInjections = [];
|
|
211
|
-
this.interruptArmed = false;
|
|
212
|
-
this.resumeRequested = false;
|
|
213
|
-
this.abortController?.abort();
|
|
214
|
-
}
|
|
215
|
-
clearStop() {
|
|
216
|
-
this.stopRequested = false;
|
|
217
|
-
this.abortController = null;
|
|
218
|
-
}
|
|
219
|
-
isStopRequested() {
|
|
220
|
-
return this.stopRequested;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Update the persisted session ID.
|
|
224
|
-
*
|
|
225
|
-
* Only saves on first capture (null/empty → id). Once set, a different ID
|
|
226
|
-
* from the SDK is ignored — it means the resume failed and the SDK created
|
|
227
|
-
* a throwaway conversation.
|
|
228
|
-
*/
|
|
229
|
-
captureSessionId(id) {
|
|
230
|
-
if (!id || this.sessionId)
|
|
231
|
-
return;
|
|
232
|
-
this.sessionId = id;
|
|
233
|
-
saveSessionId(this.config, id, this.mode);
|
|
234
|
-
}
|
|
235
|
-
captureUsageSnapshot(snapshot) {
|
|
236
|
-
const normalizedSnapshot = normalizeOpenAIUsageSnapshot(snapshot);
|
|
237
|
-
this.lastUsageSnapshot = {
|
|
238
|
-
...normalizedSnapshot,
|
|
239
|
-
capturedAtMs: Date.now(),
|
|
240
|
-
};
|
|
241
|
-
saveUsageSnapshot(this.config, normalizedSnapshot, this.mode);
|
|
242
|
-
}
|
|
243
|
-
capturePostCompactionSnapshot(_postCompactionTokens) {
|
|
244
|
-
// OpenAI compaction is handled by the SDK session decorator.
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Force the OpenAI Responses API compaction path, archiving the
|
|
248
|
-
* pre-compaction transcript to disk for audit/debugging.
|
|
249
|
-
*
|
|
250
|
-
* **The session id is invariant across compactions**, matching the
|
|
251
|
-
* Claude engine's behavior (see `captureSessionId` in the Claude
|
|
252
|
-
* session — it ignores the SDK's post-`/compact` `session_id` echo
|
|
253
|
-
* because the id "should remain the same"). Keeping it stable means:
|
|
254
|
-
*
|
|
255
|
-
* - `session.json` never needs rewriting on compaction.
|
|
256
|
-
* - The active transcript file path (`<sessionId>.json`) is also
|
|
257
|
-
* stable, so no in-memory references (`this.underlyingSession`,
|
|
258
|
-
* `this.session`) need to be rebuilt.
|
|
259
|
-
* - Only the **contents** of the transcript change; the SDK's
|
|
260
|
-
* `runCompaction` rewrites them in place via clearSession + addItems.
|
|
261
|
-
*
|
|
262
|
-
* Sequence:
|
|
263
|
-
*
|
|
264
|
-
* 1. Snapshot the active session file to
|
|
265
|
-
* `<sessionId>.compacted-<iso>.json` — freezes pre-compaction state.
|
|
266
|
-
* 2. `runCompaction({ force: true })` — the SDK clears the underlying
|
|
267
|
-
* session and writes the compacted item set into the same file.
|
|
268
|
-
*
|
|
269
|
-
* If step 1 fails (e.g. the file doesn't exist yet because no items have
|
|
270
|
-
* been persisted) we still attempt step 2 — losing the archive is
|
|
271
|
-
* preferable to skipping the actual compaction.
|
|
272
|
-
*/
|
|
273
|
-
async requestCompaction() {
|
|
274
|
-
const activePath = this.underlyingSession.getFilePath();
|
|
275
|
-
const archivePath = this.archivePathForCurrent();
|
|
276
|
-
try {
|
|
277
|
-
// Step 1: archive pre-compaction state. Non-fatal on failure.
|
|
278
|
-
try {
|
|
279
|
-
fs.copyFileSync(activePath, archivePath);
|
|
280
|
-
}
|
|
281
|
-
catch (err) {
|
|
282
|
-
logger.warn(`OpenAI session pre-compaction archive failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
283
|
-
}
|
|
284
|
-
// Step 2: run compaction in place. The SDK clears the underlying
|
|
285
|
-
// session and rewrites it with the compacted items, all under the
|
|
286
|
-
// same file (`<sessionId>.json`). Session id, file path, and all
|
|
287
|
-
// wrapper references stay valid.
|
|
288
|
-
await this.session.runCompaction({ force: true });
|
|
289
|
-
logger.system(`OpenAI session compacted (${this.mode}); archive: ${archivePath}`);
|
|
290
|
-
}
|
|
291
|
-
catch (err) {
|
|
292
|
-
logger.warn(`Request /compact failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
archivePathForCurrent() {
|
|
296
|
-
const dir = getOpenAISessionsDir();
|
|
297
|
-
const safeIso = new Date().toISOString().replace(/:/g, "-");
|
|
298
|
-
return path.join(dir, `${this.sessionId}.compacted-${safeIso}.json`);
|
|
299
|
-
}
|
|
300
|
-
buildCompactionSession(underlying) {
|
|
301
|
-
return new OpenAIResponsesCompactionSession({
|
|
302
|
-
client: this.client,
|
|
303
|
-
underlyingSession: underlying,
|
|
304
|
-
model: getModelId(this.config),
|
|
305
|
-
// Disable SDK auto-compaction entirely.
|
|
306
|
-
//
|
|
307
|
-
// The SDK's auto-compaction (default threshold: 10 candidate items)
|
|
308
|
-
// calls the OpenAI Responses `responses.compact` API, which returns
|
|
309
|
-
// a single opaque `compaction` item containing only:
|
|
310
|
-
// { id, type: "compaction", encrypted_content: "<opaque blob>" }
|
|
311
|
-
//
|
|
312
|
-
// It then *clears the underlying session and writes back only this
|
|
313
|
-
// blob* (see OpenAIResponsesCompactionSession.runCompaction at line
|
|
314
|
-
// 90 of openaiResponsesCompactionSession.mjs). That destroys our
|
|
315
|
-
// human-readable transcript, breaks the transcript indexer, and
|
|
316
|
-
// makes session history unrecoverable if we ever switch providers.
|
|
317
|
-
//
|
|
318
|
-
// It also doesn't create an archive snapshot — unlike
|
|
319
|
-
// `requestCompaction()` below which copies the active session file
|
|
320
|
-
// to `<sessionId>.compacted-<iso>.json` before invoking the same
|
|
321
|
-
// `runCompaction` API.
|
|
322
|
-
//
|
|
323
|
-
// Compaction is therefore driven exclusively by `maybeCompact()`
|
|
324
|
-
// (called from the wake-cycle runner on heartbeat), which:
|
|
325
|
-
// 1. Checks token usage against a percentage threshold.
|
|
326
|
-
// 2. Archives the pre-compaction file.
|
|
327
|
-
// 3. Calls `runCompaction({ force: true })` so the SDK still
|
|
328
|
-
// replaces the session contents — but only when *we* decide.
|
|
329
|
-
shouldTriggerCompaction: () => false,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Token-usage-driven compaction trigger, called from the wake-cycle
|
|
334
|
-
* runner on heartbeat.
|
|
335
|
-
*
|
|
336
|
-
* Mirrors Claude's behavior: re-entrancy guard + cooldown + threshold
|
|
337
|
-
* check, then delegates to {@link requestCompaction} which archives
|
|
338
|
-
* and runs the actual SDK compaction. We deliberately do NOT use the
|
|
339
|
-
* SDK's auto-compaction path (`shouldTriggerCompaction`) because it
|
|
340
|
-
* compacts purely by item count and skips the archive — see the
|
|
341
|
-
* comment on {@link buildCompactionSession}.
|
|
342
|
-
*
|
|
343
|
-
* The default threshold (70%) is calibrated for the GPT-5.x 1M-token
|
|
344
|
-
* context window. Lower if you want more aggressive trimming, raise
|
|
345
|
-
* if cross-turn context fidelity matters more than latency.
|
|
346
|
-
*/
|
|
347
|
-
async maybeCompact(options) {
|
|
348
|
-
const usedPctThreshold = options?.usedPctThreshold ?? OPENAI_COMPACT_USED_PCT_THRESHOLD;
|
|
349
|
-
const cooldownMs = options?.cooldownMs ?? 10 * 60 * 1000;
|
|
350
|
-
// --- guard: in-flight ---
|
|
351
|
-
if (this.compactInFlight) {
|
|
352
|
-
logger.debug("[compact] skip: compaction already in flight");
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
// --- guard: cooldown ---
|
|
356
|
-
const nowMs = Date.now();
|
|
357
|
-
const sinceLastCompactMs = nowMs - this.lastCompactRequestAtMs;
|
|
358
|
-
if (sinceLastCompactMs <= cooldownMs) {
|
|
359
|
-
logger.debug(`[compact] skip: cooldown active sinceLast=${sinceLastCompactMs}ms cooldown=${cooldownMs}ms`);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
// --- trigger: token usage ---
|
|
363
|
-
const snap = this.lastUsageSnapshot;
|
|
364
|
-
if (!snap || snap.contextWindow <= 0) {
|
|
365
|
-
logger.debug("[compact] skip: no usage snapshot yet");
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (snap.usedPct < usedPctThreshold) {
|
|
369
|
-
logger.debug(`[compact] skip: under threshold (usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(usedPctThreshold * 100).toFixed(1)}%)`);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
logger.system(`[compact] requesting compaction (usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(usedPctThreshold * 100).toFixed(1)}%)`);
|
|
373
|
-
this.compactInFlight = true;
|
|
374
|
-
this.lastCompactRequestAtMs = nowMs;
|
|
375
|
-
try {
|
|
376
|
-
await this.requestCompaction();
|
|
377
|
-
}
|
|
378
|
-
finally {
|
|
379
|
-
this.compactInFlight = false;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
getSessionId() {
|
|
383
|
-
return this.sessionId;
|
|
384
|
-
}
|
|
385
|
-
getTranscriptPath() {
|
|
386
|
-
return this.underlyingSession.getFilePath();
|
|
387
|
-
}
|
|
388
|
-
getTranscriptItems() {
|
|
389
|
-
return this.underlyingSession.getAllItemsSync();
|
|
390
|
-
}
|
|
391
|
-
getUsageSnapshot() {
|
|
392
|
-
return this.lastUsageSnapshot ? { ...this.lastUsageSnapshot } : null;
|
|
393
|
-
}
|
|
394
|
-
getCurrentQuery() {
|
|
395
|
-
return this.currentRun;
|
|
396
|
-
}
|
|
397
|
-
setDynamicMcpServers(servers) {
|
|
398
|
-
this.dynamicMcpServers = { ...servers };
|
|
399
|
-
}
|
|
400
|
-
getDynamicMcpServers() {
|
|
401
|
-
return { ...this.dynamicMcpServers };
|
|
402
|
-
}
|
|
403
|
-
get isInputClosed() {
|
|
404
|
-
// While a run is active (abortController set) or about to resume after a
|
|
405
|
-
// soft-interrupt, the session is still accepting injected messages.
|
|
406
|
-
if (this.abortController || this.resumeRequested) {
|
|
407
|
-
return false;
|
|
408
|
-
}
|
|
409
|
-
return this.inputClosed;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* True when injected messages have been queued but the session is no
|
|
413
|
-
* longer running. The agent loop checks this after `streamMessages`
|
|
414
|
-
* finishes to re-queue messages instead of dropping them. We deliberately
|
|
415
|
-
* ignore `resumeRequested` here: if `pendingInjections` is non-empty and
|
|
416
|
-
* there is no active abortController, the messages are by definition
|
|
417
|
-
* orphaned regardless of any stale resume flag.
|
|
418
|
-
*/
|
|
419
|
-
get hasOrphanedInjections() {
|
|
420
|
-
return (this.pendingInjections.length > 0
|
|
421
|
-
&& !this.abortController);
|
|
422
|
-
}
|
|
423
|
-
async *streamMessages(initialInput, sessionId) {
|
|
424
|
-
const mcpServers = createOpenAIMcpServers({
|
|
425
|
-
...this.runtimeSurface.externalMcpServers,
|
|
426
|
-
...this.dynamicMcpServers,
|
|
427
|
-
});
|
|
428
|
-
let lastStream = null;
|
|
429
|
-
let nextInput = initialInput;
|
|
430
|
-
try {
|
|
431
|
-
await connectMcpServers(mcpServers);
|
|
432
|
-
const tools = createOpenAITools({
|
|
433
|
-
dualSession: this.getDualSessionEnabled(),
|
|
434
|
-
androidUse: this.getAndroidUseEnabled(),
|
|
435
|
-
mode: this.mode,
|
|
436
|
-
runtimeSurface: this.runtimeSurface,
|
|
437
|
-
});
|
|
438
|
-
logOpenAIToolInventory({
|
|
439
|
-
mode: this.mode,
|
|
440
|
-
tools,
|
|
441
|
-
mcpServers,
|
|
442
|
-
runtimeSurface: this.runtimeSurface,
|
|
443
|
-
});
|
|
444
|
-
const agent = new Agent({
|
|
445
|
-
name: this.mode === "coding" ? `${this.config.agentName} Coding` : this.config.agentName,
|
|
446
|
-
instructions: renderSystemPrompt(this.buildSystemPrompt()),
|
|
447
|
-
model: getModelId(this.config),
|
|
448
|
-
tools,
|
|
449
|
-
mcpServers,
|
|
450
|
-
// Reasoning model settings.
|
|
451
|
-
//
|
|
452
|
-
// `summary: "auto"` asks the Responses API to return a human-readable
|
|
453
|
-
// summary of the model's reasoning. Without this the API returns
|
|
454
|
-
// reasoning items with empty `summary` arrays — the model still pays
|
|
455
|
-
// the reasoning tokens, we just can't see what it thought about. The
|
|
456
|
-
// summary is for our debug visibility (logs, /obs).
|
|
457
|
-
//
|
|
458
|
-
// `providerData.include = ["reasoning.encrypted_content"]` asks the
|
|
459
|
-
// API to return the opaque encrypted reasoning blob alongside each
|
|
460
|
-
// reasoning item. The SDK persists that blob into the session and
|
|
461
|
-
// replays it on subsequent turns, so the model can pick up the same
|
|
462
|
-
// reasoning chain instead of re-deriving it from scratch each turn.
|
|
463
|
-
// This matters a lot for VisionClaw because every wake cycle starts
|
|
464
|
-
// a fresh `Runner.run()` — without the encrypted blob, the model
|
|
465
|
-
// re-thinks everything from the visible artifacts (tool calls /
|
|
466
|
-
// results / messages) instead of inheriting prior chain-of-thought.
|
|
467
|
-
//
|
|
468
|
-
// NOTE: In `openaiResponsesModel.mjs` (~line 2184) `providerData` is
|
|
469
|
-
// spread into the request body AFTER the auto-built `include` array
|
|
470
|
-
// (~line 2163), which means setting `providerData.include` overwrites
|
|
471
|
-
// it. That's safe for our tool mix — the SDK's auto-includes only
|
|
472
|
-
// fire for hosted search tools with `include_search_results`, which
|
|
473
|
-
// we do not use. If we later add such tools, we'll need to merge.
|
|
474
|
-
modelSettings: {
|
|
475
|
-
reasoning: { summary: "auto" },
|
|
476
|
-
providerData: {
|
|
477
|
-
include: ["reasoning.encrypted_content"],
|
|
478
|
-
},
|
|
479
|
-
},
|
|
480
|
-
});
|
|
481
|
-
// Outer resume loop: each iteration runs one `runner.run()` to either
|
|
482
|
-
// a natural end-of-turn or a *soft-interrupt abort* — i.e. a
|
|
483
|
-
// self-initiated abort fired at the next safe SDK boundary
|
|
484
|
-
// (`tool_output`) when `injectMessage()` queued items mid-run. After
|
|
485
|
-
// the run terminates, if there are queued injections, we loop and
|
|
486
|
-
// start a new run with those items as input, reusing the same
|
|
487
|
-
// `agent`, `session`, and `mcpServers` so the model continues from
|
|
488
|
-
// the persisted transcript without paying any cold-start tax.
|
|
489
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional: loop terminates via `break` after stream completion or non-resume exit
|
|
490
|
-
while (true) {
|
|
491
|
-
this.abortController = new AbortController();
|
|
492
|
-
// Don't reset `resumeRequested` here — `injectMessage()` may have
|
|
493
|
-
// already set it (along with `interruptArmed`) before we entered
|
|
494
|
-
// this iteration, and we need to preserve that signal.
|
|
495
|
-
let interruptedThisRun = false;
|
|
496
|
-
let finishAbortedThisRun = false;
|
|
497
|
-
const stream = await this.runner.run(agent, nextInput, {
|
|
498
|
-
stream: true,
|
|
499
|
-
session: this.session,
|
|
500
|
-
maxTurns: MAX_TURNS,
|
|
501
|
-
signal: this.abortController.signal,
|
|
502
|
-
});
|
|
503
|
-
lastStream = stream;
|
|
504
|
-
this.currentRun = null;
|
|
505
|
-
try {
|
|
506
|
-
for await (const event of stream) {
|
|
507
|
-
const normalized = normalizeRunEvent(event, sessionId);
|
|
508
|
-
if (normalized) {
|
|
509
|
-
yield normalized;
|
|
510
|
-
}
|
|
511
|
-
const usage = captureUsageFromEvent(event);
|
|
512
|
-
if (usage) {
|
|
513
|
-
this.captureUsageSnapshot(usage);
|
|
514
|
-
}
|
|
515
|
-
// Soft-interrupt: a message was injected while this run was
|
|
516
|
-
// streaming. We tear the run down at the next safe boundary
|
|
517
|
-
// (after `tool_output`, when the SDK has already persisted the
|
|
518
|
-
// call+result pair to the session) so the queued items can be
|
|
519
|
-
// delivered as the next turn's input rather than waiting for
|
|
520
|
-
// the model to finish its current chain of tool calls.
|
|
521
|
-
if (this.interruptArmed
|
|
522
|
-
&& this.pendingInjections.length > 0
|
|
523
|
-
&& !this.stopRequested
|
|
524
|
-
&& isSafeInterruptBoundary(event)) {
|
|
525
|
-
interruptedThisRun = true;
|
|
526
|
-
this.interruptArmed = false;
|
|
527
|
-
// Persist work-in-progress items before aborting. Without
|
|
528
|
-
// this, every tool call the agent already executed during
|
|
529
|
-
// this run (memory views, screenshots, notify_user, etc.)
|
|
530
|
-
// is dropped from the session file and the continuation
|
|
531
|
-
// run sees only user wake events. See the longer comment
|
|
532
|
-
// on the finish-abort branch below for the SDK trace.
|
|
533
|
-
try {
|
|
534
|
-
await persistRunItemsForAbort(stream, this.session);
|
|
535
|
-
}
|
|
536
|
-
catch (err) {
|
|
537
|
-
logger.warn(`OpenAI session soft-interrupt: manual persistence failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
538
|
-
}
|
|
539
|
-
logger.system(`OpenAI session soft-interrupt: aborting run at ${event.type}/${"name" in event ? event.name : ""}; ${this.pendingInjections.length} queued message(s) will resume.`);
|
|
540
|
-
this.abortController.abort();
|
|
541
|
-
break;
|
|
542
|
-
}
|
|
543
|
-
// Finish-abort: the agent called the `finish` tool and the
|
|
544
|
-
// tool result has been persisted (safe boundary). Abort early
|
|
545
|
-
// instead of waiting for the SDK's stream.completed (~30s).
|
|
546
|
-
//
|
|
547
|
-
// IMPORTANT: We must manually persist the run items here.
|
|
548
|
-
// The SDK only persists assistant/tool items at
|
|
549
|
-
// `next_step_final_output` / `next_step_interruption`
|
|
550
|
-
// (`run.mjs` lines 714/723). Aborting at `tool_output` is
|
|
551
|
-
// earlier in the loop, so the SDK's stream-loop catches our
|
|
552
|
-
// AbortError, calls `tryHandleRunError` which only handles
|
|
553
|
-
// `MaxTurnsExceededError`, then rethrows — bypassing
|
|
554
|
-
// `saveStreamResultToSession`. The result is that wake-cycle
|
|
555
|
-
// user input is persisted (via the early `persistStreamInputIfNeeded`
|
|
556
|
-
// path) but every assistant message, tool call, and tool
|
|
557
|
-
// result generated this turn is lost from the session file.
|
|
558
|
-
// Subsequent wake cycles then see a session full of user
|
|
559
|
-
// wake events with no assistant context, which manifests as
|
|
560
|
-
// the model repeating itself or losing state across turns.
|
|
561
|
-
//
|
|
562
|
-
// To preserve the speed benefit of finish-abort while not
|
|
563
|
-
// shedding history, we replicate the SDK's persistence
|
|
564
|
-
// payload manually before aborting: extract the raw items
|
|
565
|
-
// from `stream.newItems` and append them via the same
|
|
566
|
-
// `session.addItems` call the SDK would have made. The items
|
|
567
|
-
// already include the finish tool's call+result (we're at
|
|
568
|
-
// the `tool_output` boundary), all preceding tool calls,
|
|
569
|
-
// any assistant messages, and reasoning items.
|
|
570
|
-
if (this.finishDetected
|
|
571
|
-
&& !this.stopRequested
|
|
572
|
-
&& isSafeInterruptBoundary(event)) {
|
|
573
|
-
finishAbortedThisRun = true;
|
|
574
|
-
this.finishDetected = false;
|
|
575
|
-
try {
|
|
576
|
-
await persistRunItemsForAbort(stream, this.session);
|
|
577
|
-
}
|
|
578
|
-
catch (err) {
|
|
579
|
-
logger.warn(`OpenAI session finish-abort: manual persistence failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
580
|
-
}
|
|
581
|
-
logger.system(`OpenAI session finish-abort: skipping stream.completed at ${event.type}/${"name" in event ? event.name : ""}`);
|
|
582
|
-
this.abortController.abort();
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
if (!interruptedThisRun && !finishAbortedThisRun) {
|
|
587
|
-
await stream.completed;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
catch (err) {
|
|
591
|
-
if (interruptedThisRun && isAbortError(err)) {
|
|
592
|
-
// Expected: our own soft-interrupt aborted the run. Fall
|
|
593
|
-
// through to the resume check below.
|
|
594
|
-
}
|
|
595
|
-
else if (finishAbortedThisRun && isAbortError(err)) {
|
|
596
|
-
// Expected: our own finish-abort cut the run short. Fall
|
|
597
|
-
// through to yield the result message immediately.
|
|
598
|
-
}
|
|
599
|
-
else if (this.stopRequested && isAbortError(err)) {
|
|
600
|
-
// Expected: caller invoked requestStop() — treat as a clean
|
|
601
|
-
// termination, not as a session error.
|
|
602
|
-
logger.system("OpenAI session: run aborted by requestStop.");
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
throw err;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
const usage = extractUsageFromStream(stream);
|
|
609
|
-
if (usage) {
|
|
610
|
-
this.captureUsageSnapshot(usage);
|
|
611
|
-
}
|
|
612
|
-
// resumeRequested is mutated by injectMessage() while the stream is awaited above.
|
|
613
|
-
if (this.resumeRequested && this.pendingInjections.length > 0 && !this.stopRequested) {
|
|
614
|
-
// Hand the queued injections to the next run and loop. The previous
|
|
615
|
-
// run reached a safe SDK boundary (either via soft-interrupt abort
|
|
616
|
-
// or natural completion), so its session persistence is complete
|
|
617
|
-
// before this follow-up turn starts.
|
|
618
|
-
nextInput = this.pendingInjections;
|
|
619
|
-
this.pendingInjections = [];
|
|
620
|
-
this.resumeRequested = false;
|
|
621
|
-
this.interruptArmed = false;
|
|
622
|
-
this.finishDetected = false;
|
|
623
|
-
this.abortController = null;
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
// Natural end of stream (no resume): emit final result and exit.
|
|
627
|
-
yield buildResultMessage(stream, this.config);
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
catch (err) {
|
|
632
|
-
// Flush any remaining final-result message so the loop sees a clean
|
|
633
|
-
// termination event instead of a raw throw.
|
|
634
|
-
if (lastStream) {
|
|
635
|
-
try {
|
|
636
|
-
yield buildResultMessage(lastStream, this.config);
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
// best-effort
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
throw err;
|
|
643
|
-
}
|
|
644
|
-
finally {
|
|
645
|
-
this.inputClosed = true;
|
|
646
|
-
this.abortController = null;
|
|
647
|
-
this.currentRun = null;
|
|
648
|
-
this.interruptArmed = false;
|
|
649
|
-
this.resumeRequested = false;
|
|
650
|
-
this.finishDetected = false;
|
|
651
|
-
await closeMcpServers(mcpServers);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
function logOpenAIToolInventory(options) {
|
|
656
|
-
const hosted = options.runtimeSurface.nativeTools.kind === "openai-hosted"
|
|
657
|
-
? options.runtimeSurface.nativeTools.hostedTools.map(() => "WebSearch")
|
|
658
|
-
: [];
|
|
659
|
-
const extensions = options.mode === "coding" ? ["codex"] : [];
|
|
660
|
-
const sdkLocal = [];
|
|
661
|
-
const directTools = [];
|
|
662
|
-
for (const t of options.tools) {
|
|
663
|
-
const display = getToolDisplayName(t);
|
|
664
|
-
if (!display)
|
|
665
|
-
continue;
|
|
666
|
-
if (getToolType(t) === "shell" || getToolType(t) === "apply_patch") {
|
|
667
|
-
sdkLocal.push(display);
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
directTools.push(display);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
const mcpServerNames = options.mcpServers.map((server) => server.name);
|
|
674
|
-
logger.system(`OpenAI tool inventory (${options.mode}): hosted=[${hosted.join(", ") || "none"}] extensions=[${extensions.join(", ") || "none"}] sdkLocal=[${sdkLocal.join(", ") || "none"}] direct=[${directTools.join(", ") || "none"}] mcpServers=[${mcpServerNames.join(", ") || "none"}]`);
|
|
675
|
-
}
|
|
676
|
-
function getToolDisplayName(toolLike) {
|
|
677
|
-
if (!toolLike || typeof toolLike !== "object")
|
|
678
|
-
return null;
|
|
679
|
-
const record = toolLike;
|
|
680
|
-
const name = record.name ?? record.toolName;
|
|
681
|
-
return typeof name === "string" && name.length > 0 ? name : null;
|
|
682
|
-
}
|
|
683
|
-
function getToolType(toolLike) {
|
|
684
|
-
if (!toolLike || typeof toolLike !== "object")
|
|
685
|
-
return null;
|
|
686
|
-
const record = toolLike;
|
|
687
|
-
const type = record.type;
|
|
688
|
-
return typeof type === "string" && type.length > 0 ? type : null;
|
|
689
|
-
}
|
|
690
|
-
function isAbortError(err) {
|
|
691
|
-
if (!err || typeof err !== "object")
|
|
692
|
-
return false;
|
|
693
|
-
const candidate = err;
|
|
694
|
-
return candidate.name === "AbortError" || candidate.code === "ABORT_ERR";
|
|
695
|
-
}
|
|
696
|
-
/**
|
|
697
|
-
* Whether a stream event marks a safe place to abort the current run.
|
|
698
|
-
*
|
|
699
|
-
* "Safe" means the runtime state is balanced: any in-flight tool call has
|
|
700
|
-
* its matching tool result already produced, so aborting here leaves a
|
|
701
|
-
* clean tool_call + tool_call_result pair in the in-memory run state. The
|
|
702
|
-
* Responses API would otherwise reject a resume payload that referenced an
|
|
703
|
-
* orphan tool call without its result.
|
|
704
|
-
*
|
|
705
|
-
* IMPORTANT: "safe" here refers only to runtime state, NOT to session file
|
|
706
|
-
* persistence. The SDK only writes assistant/tool items to the session at
|
|
707
|
-
* `next_step_final_output` / `next_step_interruption` (see
|
|
708
|
-
* `runner/sessionPersistence.mjs::persistRunItemsToSession` invoked from
|
|
709
|
-
* `run.mjs` lines 714/723). When we abort at `tool_output` we are *before*
|
|
710
|
-
* those checkpoints, so the manual persistence helper
|
|
711
|
-
* {@link persistRunItemsForAbort} must be called by the caller.
|
|
712
|
-
*
|
|
713
|
-
* - `run_item_stream_event` / `tool_output`: a tool just finished. This is
|
|
714
|
-
* the most common boundary we hit.
|
|
715
|
-
* - `agent_updated_stream_event`: a handoff completed and the SDK swapped
|
|
716
|
-
* the active agent.
|
|
717
|
-
*
|
|
718
|
-
* We deliberately do NOT treat raw model deltas (`raw_model_stream_event`)
|
|
719
|
-
* as safe — those fire mid-token while a tool call is being constructed,
|
|
720
|
-
* before any tool result has been produced.
|
|
721
|
-
*/
|
|
722
|
-
function isSafeInterruptBoundary(event) {
|
|
723
|
-
if (event.type === "agent_updated_stream_event") {
|
|
724
|
-
return true;
|
|
725
|
-
}
|
|
726
|
-
if (event.type !== "run_item_stream_event") {
|
|
727
|
-
return false;
|
|
728
|
-
}
|
|
729
|
-
return event.name === "tool_output";
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Manually persist the assistant/tool items the SDK has accumulated
|
|
733
|
-
* during the current run before we abort it.
|
|
734
|
-
*
|
|
735
|
-
* This is what `runner/sessionPersistence.mjs::persistRunItemsToSession`
|
|
736
|
-
* does internally — but only at `next_step_final_output` /
|
|
737
|
-
* `next_step_interruption` (`run.mjs` lines 714/723). Both of our abort
|
|
738
|
-
* triggers fire much earlier in the SDK loop:
|
|
739
|
-
* - **finish-abort**: at the `run_item_stream_event/tool_output` for
|
|
740
|
-
* the `finish` tool's result.
|
|
741
|
-
* - **soft-interrupt**: at the same `tool_output` boundary, when a new
|
|
742
|
-
* user message has been queued and we want to inject it now instead
|
|
743
|
-
* of waiting out the current chain.
|
|
744
|
-
*
|
|
745
|
-
* In both cases the SDK's stream loop catches our `AbortError`, runs
|
|
746
|
-
* `tryHandleRunError` (which only handles `MaxTurnsExceededError` —
|
|
747
|
-
* verified in `runner/errorHandlers.mjs` line 25), and rethrows. The
|
|
748
|
-
* `await saveStreamResultToSession(...)` call is bypassed entirely, so
|
|
749
|
-
* every tool call, tool result, reasoning summary, and assistant
|
|
750
|
-
* message produced this turn is lost from the session file. The next
|
|
751
|
-
* wake cycle then sees a session full of user wake events with no
|
|
752
|
-
* record of the agent's prior work, which manifests as the model
|
|
753
|
-
* "forgetting" what it just did or repeating itself.
|
|
754
|
-
*
|
|
755
|
-
* We replicate the relevant subset of the SDK's persistence logic:
|
|
756
|
-
*
|
|
757
|
-
* 1. Pull `stream.newItems` (the public read-only run-items collection).
|
|
758
|
-
* 2. Filter out `tool_approval_item` (matches the SDK's filter at
|
|
759
|
-
* `runner/items.mjs::extractOutputItemsFromRunItems`).
|
|
760
|
-
* 3. Map each `RunItem` to its `rawItem` (the persistable
|
|
761
|
-
* `AgentInputItem` shape — function_call, function_call_result,
|
|
762
|
-
* reasoning, message, etc.).
|
|
763
|
-
* 4. Call `session.addItems()` to append. The wrapping
|
|
764
|
-
* `OpenAIResponsesCompactionSession` forwards to the underlying
|
|
765
|
-
* file-backed session, which writes to disk.
|
|
766
|
-
*
|
|
767
|
-
* We deliberately do not normalize ids or strip transient call ids the
|
|
768
|
-
* way the SDK's `normalizeItemsForSessionPersistence` does — for our
|
|
769
|
-
* file-only session those normalizations are mostly server-side concerns
|
|
770
|
-
* and the rawItem already passes through cleanly. If we ever observe
|
|
771
|
-
* resume-time conflicts on encrypted_content or call_id collisions, we
|
|
772
|
-
* can mirror more of `sessionPersistence.mjs::stripTransientCallIds`
|
|
773
|
-
* here.
|
|
774
|
-
*/
|
|
775
|
-
async function persistRunItemsForAbort(stream, session) {
|
|
776
|
-
const newItems = stream.newItems;
|
|
777
|
-
if (!Array.isArray(newItems) || newItems.length === 0) {
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
const rawItems = [];
|
|
781
|
-
for (const entry of newItems) {
|
|
782
|
-
if (entry === null || typeof entry !== "object")
|
|
783
|
-
continue;
|
|
784
|
-
const item = entry;
|
|
785
|
-
if (item.type === "tool_approval_item")
|
|
786
|
-
continue;
|
|
787
|
-
const raw = item.rawItem;
|
|
788
|
-
if (raw && typeof raw === "object") {
|
|
789
|
-
rawItems.push(raw);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
if (rawItems.length === 0) {
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
await session.addItems(rawItems);
|
|
796
|
-
logger.debug(`OpenAI session: manually persisted ${rawItems.length} run item(s) before abort`);
|
|
797
|
-
}
|
|
798
|
-
function renderSystemPrompt(prompt) {
|
|
799
|
-
if (typeof prompt === "string") {
|
|
800
|
-
return prompt;
|
|
801
|
-
}
|
|
802
|
-
const presetIntro = "You are in a coding-focused session. Prefer careful, multi-step engineering work.";
|
|
803
|
-
return [presetIntro, prompt.append ?? ""].filter(Boolean).join("\n\n");
|
|
804
|
-
}
|
|
805
|
-
function contentBlocksToInputItems(content) {
|
|
806
|
-
const blocks = normalizeInputContent(content);
|
|
807
|
-
return [
|
|
808
|
-
{
|
|
809
|
-
role: "user",
|
|
810
|
-
content: blocks.map((block) => {
|
|
811
|
-
if (block.type === "text") {
|
|
812
|
-
return {
|
|
813
|
-
type: "input_text",
|
|
814
|
-
text: block.text,
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
if (block.source.type === "url") {
|
|
818
|
-
return {
|
|
819
|
-
type: "input_image",
|
|
820
|
-
image: block.source.url,
|
|
821
|
-
detail: "original",
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
return {
|
|
825
|
-
type: "input_image",
|
|
826
|
-
image: `data:${block.source.media_type};base64,${block.source.data}`,
|
|
827
|
-
detail: "original",
|
|
828
|
-
};
|
|
829
|
-
}),
|
|
830
|
-
},
|
|
831
|
-
];
|
|
832
|
-
}
|
|
833
|
-
function normalizeInputContent(content) {
|
|
834
|
-
if (typeof content === "string") {
|
|
835
|
-
return [{ type: "text", text: content }];
|
|
836
|
-
}
|
|
837
|
-
const blocks = [];
|
|
838
|
-
for (const block of content) {
|
|
839
|
-
if (block.type === "text") {
|
|
840
|
-
blocks.push({ type: "text", text: block.text });
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
if (block.type === "image") {
|
|
844
|
-
if (block.source.type === "url") {
|
|
845
|
-
blocks.push({ type: "image", source: { type: "url", url: block.source.url } });
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
blocks.push({
|
|
849
|
-
type: "image",
|
|
850
|
-
source: {
|
|
851
|
-
type: "base64",
|
|
852
|
-
media_type: block.source.media_type,
|
|
853
|
-
data: block.source.data,
|
|
854
|
-
},
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return blocks;
|
|
859
|
-
}
|
|
860
|
-
export function normalizeOpenAIRunEventForTest(event, sessionId) {
|
|
861
|
-
return normalizeRunEvent(event, sessionId);
|
|
862
|
-
}
|
|
863
|
-
function normalizeRunEvent(event, sessionId) {
|
|
864
|
-
if (event.type === "raw_model_stream_event") {
|
|
865
|
-
const data = event.data;
|
|
866
|
-
if (data.type === "output_text_delta" && data.delta) {
|
|
867
|
-
return {
|
|
868
|
-
type: "assistant",
|
|
869
|
-
session_id: sessionId,
|
|
870
|
-
message: {
|
|
871
|
-
role: "assistant",
|
|
872
|
-
content: [{ type: "text", text: data.delta }],
|
|
873
|
-
},
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
return null;
|
|
877
|
-
}
|
|
878
|
-
if (event.type !== "run_item_stream_event") {
|
|
879
|
-
return null;
|
|
880
|
-
}
|
|
881
|
-
const raw = event.item.rawItem;
|
|
882
|
-
if (!raw?.type) {
|
|
883
|
-
return null;
|
|
884
|
-
}
|
|
885
|
-
if (event.name === "message_output_created" && raw.type === "message") {
|
|
886
|
-
return null;
|
|
887
|
-
}
|
|
888
|
-
if (event.name === "reasoning_item_created" && raw.type === "reasoning") {
|
|
889
|
-
// The OpenAI Agents SDK's `RunReasoningItem` normalizes the API's
|
|
890
|
-
// `summary_text` parts as `{ type: "input_text", text, providerData: { type: "summary_text" } }`.
|
|
891
|
-
// Earlier we filtered by `entry.type === "reasoning_text"` which never
|
|
892
|
-
// matched any item the SDK actually produces, so reasoning summaries
|
|
893
|
-
// were silently dropped before reaching the stream handler. Match
|
|
894
|
-
// both shapes so we work whether the SDK changes its internal tag in
|
|
895
|
-
// a future release or keeps the current `input_text` mapping.
|
|
896
|
-
const text = raw.content
|
|
897
|
-
?.filter((entry) => (entry.type === "input_text"
|
|
898
|
-
|| entry.type === "reasoning_text"
|
|
899
|
-
|| entry.type === "summary_text")
|
|
900
|
-
&& typeof entry.text === "string")
|
|
901
|
-
.map((entry) => entry.text)
|
|
902
|
-
.join(" ");
|
|
903
|
-
if (!text) {
|
|
904
|
-
return null;
|
|
905
|
-
}
|
|
906
|
-
return {
|
|
907
|
-
type: "assistant",
|
|
908
|
-
session_id: sessionId,
|
|
909
|
-
message: {
|
|
910
|
-
role: "assistant",
|
|
911
|
-
content: [{ type: "thinking", thinking: text }],
|
|
912
|
-
},
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
if (event.name === "tool_called") {
|
|
916
|
-
if (raw.type === "function_call") {
|
|
917
|
-
return {
|
|
918
|
-
type: "assistant",
|
|
919
|
-
session_id: sessionId,
|
|
920
|
-
message: {
|
|
921
|
-
role: "assistant",
|
|
922
|
-
content: [{
|
|
923
|
-
type: "tool_use",
|
|
924
|
-
id: raw.callId,
|
|
925
|
-
tool_use_id: raw.callId,
|
|
926
|
-
name: raw.name ?? "tool",
|
|
927
|
-
input: parseToolArguments(raw.arguments),
|
|
928
|
-
}],
|
|
929
|
-
},
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
if (raw.type === "computer_call") {
|
|
933
|
-
return {
|
|
934
|
-
type: "assistant",
|
|
935
|
-
session_id: sessionId,
|
|
936
|
-
message: {
|
|
937
|
-
role: "assistant",
|
|
938
|
-
content: [{
|
|
939
|
-
type: "tool_use",
|
|
940
|
-
id: raw.callId,
|
|
941
|
-
tool_use_id: raw.callId,
|
|
942
|
-
name: "computer",
|
|
943
|
-
input: raw.action ?? {},
|
|
944
|
-
}],
|
|
945
|
-
},
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
if (event.name === "tool_output") {
|
|
950
|
-
if (raw.type === "function_call_result" || raw.type === "computer_call_result") {
|
|
951
|
-
return {
|
|
952
|
-
type: "user",
|
|
953
|
-
session_id: sessionId,
|
|
954
|
-
parent_tool_use_id: null,
|
|
955
|
-
message: {
|
|
956
|
-
role: "user",
|
|
957
|
-
content: [{
|
|
958
|
-
type: "tool_result",
|
|
959
|
-
tool_use_id: raw.callId ?? "",
|
|
960
|
-
content: raw.output ?? "",
|
|
961
|
-
is_error: false,
|
|
962
|
-
}],
|
|
963
|
-
},
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
return null;
|
|
968
|
-
}
|
|
969
|
-
function parseToolArguments(value) {
|
|
970
|
-
if (!value) {
|
|
971
|
-
return {};
|
|
972
|
-
}
|
|
973
|
-
try {
|
|
974
|
-
return JSON.parse(value);
|
|
975
|
-
}
|
|
976
|
-
catch {
|
|
977
|
-
return value;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
function captureUsageFromEvent(event) {
|
|
981
|
-
if (event.type !== "raw_model_stream_event") {
|
|
982
|
-
return null;
|
|
983
|
-
}
|
|
984
|
-
const data = event.data;
|
|
985
|
-
if (data.type !== "response_done") {
|
|
986
|
-
return null;
|
|
987
|
-
}
|
|
988
|
-
const inputTokens = data.response?.usage?.inputTokens;
|
|
989
|
-
if (typeof inputTokens !== "number") {
|
|
990
|
-
return null;
|
|
991
|
-
}
|
|
992
|
-
return buildOpenAIUsageSnapshot(inputTokens);
|
|
993
|
-
}
|
|
994
|
-
function extractUsageFromStream(stream) {
|
|
995
|
-
const usage = stream.rawResponses?.at(-1)?.usage;
|
|
996
|
-
if (typeof usage?.inputTokens !== "number") {
|
|
997
|
-
return null;
|
|
998
|
-
}
|
|
999
|
-
return buildOpenAIUsageSnapshot(usage.inputTokens);
|
|
1000
|
-
}
|
|
1001
|
-
function buildOpenAIUsageSnapshot(inputTokens) {
|
|
1002
|
-
return {
|
|
1003
|
-
usedInputTokens: inputTokens,
|
|
1004
|
-
contextWindow: OPENAI_CONTEXT_WINDOW,
|
|
1005
|
-
usedPct: inputTokens / OPENAI_CONTEXT_WINDOW,
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
function normalizeOpenAIUsageSnapshot(snapshot) {
|
|
1009
|
-
if (snapshot.contextWindow > 0) {
|
|
1010
|
-
return snapshot;
|
|
1011
|
-
}
|
|
1012
|
-
return buildOpenAIUsageSnapshot(snapshot.usedInputTokens);
|
|
1013
|
-
}
|
|
1014
|
-
function buildResultMessage(stream, config) {
|
|
1015
|
-
const usage = stream.rawResponses?.at(-1)?.usage;
|
|
1016
|
-
const inputTokens = usage?.inputTokens ?? 0;
|
|
1017
|
-
const outputTokens = usage?.outputTokens ?? 0;
|
|
1018
|
-
const modelId = getModelId(config);
|
|
1019
|
-
const errorText = stream.error instanceof Error
|
|
1020
|
-
? stream.error.message
|
|
1021
|
-
: stream.error
|
|
1022
|
-
? JSON.stringify(stream.error)
|
|
1023
|
-
: "";
|
|
1024
|
-
const hasUsage = typeof usage?.inputTokens === "number";
|
|
1025
|
-
const modelUsage = hasUsage
|
|
1026
|
-
? {
|
|
1027
|
-
[modelId]: {
|
|
1028
|
-
contextWindow: OPENAI_CONTEXT_WINDOW,
|
|
1029
|
-
inputTokens,
|
|
1030
|
-
outputTokens,
|
|
1031
|
-
cacheReadInputTokens: 0,
|
|
1032
|
-
cacheCreationInputTokens: 0,
|
|
1033
|
-
},
|
|
1034
|
-
}
|
|
1035
|
-
: undefined;
|
|
1036
|
-
return {
|
|
1037
|
-
type: "result",
|
|
1038
|
-
subtype: errorText ? "error" : "success",
|
|
1039
|
-
num_turns: 1,
|
|
1040
|
-
usage: {
|
|
1041
|
-
input_tokens: inputTokens,
|
|
1042
|
-
output_tokens: outputTokens,
|
|
1043
|
-
},
|
|
1044
|
-
total_cost_usd: 0,
|
|
1045
|
-
is_error: Boolean(errorText),
|
|
1046
|
-
errors: errorText ? [errorText] : [],
|
|
1047
|
-
model: modelId,
|
|
1048
|
-
...(modelUsage ? { modelUsage } : {}),
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
async function connectMcpServers(servers) {
|
|
1052
|
-
for (const server of servers) {
|
|
1053
|
-
await server.connect();
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
async function closeMcpServers(servers) {
|
|
1057
|
-
for (const server of servers) {
|
|
1058
|
-
try {
|
|
1059
|
-
await server.close();
|
|
1060
|
-
}
|
|
1061
|
-
catch (err) {
|
|
1062
|
-
logger.warn(`Failed to close MCP server "${server.name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
//# sourceMappingURL=session.js.map
|