@xopcai/xopc 0.0.373 → 0.0.374
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/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear--kDkSTqC.js → agent-avatar-dicebear-BrA20N2m.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-z4_2-Kn2.js → agent-defaults-settings-panel-BZrOdxou.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-plugin-dialog-BYndK83K.js → agent-plugin-dialog-B3ux1AhY.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-CVo96cx1.js → agents-CXlXFU6M.js} +3 -3
- package/dist/gateway/static/root/assets/{app-management-settings-panel-D9oOJKiH.js → app-management-settings-panel-BXahEVPm.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-B0-8Cdyv.js → appearance-settings-CE2qJMkD.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-DasVc6i4.js → apps-page-DGVOnCxZ.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BlCNqy2i.js → automations-page-BWL7eT4_.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-workspace-C8ipDP05.js → automations-workspace-BxIShhkZ.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Cktlez6g.js → block-editor-3n446-mX.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-KRfDl8ej.js → browser-automations-page-BmTK7Gtn.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-settings-page-BraQ4wnh.js → browser-settings-page-DBQ-WETQ.js} +1 -1
- package/dist/gateway/static/root/assets/{capabilities-page-B3m8xGzd.js → capabilities-page-D-nQ6Zzi.js} +2 -2
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-VQ4fHuBV.js → capabilities-settings-panel-Dx1fpBmH.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-header-actions-f5Ido4Eg.js → capability-header-actions-BRMqvQVb.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-N4Vc2nEG.js → channels-DXfEP1FR.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-BwvTKwsn.js → chat-terminal-dock-BRdzCjWO.js} +1 -1
- package/dist/gateway/static/root/assets/{computer-settings-page-CAwUsKpw.js → computer-settings-page-BhCHMRpD.js} +1 -1
- package/dist/gateway/static/root/assets/{connector-service-page-C0HuAMuH.js → connector-service-page-BXE8Z-Ll.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-BcBmW8JT.js → connectors-page-D9RZjhw8.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-DiQxRpe-.js → date-picker-BJ7N92wm.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-Ds0_BJbN.js → dependency-picker-gyxCM-hb.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-ZMEOYwrR.js → desktop-pet-B-WM4l-L.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-DkyTqD07.js → desktop-pet-settings-usJqcew5.js} +1 -1
- package/dist/gateway/static/root/assets/{device-pairing-wizard-CvaI5ZJZ.js → device-pairing-wizard-DoSR4DGk.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-Bfv93eK1.js → directory-picker-path-field-DqVlV2YH.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-JXKGtIdM.js → extension-page-CUule9SU.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-lsQ2xvbr.js → extension-settings-page-_OFukkEJ.js} +1 -1
- package/dist/gateway/static/root/assets/{fallback-models-7bG0AND1.js → fallback-models-CiSc3h8i.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-BpbcoPx9.js → gateway-settings-qSUrpS1h.js} +1 -1
- package/dist/gateway/static/root/assets/{home-page-BxctC0aJ.js → home-page-COjDsuzg.js} +1 -1
- package/dist/gateway/static/root/assets/{imports-page-Bq8CazCb.js → imports-page-DdvIj-ZL.js} +1 -1
- package/dist/gateway/static/root/assets/{index-B40-KDN8.js → index-DEaVabvo.js} +6 -6
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-kkAevkl2.js → keyboard-shortcuts-settings-DbqFlNsF.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-abLaf1hW.js → local-app-workbench-page-REowUzkp.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-nwsnwYjK.js → local-apps-page-DdCMNn_z.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-BEgYS-Xg.js → logs-page-B_6fHDoi.js} +1 -1
- package/dist/gateway/static/root/assets/management-settings-D4vcSRE3.js +1 -0
- package/dist/gateway/static/root/assets/{note-detail-panel-CCnfurBR.js → note-detail-panel-B7U6EbAi.js} +3 -3
- package/dist/gateway/static/root/assets/{notes-page-D9u_znxM.js → notes-page-BzHVaH8j.js} +1 -1
- package/dist/gateway/static/root/assets/{page-context-capture-button-BO3FoRej.js → page-context-capture-button-D7Y4vFRN.js} +1 -1
- package/dist/gateway/static/root/assets/personal-page-E6huhFk4.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-Dso2_hyl.js → product-open-page-CK_czbBx.js} +1 -1
- package/dist/gateway/static/root/assets/{product-preview-content-CkvVb3EA.js → product-preview-content-CU6KlaVP.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-UjrrzE3H.js → project-detail-page-c3ye2bdq.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-C1h63P2M.js → projects-page-BA1UuuRF.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-BJifH8AB.js → remote-access-hub-D_FLVZAm.js} +1 -1
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-vG1wqcEb.js → runtime-tools-settings-panel-Cxjmwdyf.js} +1 -1
- package/dist/gateway/static/root/assets/{scenes-page-CwoAoYMX.js → scenes-page-Av47PHdM.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-BIsHdyz7.js → schema-form-IM7QeSm3.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-BcCLp3yU.js → sessions-page-Bol3ScEc.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DpCanqrD.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-z9Jlk0BL.js → settings-page-BeVtwwVd.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-CrpXECqe.js → setup-status-panel-zTmrMwMm.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-D9XpTJXP.js → shares-settings-Bkhkt_6M.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-api-DtPOf1aW.js → skill-api-Dgwc707w.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-Bq9-2b9U.js → skills-page-CFIte-vM.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-P7T729uD.js → system-settings-panel-Ck6zGDHb.js} +1 -1
- package/dist/gateway/static/root/assets/{task-detail-page-CsB9AFx6.js → task-detail-page-C3ZPEZqw.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-CuowqIZ7.js → tunnel-api-CjxMpAm2.js} +1 -1
- package/dist/gateway/static/root/assets/{usage-page-avX3msoZ.js → usage-page-Dnhp9xQ4.js} +1 -1
- package/dist/gateway/static/root/assets/{user-model-page-DGDBWw7P.js → user-model-page-sx1lNWKf.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-BgJNz2tH.js → work-discovery-overlay-CVd_y3K2.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-1I2q2J_U.js → workflow-run-setup-panel-Dwg40uGK.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-DhJl9pnO.js → workflows-page-BQR4JdXB.js} +1 -1
- package/dist/gateway/static/root/assets/{xopc-cloud-account-card-CnPBP1V_.js → xopc-cloud-account-card-CaKc_J7j.js} +1 -1
- package/dist/gateway/static/root/index.html +1 -1
- package/dist/package.js +1 -1
- package/dist/packages/realtime-protocol/src/voice.js +11 -0
- package/dist/src/agent/agent-manager.js +4 -4
- package/dist/src/agent/background-review/run-background-review.js +1 -1
- package/dist/src/agent/embedded/model-runtime.js +1 -1
- package/dist/src/agent/embedded/run-for-session.js +2 -2
- package/dist/src/agent/embedded/run-turn.js +2 -2
- package/dist/src/agent/image/tool-model-config.js +1 -1
- package/dist/src/agent/service.js +3 -3
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/tasks/task-judge-service.js +3 -3
- package/dist/src/agent-config/schema.d.ts +9 -0
- package/dist/src/agent-config/schema.js +1 -0
- package/dist/src/ai-assist/text-assist.js +1 -1
- package/dist/src/auth/credentials.js +2 -2
- package/dist/src/auth/model-credential-store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +1 -1
- package/dist/src/chat-commands/builtins/config.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/image-providers.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/gateway/shared.js +2 -1
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/cli/commands/onboard/model.js +3 -3
- package/dist/src/computer/runtime.js +1 -1
- package/dist/src/config/gateway-bind.js +2 -1
- package/dist/src/config/index.js +5 -5
- package/dist/src/config/loader.js +1 -1
- package/dist/src/config/models-json.d.ts +8 -8
- package/dist/src/config/models-json.js +2 -2
- package/dist/src/config/schema.d.ts +13 -12
- package/dist/src/config/schema.js +7 -3
- package/dist/src/daemon/constants.js +14 -9
- package/dist/src/daemon/index.js +4 -1
- package/dist/src/daemon/install-plan.js +2 -1
- package/dist/src/daemon/launchd.js +2 -1
- package/dist/src/daemon/schtasks.js +2 -1
- package/dist/src/daemon/service-audit.js +2 -1
- package/dist/src/daemon/service.js +2 -1
- package/dist/src/daemon/systemd.js +2 -1
- package/dist/src/extensions/activation-context.js +1 -1
- package/dist/src/gateway/client-ip.js +2 -1
- package/dist/src/gateway/hono/app.js +2 -1
- package/dist/src/gateway/hono/lib/config-payload.js +2 -2
- package/dist/src/gateway/hono/middleware/auth.d.ts +3 -5
- package/dist/src/gateway/hono/middleware/auth.js +56 -14
- package/dist/src/gateway/hono/middleware/logger.js +2 -1
- package/dist/src/gateway/hono/middleware/public-device-rate-limit.d.ts +16 -0
- package/dist/src/gateway/hono/middleware/public-device-rate-limit.js +43 -0
- package/dist/src/gateway/hono/middleware/strict-rate-limit.js +3 -2
- package/dist/src/gateway/hono/oauth-async.js +2 -1
- package/dist/src/gateway/hono/routes/browser.js +2 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
- package/dist/src/gateway/hono/routes/config.js +2 -1
- package/dist/src/gateway/hono/routes/device-pairing-approval.d.ts +3 -8
- package/dist/src/gateway/hono/routes/device-pairing-approval.js +5 -20
- package/dist/src/gateway/hono/routes/devices.js +25 -9
- package/dist/src/gateway/hono/routes/endpoint-tools.js +1 -0
- package/dist/src/gateway/hono/routes/models.js +3 -3
- package/dist/src/gateway/hono/routes/observability.js +1 -1
- package/dist/src/gateway/hono/routes/personal-agent.js +3 -2
- package/dist/src/gateway/hono/routes/public-gateway.js +6 -6
- package/dist/src/gateway/hono/routes/shares.js +2 -1
- package/dist/src/gateway/hono/routes/voice.js +2 -1
- package/dist/src/gateway/host.js +9 -4
- package/dist/src/gateway/index.js +8 -4
- package/dist/src/gateway/listen.js +2 -1
- package/dist/src/gateway/rate-limit/auth-policy.js +7 -11
- package/dist/src/gateway/rate-limit/buckets.d.ts +6 -0
- package/dist/src/gateway/rate-limit/buckets.js +211 -189
- package/dist/src/gateway/rate-limit/env-flags.js +4 -1
- package/dist/src/gateway/rate-limit/index.js +13 -4
- package/dist/src/gateway/runtime-config.js +5 -3
- package/dist/src/gateway/security/audit.js +3 -2
- package/dist/src/gateway/security/browser-session.js +2 -1
- package/dist/src/gateway/security/device-request-proof.d.ts +28 -0
- package/dist/src/gateway/security/device-request-proof.js +122 -0
- package/dist/src/gateway/security/loopback.js +7 -6
- package/dist/src/gateway/service/config-coordinator.js +1 -1
- package/dist/src/gateway/service/session-command-service.js +1 -1
- package/dist/src/gateway/service.js +12 -10
- package/dist/src/gateway/tailscale-auth.js +2 -1
- package/dist/src/gateway/ws-security.js +2 -1
- package/dist/src/home-intelligence/generator.js +1 -1
- package/dist/src/infra/rate-limit/failure-limiter.js +78 -90
- package/dist/src/infra/rate-limit/index.js +11 -3
- package/dist/src/infra/rate-limit/keyed-store.js +52 -47
- package/dist/src/infra/rate-limit/rate-limiter.js +55 -58
- package/dist/src/infra/update-service-refresh.js +2 -1
- package/dist/src/notes/service.js +1 -1
- package/dist/src/observability/runtime.js +1 -1
- package/dist/src/personal-agent/proactivity/service.js +2 -2
- package/dist/src/personal-agent/reply-composer.js +2 -2
- package/dist/src/personal-agent/repository.d.ts +3 -0
- package/dist/src/personal-agent/repository.js +1 -0
- package/dist/src/personal-agent/service.d.ts +6 -2
- package/dist/src/personal-agent/service.js +4 -3
- package/dist/src/platform/client.d.ts +1 -1
- package/dist/src/platform/contracts.d.ts +1 -1
- package/dist/src/platform/index.js +1 -1
- package/dist/src/providers/index.js +4 -4
- package/dist/src/providers/model-registry.js +2 -2
- package/dist/src/providers/provider-auth-service.js +1 -1
- package/dist/src/providers/provider-disconnect-core.js +1 -1
- package/dist/src/providers/register-runtime-providers.js +1 -1
- package/dist/src/providers/xopc-cloud-catalog-coordinator.js +1 -1
- package/dist/src/realtime/runtime.js +6 -0
- package/dist/src/realtime/tickets.d.ts +1 -0
- package/dist/src/realtime/tickets.js +4 -0
- package/dist/src/remote-access/exposure-guards.js +2 -1
- package/dist/src/share/share-rate-limit.js +2 -1
- package/dist/src/share/share-url.js +2 -1
- package/dist/src/share/site-share-router.js +2 -1
- package/dist/src/storage/sqlite/device-access-repository.d.ts +7 -0
- package/dist/src/storage/sqlite/device-access-repository.js +22 -4
- package/dist/src/storage/sqlite/migrations/243_voice_opening_greetings.sql +12 -0
- package/dist/src/storage/sqlite/migrations/244_device_request_proofs.sql +8 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tasks/task-main-update-decision-service.js +2 -2
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/tunnel/broker-client.js +2 -0
- package/dist/src/tunnel/tunnel-config.js +2 -2
- package/dist/src/tunnel/tunnel-rate-limit.js +2 -1
- package/dist/src/utils/logger/redact.js +4 -1
- package/dist/src/voice/platform-streams.d.ts +6 -0
- package/dist/src/voice/platform-streams.js +43 -4
- package/dist/src/voice/realtime/agentEngine.js +138 -83
- package/dist/src/voice/realtime/engine.d.ts +6 -0
- package/dist/src/voice/realtime/opening-greeting.d.ts +12 -0
- package/dist/src/voice/realtime/opening-greeting.js +22 -0
- package/dist/src/voice/realtime/runtime.js +90 -1
- package/dist/src/voice/realtime/runtime.types.d.ts +4 -0
- package/dist/src/work-discovery/service.js +2 -2
- package/dist/src/workflows/service/workflow-run-service.js +1 -1
- package/package.json +3 -3
- package/dist/gateway/static/root/assets/management-settings-D_JQ6_X0.js +0 -1
- package/dist/gateway/static/root/assets/personal-page-BJn6UHAF.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-D114UdRI.js +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { TaskRepository, init_task_repository } from "./task-repository.js";
|
|
2
2
|
import { TaskRunRepository, init_task_run_repository } from "./task-run-repository.js";
|
|
3
|
-
import { extractAssistantText, getAssistantMessageErrorReason, stripCodeFences } from "../providers/model-response.js";
|
|
4
3
|
import { init_providers, resolveModel } from "../providers/index.js";
|
|
5
|
-
import { completeWithResolvedCredentials } from "../providers/model-call.js";
|
|
6
4
|
import { getPersonalAgentByConversation } from "../personal-agent/repository.js";
|
|
5
|
+
import { completeWithResolvedCredentials } from "../providers/model-call.js";
|
|
6
|
+
import { extractAssistantText, getAssistantMessageErrorReason, stripCodeFences } from "../providers/model-response.js";
|
|
7
7
|
import { TaskCollaborationRepository } from "./task-collaboration-repository.js";
|
|
8
8
|
//#region src/tasks/task-main-update-decision-service.ts
|
|
9
9
|
init_providers();
|
|
@@ -11,8 +11,8 @@ import { init_sqlite } from "../../storage/sqlite/index.js";
|
|
|
11
11
|
import { normalizeAgentId } from "../../agent-catalog/id.js";
|
|
12
12
|
import { init_agent_scope, listAgentEntries } from "../../agent/agent-scope.js";
|
|
13
13
|
import { AgentCatalogService } from "../../agent-catalog/service.js";
|
|
14
|
-
import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
|
|
15
14
|
import { init_paths } from "../../config/paths.js";
|
|
15
|
+
import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
|
|
16
16
|
import { init_agent_session_key, resolveAgentIdFromConversationId } from "../../routing/agent-session-key.js";
|
|
17
17
|
import { resolveEffectiveAgentProfile } from "../../config/agent-profile.js";
|
|
18
18
|
import { effectiveWorkspacePathForSession } from "../../session/session-workspace.js";
|
package/dist/src/tui/tui.js
CHANGED
|
@@ -2,8 +2,8 @@ import { version } from "../../package.js";
|
|
|
2
2
|
import { resolveStateDir, resolveXopcDatabasePath } from "../config/paths-state.js";
|
|
3
3
|
import { init_paths } from "../config/paths.js";
|
|
4
4
|
import { init_agent_session_key, normalizeAgentId } from "../routing/agent-session-key.js";
|
|
5
|
-
import { getAllModels, getAllProviders, getApiKey, init_providers, isProviderConfiguredSync } from "../providers/index.js";
|
|
6
5
|
import { resolveEffectiveAgentProfile } from "../config/agent-profile.js";
|
|
6
|
+
import { getAllModels, getAllProviders, getApiKey, init_providers, isProviderConfiguredSync } from "../providers/index.js";
|
|
7
7
|
import { isTerminalWorkflowRunStatus } from "../workflows/domain/run.js";
|
|
8
8
|
import "../workflows/domain/index.js";
|
|
9
9
|
import { ProjectTrustStore, hasTrustRequiringProjectResources } from "../project-trust/trust-store.js";
|
|
@@ -29,6 +29,8 @@ const registrationResponseSchema = z.object({
|
|
|
29
29
|
var TunnelBrokerClient = class {
|
|
30
30
|
constructor(baseUrl) {
|
|
31
31
|
this.baseUrl = baseUrl;
|
|
32
|
+
const url = new URL(baseUrl);
|
|
33
|
+
if (url.protocol !== "https:" || url.username || url.password) throw new Error("Tunnel broker requires HTTPS without URL credentials");
|
|
32
34
|
}
|
|
33
35
|
apiUrl(path) {
|
|
34
36
|
return `${this.baseUrl.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TunnelConfigSchema, TunnelConsentSchema, init_schema } from "../config/schema.js";
|
|
1
|
+
import { TunnelBrokerUrlSchema, TunnelConfigSchema, TunnelConsentSchema, init_schema } from "../config/schema.js";
|
|
2
2
|
import { getTunnelRegistrationSecretMeta, isMaskedTunnelSecretPatchValue } from "./env.js";
|
|
3
3
|
import { assertTunnelAutoStartAllowed, buildTunnelConsentRecord, hasValidTunnelConsent } from "./consent.js";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
init_schema();
|
|
7
7
|
const TunnelConfigPatchSchema = z.object({
|
|
8
8
|
enabled: z.boolean().optional(),
|
|
9
|
-
brokerUrl:
|
|
9
|
+
brokerUrl: TunnelBrokerUrlSchema.optional(),
|
|
10
10
|
registrationSecret: z.union([z.string(), z.null()]).optional(),
|
|
11
11
|
autoStart: z.boolean().optional(),
|
|
12
12
|
subdomain: z.string().optional(),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createLogger } from "../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../utils/logger.js";
|
|
3
3
|
import { buckets } from "../gateway/rate-limit/buckets.js";
|
|
4
|
-
import "../gateway/rate-limit/index.js";
|
|
4
|
+
import { init_rate_limit } from "../gateway/rate-limit/index.js";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
//#region src/tunnel/tunnel-rate-limit.ts
|
|
7
|
+
init_rate_limit();
|
|
7
8
|
init_logger();
|
|
8
9
|
const log = createLogger("TunnelRateLimit");
|
|
9
10
|
function tokenKey(token) {
|
|
@@ -62,8 +62,9 @@ var init_redact = __esmMin((() => {
|
|
|
62
62
|
KEEP_PREFIX_LENGTH = 6;
|
|
63
63
|
KEEP_SUFFIX_LENGTH = 4;
|
|
64
64
|
DEFAULT_REDACT_PATTERNS = [
|
|
65
|
+
/X-Xopc-Proof\s*[:=]\s*([A-Za-z0-9._-]+)/gi,
|
|
65
66
|
/\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*((?:\\?["'])?)([^\s"'\\]+)\1/gi,
|
|
66
|
-
/"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken|privateKey)"\s*:\s*"([^"]+)"/gi,
|
|
67
|
+
/"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken|privateKey|signature|x-xopc-proof)"\s*:\s*"([^"]+)"/gi,
|
|
67
68
|
/(?:__Host-xopc-session|xopc-local-session)=([A-Za-z0-9_-]{43})/g,
|
|
68
69
|
/Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
|
|
69
70
|
/\b(sk-[A-Za-z0-9_-]{8,})\b/gi,
|
|
@@ -88,6 +89,8 @@ var init_redact = __esmMin((() => {
|
|
|
88
89
|
"cookie",
|
|
89
90
|
"set-cookie",
|
|
90
91
|
"signedpayload",
|
|
92
|
+
"signature",
|
|
93
|
+
"x-xopc-proof",
|
|
91
94
|
"credential",
|
|
92
95
|
"credentials"
|
|
93
96
|
]);
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { StreamingSttOpenRequest, StreamingSttSession } from '../media-understanding/types.js';
|
|
2
2
|
import type { SpeechSynthesisStreamResult } from './tts/speech-provider-types.js';
|
|
3
|
+
export declare class PlatformVoiceError extends Error {
|
|
4
|
+
readonly status?: number;
|
|
5
|
+
readonly code?: string;
|
|
6
|
+
constructor(message: string, status?: number, code?: string);
|
|
7
|
+
get clientMessage(): string;
|
|
8
|
+
}
|
|
3
9
|
export declare function openPlatformStt(request: StreamingSttOpenRequest): Promise<StreamingSttSession>;
|
|
4
10
|
export declare function openPlatformTts(request: {
|
|
5
11
|
baseUrl: string;
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
//#region src/voice/platform-streams.ts
|
|
3
3
|
const MAX_BUFFERED_BYTES = 512 * 1024;
|
|
4
|
+
const MAX_ERROR_BODY_BYTES = 16 * 1024;
|
|
5
|
+
var PlatformVoiceError = class extends Error {
|
|
6
|
+
constructor(message, status, code) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = "PlatformVoiceError";
|
|
11
|
+
}
|
|
12
|
+
get clientMessage() {
|
|
13
|
+
if (this.code === "realtime_quota_workspace_budget") return "XOPC Cloud voice workspace budget is exhausted. Increase the workspace budget or choose another voice provider.";
|
|
14
|
+
if (this.status === 402 || this.code === "realtime_quota_user_credits") return "XOPC Cloud voice has insufficient Credit or quota. Check your Cloud balance and workspace budget, or choose another voice provider.";
|
|
15
|
+
if (this.status === 401 || this.status === 403) return "XOPC Cloud voice authorization failed. Sign in again and check your access to this voice model.";
|
|
16
|
+
if (this.status === 429 || this.code?.startsWith("realtime_quota_")) return "XOPC Cloud voice quota or rate limit reached. Try again later or choose another voice provider.";
|
|
17
|
+
return "XOPC Cloud voice is unavailable. Try again later or choose another voice provider.";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
4
20
|
function connect(url, token, signal, onEvent, onFailure) {
|
|
5
21
|
signal.throwIfAborted();
|
|
6
22
|
const socket = new WebSocket(url, {
|
|
@@ -30,15 +46,38 @@ function connect(url, token, signal, onEvent, onFailure) {
|
|
|
30
46
|
if (binary) throw new Error("Unexpected platform binary event");
|
|
31
47
|
const event = JSON.parse(raw.toString());
|
|
32
48
|
if (!event || typeof event.type !== "string") throw new Error("Invalid platform voice event");
|
|
33
|
-
if (event.type === "error") throw new
|
|
49
|
+
if (event.type === "error") throw new PlatformVoiceError(event.error?.message ?? "Platform voice failed", void 0, event.error?.code);
|
|
34
50
|
onEvent(event);
|
|
35
51
|
} catch (error) {
|
|
36
52
|
fail(error instanceof Error ? error : /* @__PURE__ */ new Error("Invalid platform voice event"));
|
|
37
53
|
}
|
|
38
54
|
});
|
|
39
55
|
socket.on("unexpected-response", (_request, response) => {
|
|
40
|
-
response.
|
|
41
|
-
|
|
56
|
+
const fallback = `Platform voice rejected connection (${response.statusCode})`;
|
|
57
|
+
let bytes = 0;
|
|
58
|
+
const chunks = [];
|
|
59
|
+
const reject = () => {
|
|
60
|
+
let detail;
|
|
61
|
+
try {
|
|
62
|
+
detail = JSON.parse(Buffer.concat(chunks).toString("utf8"))?.error;
|
|
63
|
+
} catch {}
|
|
64
|
+
const code = typeof detail?.code === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(detail.code) ? detail.code : void 0;
|
|
65
|
+
const message = typeof detail?.message === "string" ? detail.message.slice(0, 1e3) : void 0;
|
|
66
|
+
fail(new PlatformVoiceError(`${fallback}${code ? `: ${code}` : ""}${message ? `: ${message}` : ""}`, response.statusCode, code));
|
|
67
|
+
};
|
|
68
|
+
response.on("data", (chunk) => {
|
|
69
|
+
bytes += chunk.length;
|
|
70
|
+
if (bytes > MAX_ERROR_BODY_BYTES) {
|
|
71
|
+
chunks.length = 0;
|
|
72
|
+
reject();
|
|
73
|
+
response.destroy();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
chunks.push(chunk);
|
|
77
|
+
});
|
|
78
|
+
response.once("end", reject);
|
|
79
|
+
response.once("error", reject);
|
|
80
|
+
response.once("aborted", reject);
|
|
42
81
|
});
|
|
43
82
|
socket.on("error", fail);
|
|
44
83
|
socket.on("close", () => fail(/* @__PURE__ */ new Error("Platform voice disconnected before completion")));
|
|
@@ -265,4 +304,4 @@ async function openPlatformTts(request) {
|
|
|
265
304
|
};
|
|
266
305
|
}
|
|
267
306
|
//#endregion
|
|
268
|
-
export { openPlatformStt, openPlatformTts };
|
|
307
|
+
export { PlatformVoiceError, openPlatformStt, openPlatformTts };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
|
+
import { PlatformVoiceError } from "../platform-streams.js";
|
|
3
4
|
import { prepareSpeechStreamSession, speakStream } from "../tts/speak-core.js";
|
|
4
5
|
import { AudioPlaybackWindow } from "./audio-playback-window.js";
|
|
5
6
|
import { TurnCoordinator } from "./turnPolicy.js";
|
|
@@ -43,6 +44,7 @@ function createAgentVoiceEngine(options) {
|
|
|
43
44
|
let turnReceivedAtMs;
|
|
44
45
|
let taskUpdateQueued = false;
|
|
45
46
|
let lastUserSpeechAt = 0;
|
|
47
|
+
let openingInputSeen = false;
|
|
46
48
|
const turn = new TurnCoordinator(claim.silenceDurationMs, (text, decision, turnId) => {
|
|
47
49
|
if (closed || muted) return;
|
|
48
50
|
const receivedAtMs = turnReceivedAtMs ?? Date.now();
|
|
@@ -103,13 +105,15 @@ function createAgentVoiceEngine(options) {
|
|
|
103
105
|
};
|
|
104
106
|
activeResponse = void 0;
|
|
105
107
|
clearTimeout(response.firstPhraseTimer);
|
|
108
|
+
clearTimeout(response.openingTimer);
|
|
109
|
+
if (response.opening && response.opening.result !== "expired") response.opening.result = "interrupted";
|
|
106
110
|
response.abortController.abort(reason);
|
|
107
111
|
send("response.cancelled", {
|
|
108
112
|
responseId: response.id,
|
|
109
113
|
reason
|
|
110
114
|
});
|
|
111
115
|
const conversationId = claim.request.conversationId;
|
|
112
|
-
if (conversationId && reason !== "session_closed") interruptionWrites = interruptionWrites.then(() => options.runtime.recordInterruption({
|
|
116
|
+
if (conversationId && !response.opening && reason !== "session_closed") interruptionWrites = interruptionWrites.then(() => options.runtime.recordInterruption({
|
|
113
117
|
conversationId,
|
|
114
118
|
responseId: response.id,
|
|
115
119
|
reason,
|
|
@@ -221,6 +225,12 @@ function createAgentVoiceEngine(options) {
|
|
|
221
225
|
for (const frame of response.pcm.push(item.value)) {
|
|
222
226
|
await response.playback.reserve(frame.byteLength, response.abortController.signal);
|
|
223
227
|
if (activeResponse !== response || closed) return;
|
|
228
|
+
if (response.opening && options.runtime.sessionBusy(claim.request.conversationId)) {
|
|
229
|
+
cancelActiveResponse("barge_in");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
response.firstFrameSent = true;
|
|
233
|
+
clearTimeout(response.openingTimer);
|
|
224
234
|
options.sendAudio(response.id, frame);
|
|
225
235
|
}
|
|
226
236
|
}
|
|
@@ -304,17 +314,19 @@ function createAgentVoiceEngine(options) {
|
|
|
304
314
|
if (response.speechWorker) prefetchNextSpeech(response);
|
|
305
315
|
else startSpeechWorker(response);
|
|
306
316
|
}
|
|
307
|
-
async function runAssistantTurn(text, turnId, updateClientMessageId, receivedAtMs = Date.now(), committedAtMs = receivedAtMs) {
|
|
317
|
+
async function runAssistantTurn(text, turnId, updateClientMessageId, receivedAtMs = Date.now(), committedAtMs = receivedAtMs, opening) {
|
|
308
318
|
if (!claim.tts || !claim.request.conversationId || !text.trim() || closed) return false;
|
|
309
319
|
cancelActiveResponse("barge_in");
|
|
310
320
|
const response = {
|
|
311
|
-
id: `resp_${crypto.randomUUID()}`,
|
|
321
|
+
id: opening?.responseId ?? `resp_${crypto.randomUUID()}`,
|
|
322
|
+
...opening ? { opening } : {},
|
|
312
323
|
playback: new AudioPlaybackWindow(),
|
|
313
324
|
abortController: new AbortController(),
|
|
314
325
|
segmenter: new SpeakableSegmenter(AGENT_TTS_MAX_SEGMENT_CHARACTERS, AGENT_TTS_MIN_SEGMENT_CHARACTERS, 0),
|
|
315
326
|
text: "",
|
|
316
327
|
audibleText: "",
|
|
317
328
|
audioStarted: false,
|
|
329
|
+
firstFrameSent: false,
|
|
318
330
|
speechQueue: [],
|
|
319
331
|
queuedSpeechCharacters: 0,
|
|
320
332
|
startedAt: Date.now(),
|
|
@@ -328,13 +340,19 @@ function createAgentVoiceEngine(options) {
|
|
|
328
340
|
const taskUpdateExposed = () => Boolean(updateClientMessageId && response.text.trim());
|
|
329
341
|
activeResponse = response;
|
|
330
342
|
send("response.created", { responseId: response.id });
|
|
343
|
+
if (opening) response.openingTimer = setTimeout(() => {
|
|
344
|
+
if (activeResponse === response) {
|
|
345
|
+
opening.result = "expired";
|
|
346
|
+
cancelActiveResponse("client_cancelled");
|
|
347
|
+
}
|
|
348
|
+
}, Math.max(0, opening.deadlineAt - Date.now()));
|
|
331
349
|
try {
|
|
332
|
-
const
|
|
350
|
+
const speechOpening = prepareSpeechStreamSession(claim.tts.config, {
|
|
333
351
|
appConfig: claim.config,
|
|
334
352
|
signal: response.abortController.signal,
|
|
335
353
|
allowFallback: false
|
|
336
354
|
});
|
|
337
|
-
if (
|
|
355
|
+
if (speechOpening) response.preparedSpeech = speechOpening.then((session) => {
|
|
338
356
|
log.info({
|
|
339
357
|
sessionId: claim.sessionId,
|
|
340
358
|
responseId: response.id,
|
|
@@ -346,86 +364,96 @@ function createAgentVoiceEngine(options) {
|
|
|
346
364
|
}, (error) => ({ error }));
|
|
347
365
|
await interruptionWrites;
|
|
348
366
|
if (response.abortController.signal.aborted || closed) return taskUpdateExposed();
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
367
|
+
if (opening) {
|
|
368
|
+
response.text = text;
|
|
369
|
+
response.firstTextAtMs = Date.now();
|
|
370
|
+
send("response.text.delta", {
|
|
371
|
+
responseId: response.id,
|
|
372
|
+
delta: text
|
|
373
|
+
});
|
|
374
|
+
queuePhrases(response, [text]);
|
|
375
|
+
} else {
|
|
376
|
+
if (!claim.conversationSessionId) throw new Error("Conversation identity is unavailable");
|
|
377
|
+
const task = await options.runtime.agentBroker.delegate({
|
|
378
|
+
text,
|
|
379
|
+
turnId,
|
|
380
|
+
conversationId: claim.request.conversationId,
|
|
381
|
+
expectedTranscriptId: claim.conversationSessionId,
|
|
382
|
+
signal: response.abortController.signal,
|
|
383
|
+
clientMessageId: updateClientMessageId ?? `voice:${claim.conversationSessionId}:${claim.sessionId}:${inputGeneration}:${turnId}`,
|
|
384
|
+
...updateClientMessageId ? { origin: {
|
|
385
|
+
type: "system",
|
|
386
|
+
source: "task_update"
|
|
387
|
+
} } : {}
|
|
388
|
+
});
|
|
389
|
+
response.taskId = task.taskId;
|
|
390
|
+
send("task.created", {
|
|
391
|
+
responseId: response.id,
|
|
392
|
+
taskId: task.taskId
|
|
393
|
+
});
|
|
394
|
+
for await (const event of task.events) {
|
|
395
|
+
if (activeResponse !== response || response.abortController.signal.aborted) return taskUpdateExposed();
|
|
396
|
+
if (event.type === "assistant_delta" || event.type === "tool_end" && event.payload?.toolName !== "clarify") response.awaitingClarification = false;
|
|
397
|
+
if (event.type === "assistant_delta" && typeof event.payload?.delta === "string") {
|
|
398
|
+
if (response.text.length + event.payload.delta.length > 32e3) throw new Error("Voice response text limit reached");
|
|
399
|
+
if (!response.firstTextSeen && event.payload.delta.trim()) {
|
|
400
|
+
response.firstTextSeen = true;
|
|
401
|
+
response.firstTextAtMs = Date.now();
|
|
402
|
+
log.info({
|
|
403
|
+
sessionId: claim.sessionId,
|
|
404
|
+
responseId: response.id,
|
|
405
|
+
conversationId: claim.request.conversationId,
|
|
406
|
+
phase: "first_response_text",
|
|
407
|
+
latencyMs: Date.now() - response.startedAt,
|
|
408
|
+
inputToTextMs: Date.now() - response.inputReceivedAtMs,
|
|
409
|
+
turnDecisionMs: Math.max(0, response.turnCommittedAtMs - response.inputReceivedAtMs),
|
|
410
|
+
queueMs: Math.max(0, response.startedAt - response.turnCommittedAtMs)
|
|
411
|
+
}, "Realtime voice first response text ready");
|
|
412
|
+
}
|
|
413
|
+
response.text += event.payload.delta;
|
|
414
|
+
send("response.text.delta", {
|
|
377
415
|
responseId: response.id,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
},
|
|
416
|
+
delta: event.payload.delta
|
|
417
|
+
});
|
|
418
|
+
queuePhrases(response, response.segmenter.push(event.payload.delta));
|
|
419
|
+
if (response.firstPhraseAtMs === void 0 && response.firstPhraseTimer === void 0) response.firstPhraseTimer = setTimeout(() => {
|
|
420
|
+
response.firstPhraseTimer = void 0;
|
|
421
|
+
if (activeResponse === response && !response.abortController.signal.aborted) queuePhrases(response, response.segmenter.flushFirstPhrase());
|
|
422
|
+
}, FIRST_PHRASE_WAIT_MS);
|
|
385
423
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
424
|
+
if (event.type === "tool_start") queuePhrases(response, response.segmenter.flush());
|
|
425
|
+
if ((event.type === "tool_start" || event.type === "tool_end") && typeof event.payload?.toolCallId === "string" && typeof event.payload?.toolName === "string") send("task.activity", {
|
|
426
|
+
taskId: task.taskId,
|
|
427
|
+
toolCallId: event.payload.toolCallId.slice(0, 160),
|
|
428
|
+
toolName: event.payload.toolName.slice(0, 256),
|
|
429
|
+
status: event.type === "tool_start" ? "running" : event.payload.status === "error" ? "failed" : "completed"
|
|
390
430
|
});
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
response.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
431
|
+
if (event.type === "run_end" || event.type === "stream_end") {
|
|
432
|
+
const status = event.payload?.status;
|
|
433
|
+
if (!response.taskDone) {
|
|
434
|
+
response.taskDone = true;
|
|
435
|
+
send("task.done", {
|
|
436
|
+
taskId: task.taskId,
|
|
437
|
+
status: status === "cancelled" ? "cancelled" : status === "suspended" ? "suspended" : status === "error" ? "failed" : "completed"
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (event.type === "clarify_request" && typeof event.payload?.requestId === "string" && typeof event.payload?.question === "string") {
|
|
442
|
+
response.awaitingClarification = true;
|
|
443
|
+
send("response.clarification", {
|
|
444
|
+
responseId: response.id,
|
|
445
|
+
requestId: event.payload.requestId.slice(0, 160),
|
|
446
|
+
kind: event.payload.kind === "approval" ? "approval" : "input",
|
|
447
|
+
question: event.payload.question.slice(0, 8e3),
|
|
448
|
+
...Array.isArray(event.payload.choices) ? { choices: event.payload.choices.filter((choice) => typeof choice === "string").slice(0, 20).map((choice) => choice.slice(0, 1e3)) } : {},
|
|
449
|
+
...typeof event.payload.suggestedAnswer === "string" ? { suggestedAnswer: event.payload.suggestedAnswer.slice(0, 1e3) } : {},
|
|
450
|
+
version: typeof event.payload.version === "number" ? event.payload.version : 1,
|
|
451
|
+
createdAt: typeof event.payload.createdAt === "number" ? event.payload.createdAt : Date.now(),
|
|
452
|
+
...typeof event.payload.expiresAt === "number" ? { expiresAt: event.payload.expiresAt } : {}
|
|
411
453
|
});
|
|
412
454
|
}
|
|
455
|
+
if (event.type === "error") throw new Error(typeof event.payload?.message === "string" ? event.payload.message : "Agent response failed");
|
|
413
456
|
}
|
|
414
|
-
if (event.type === "clarify_request" && typeof event.payload?.requestId === "string" && typeof event.payload?.question === "string") {
|
|
415
|
-
response.awaitingClarification = true;
|
|
416
|
-
send("response.clarification", {
|
|
417
|
-
responseId: response.id,
|
|
418
|
-
requestId: event.payload.requestId.slice(0, 160),
|
|
419
|
-
kind: event.payload.kind === "approval" ? "approval" : "input",
|
|
420
|
-
question: event.payload.question.slice(0, 8e3),
|
|
421
|
-
...Array.isArray(event.payload.choices) ? { choices: event.payload.choices.filter((choice) => typeof choice === "string").slice(0, 20).map((choice) => choice.slice(0, 1e3)) } : {},
|
|
422
|
-
...typeof event.payload.suggestedAnswer === "string" ? { suggestedAnswer: event.payload.suggestedAnswer.slice(0, 1e3) } : {},
|
|
423
|
-
version: typeof event.payload.version === "number" ? event.payload.version : 1,
|
|
424
|
-
createdAt: typeof event.payload.createdAt === "number" ? event.payload.createdAt : Date.now(),
|
|
425
|
-
...typeof event.payload.expiresAt === "number" ? { expiresAt: event.payload.expiresAt } : {}
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
if (event.type === "error") throw new Error(typeof event.payload?.message === "string" ? event.payload.message : "Agent response failed");
|
|
429
457
|
}
|
|
430
458
|
if (activeResponse !== response || response.abortController.signal.aborted || closed) return taskUpdateExposed();
|
|
431
459
|
queuePhrases(response, response.segmenter.flush());
|
|
@@ -436,6 +464,12 @@ function createAgentVoiceEngine(options) {
|
|
|
436
464
|
if (finalFrame) {
|
|
437
465
|
await response.playback.reserve(finalFrame.byteLength, response.abortController.signal);
|
|
438
466
|
if (activeResponse !== response || closed) return taskUpdateExposed();
|
|
467
|
+
if (response.opening && options.runtime.sessionBusy(claim.request.conversationId)) {
|
|
468
|
+
cancelActiveResponse("barge_in");
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
response.firstFrameSent = true;
|
|
472
|
+
clearTimeout(response.openingTimer);
|
|
439
473
|
options.sendAudio(response.id, finalFrame);
|
|
440
474
|
}
|
|
441
475
|
await response.playback.drain(response.abortController.signal);
|
|
@@ -451,6 +485,7 @@ function createAgentVoiceEngine(options) {
|
|
|
451
485
|
finishReason: response.audioStarted ? "completed" : "text_only",
|
|
452
486
|
audio: response.audioStarted
|
|
453
487
|
});
|
|
488
|
+
if (opening) opening.result = response.audioStarted ? "completed" : "failed";
|
|
454
489
|
activeResponse = void 0;
|
|
455
490
|
return !updateClientMessageId || taskUpdateExposed();
|
|
456
491
|
} catch (error) {
|
|
@@ -461,7 +496,7 @@ function createAgentVoiceEngine(options) {
|
|
|
461
496
|
responseId: response.id,
|
|
462
497
|
...updateClientMessageId ? { clientMessageId: updateClientMessageId } : {}
|
|
463
498
|
}, updateClientMessageId ? "Proactive voice task update deferred" : "Realtime voice response failed");
|
|
464
|
-
if (!updateClientMessageId) send("session.error", {
|
|
499
|
+
if (!updateClientMessageId && !opening) send("session.error", {
|
|
465
500
|
code: "RESPONSE_FAILED",
|
|
466
501
|
message: "Voice response failed",
|
|
467
502
|
recoverable: true
|
|
@@ -483,6 +518,7 @@ function createAgentVoiceEngine(options) {
|
|
|
483
518
|
if (activeResponse === response) activeResponse = void 0;
|
|
484
519
|
return taskUpdateExposed();
|
|
485
520
|
} finally {
|
|
521
|
+
clearTimeout(response.openingTimer);
|
|
486
522
|
clearTimeout(response.firstPhraseTimer);
|
|
487
523
|
await waitForSpeech(response);
|
|
488
524
|
response.abortController.abort("response_finished");
|
|
@@ -538,14 +574,15 @@ function createAgentVoiceEngine(options) {
|
|
|
538
574
|
if (event.type === "error") {
|
|
539
575
|
turn.reset();
|
|
540
576
|
echoCandidates.clear();
|
|
577
|
+
const message = event.error instanceof PlatformVoiceError ? event.error.clientMessage : "Streaming transcription failed";
|
|
541
578
|
log.warn({
|
|
542
579
|
err: event.error,
|
|
543
580
|
sessionId: claim.sessionId,
|
|
544
581
|
provider: stt.route.provider
|
|
545
|
-
},
|
|
582
|
+
}, `Realtime STT failed: ${message}`);
|
|
546
583
|
send("session.error", {
|
|
547
584
|
code: "PROVIDER_ERROR",
|
|
548
|
-
message
|
|
585
|
+
message,
|
|
549
586
|
recoverable: false
|
|
550
587
|
});
|
|
551
588
|
options.onClose("provider_error", true);
|
|
@@ -553,6 +590,8 @@ function createAgentVoiceEngine(options) {
|
|
|
553
590
|
}
|
|
554
591
|
if (muted || finalizedUtterances.has(event.utteranceId)) return;
|
|
555
592
|
if (event.type === "speech_started") {
|
|
593
|
+
openingInputSeen = true;
|
|
594
|
+
if (activeResponse?.opening && !activeResponse.firstFrameSent) cancelActiveResponse("barge_in");
|
|
556
595
|
lastUserSpeechAt = Date.now();
|
|
557
596
|
echoCandidates.remember(event.utteranceId, activeResponse?.audioStarted ? activeResponse.audibleText : recentPlayback && recentPlayback.expiresAt >= Date.now() ? recentPlayback.text : void 0);
|
|
558
597
|
if (claim.request.purpose === "conversation") {
|
|
@@ -578,6 +617,7 @@ function createAgentVoiceEngine(options) {
|
|
|
578
617
|
if (event.type === "transcript_final") {
|
|
579
618
|
lastUserSpeechAt = Date.now();
|
|
580
619
|
const text = event.text.trim();
|
|
620
|
+
if (text) openingInputSeen = true;
|
|
581
621
|
if (!text) {
|
|
582
622
|
if (claim.request.purpose === "conversation") bufferFinal(event.utteranceId, "");
|
|
583
623
|
return;
|
|
@@ -613,7 +653,7 @@ function createAgentVoiceEngine(options) {
|
|
|
613
653
|
turn.reset();
|
|
614
654
|
return;
|
|
615
655
|
}
|
|
616
|
-
if (claim.config.voice?.realtime?.bargeIn) cancelActiveResponse("barge_in");
|
|
656
|
+
if (activeResponse?.opening || claim.config.voice?.realtime?.bargeIn) cancelActiveResponse("barge_in");
|
|
617
657
|
bufferFinal(event.utteranceId, text);
|
|
618
658
|
}
|
|
619
659
|
}
|
|
@@ -654,6 +694,21 @@ function createAgentVoiceEngine(options) {
|
|
|
654
694
|
}
|
|
655
695
|
return {
|
|
656
696
|
isConversationIdle,
|
|
697
|
+
async offerOpeningGreeting(input) {
|
|
698
|
+
if (openingInputSeen || !canOfferTaskUpdateInput() || options.runtime.sessionBusy(claim.request.conversationId)) return "skipped";
|
|
699
|
+
const opening = {
|
|
700
|
+
...input,
|
|
701
|
+
result: "failed"
|
|
702
|
+
};
|
|
703
|
+
const attempt = conversationTail.then(async () => {
|
|
704
|
+
if (Date.now() >= input.deadlineAt) return "expired";
|
|
705
|
+
if (openingInputSeen || !canOfferTaskUpdateInput() || options.runtime.sessionBusy(claim.request.conversationId)) return "skipped";
|
|
706
|
+
await runAssistantTurn(input.text, input.responseId, void 0, input.deadlineAt - 2e3, Date.now(), opening);
|
|
707
|
+
return opening.result;
|
|
708
|
+
});
|
|
709
|
+
conversationTail = attempt.then(() => {}, () => {});
|
|
710
|
+
return attempt;
|
|
711
|
+
},
|
|
657
712
|
canOfferTaskUpdate() {
|
|
658
713
|
return !taskUpdateQueued && canOfferTaskUpdateInput();
|
|
659
714
|
},
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import type { OpeningGreetingResult } from './opening-greeting.js';
|
|
1
2
|
import type { VoiceServerEvent } from '@xopcai/realtime-protocol/voice';
|
|
2
3
|
export type VoiceEventSink = <T extends VoiceServerEvent['type']>(type: T, payload: Extract<VoiceServerEvent, {
|
|
3
4
|
type: T;
|
|
4
5
|
}>['payload']) => void;
|
|
5
6
|
export interface VoiceEngine {
|
|
6
7
|
start(): Promise<void>;
|
|
8
|
+
offerOpeningGreeting?(input: {
|
|
9
|
+
responseId: string;
|
|
10
|
+
text: string;
|
|
11
|
+
deadlineAt: number;
|
|
12
|
+
}): Promise<OpeningGreetingResult>;
|
|
7
13
|
appendAudio(bytes: Uint8Array): void;
|
|
8
14
|
setInputMuted(muted: boolean): void | Promise<void>;
|
|
9
15
|
commit(): Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type OpeningGreetingResult = 'completed' | 'skipped' | 'interrupted' | 'failed' | 'expired';
|
|
2
|
+
export interface OpeningGreetingIdentity {
|
|
3
|
+
principalId: string;
|
|
4
|
+
conversationId: string;
|
|
5
|
+
logicalCallId: string;
|
|
6
|
+
responseId: string;
|
|
7
|
+
}
|
|
8
|
+
/** Only an explicitly provided name is used; never infer identity from an account ID. */
|
|
9
|
+
export declare function openingGreetingText(callName: string | null | undefined, language: 'zh' | 'en'): string;
|
|
10
|
+
/** Claim before output: crashes and interrupted calls must never replay the greeting. */
|
|
11
|
+
export declare function claimOpeningGreeting(identity: OpeningGreetingIdentity, now?: number): boolean;
|
|
12
|
+
export declare function finishOpeningGreeting(identity: OpeningGreetingIdentity, state: OpeningGreetingResult, reason?: string): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getSqliteDatabase, init_transaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
//#region src/voice/realtime/opening-greeting.ts
|
|
3
|
+
init_transaction();
|
|
4
|
+
/** Only an explicitly provided name is used; never infer identity from an account ID. */
|
|
5
|
+
function openingGreetingText(callName, language) {
|
|
6
|
+
const name = callName?.replace(/[\p{Cc}\p{Cf}]/gu, "").replace(/\s+/g, " ").trim().slice(0, 40);
|
|
7
|
+
return language === "en" ? name ? `Hi, ${name}. I'm here.` : "Hi, I'm here." : name ? `嗨,${name},我在。` : "嗨,我在。";
|
|
8
|
+
}
|
|
9
|
+
/** Claim before output: crashes and interrupted calls must never replay the greeting. */
|
|
10
|
+
function claimOpeningGreeting(identity, now = Date.now()) {
|
|
11
|
+
const db = getSqliteDatabase();
|
|
12
|
+
db.prepare("DELETE FROM voice_opening_greetings WHERE created_at < ?").run(now - 864e5);
|
|
13
|
+
return db.prepare(`INSERT OR IGNORE INTO voice_opening_greetings
|
|
14
|
+
(principal_id, conversation_id, logical_call_id, response_id, state, created_at, updated_at)
|
|
15
|
+
VALUES (?, ?, ?, ?, 'claimed', ?, ?)`).run(identity.principalId, identity.conversationId, identity.logicalCallId, identity.responseId, now, now).changes === 1;
|
|
16
|
+
}
|
|
17
|
+
function finishOpeningGreeting(identity, state, reason) {
|
|
18
|
+
getSqliteDatabase().prepare(`UPDATE voice_opening_greetings SET state = ?, reason = ?, updated_at = ?
|
|
19
|
+
WHERE principal_id = ? AND conversation_id = ? AND logical_call_id = ? AND state = 'claimed'`).run(state, reason ?? null, Date.now(), identity.principalId, identity.conversationId, identity.logicalCallId);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { claimOpeningGreeting, finishOpeningGreeting, openingGreetingText };
|