@kuralle-syrinx/cf-agents 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -97,6 +97,7 @@ comes with —
97
97
  | `onDelegateQuery` / `onDelegateResult` | G2 observability hooks around the reasoner run. `onDelegateResult` is self-contained (`{ query, answer, durationMs, grounded, toolId?, toolName?, turnId, sessionId, connection }`) — the one hook for logging/persisting grounded Q&A pairs. Throwing never affects the call. |
98
98
  | `durableHistory` | G4 durable session state over the Agent's SQLite (default `true`). Set `false` for ephemeral pre-G4 behavior. |
99
99
  | `delayCueAfterMs` | G3: ms before a pending tool call fires the `tool_call_delayed` ("still working") cue. 0 disables. Default 2000. |
100
+ | `backgroundAudio` | `{ ambient?, thinking?, duckWhileSpeaking? }` — looped ambient bed + thinking loop (raw mono PCM16 sources), mixed (ducked) under assistant speech; on the `"twilio"` transport the bed also fills between-turn gaps as comfort noise. Thinking follows the G3 cues. |
100
101
  | `inputSampleRateHz` / `outputSampleRateHz` | Edge audio rates (default 16000). |
101
102
  | `resumeWindowMs` | How long a dropped connection can resume its session. |
102
103
  | `sessionId` | `(request, agentName) => string`. Defaults to the `?sessionId=` query param (so a reconnecting client can resume), else a per-connection random id. (Not the Agent name — concurrent connections to one instance must not share a session.) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuralle-syrinx/cf-agents",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,13 +12,13 @@
12
12
  "./r2-recorder": "./src/r2-recorder.ts"
13
13
  },
14
14
  "dependencies": {
15
- "@kuralle-syrinx/core": "4.0.0",
16
- "@kuralle-syrinx/aisdk": "4.0.0",
17
- "@kuralle-syrinx/kuralle": "4.0.0",
18
- "@kuralle-syrinx/server-websocket": "4.0.0",
19
- "@kuralle-syrinx/ws": "4.0.0",
20
- "@kuralle-syrinx/realtime": "4.0.0",
21
- "@kuralle-syrinx/recorder": "4.0.0"
15
+ "@kuralle-syrinx/core": "4.1.0",
16
+ "@kuralle-syrinx/aisdk": "4.1.0",
17
+ "@kuralle-syrinx/server-websocket": "4.1.0",
18
+ "@kuralle-syrinx/ws": "4.1.0",
19
+ "@kuralle-syrinx/recorder": "4.1.0",
20
+ "@kuralle-syrinx/realtime": "4.1.0",
21
+ "@kuralle-syrinx/kuralle": "4.1.0"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "agents": ">=0.14.0 <1.0.0"
@@ -89,6 +89,13 @@ export interface CascadedPipeline<Env> {
89
89
  * (engine default 7000). Set it when a provider-endpointed cascade tunes this (e.g. Deepgram at 3500).
90
90
  */
91
91
  readonly sttForceFinalizeTimeoutMs?: number;
92
+ /**
93
+ * Speculative generation: start the reasoner on an eager end-of-turn signal
94
+ * (`eos.interim` — Deepgram Flux `eager_eot_threshold`, smart-turn interim) and
95
+ * commit/discard when the endpoint confirms. Trades extra LLM calls for
96
+ * parallelizing LLM TTFT with endpoint confirmation. @default false
97
+ */
98
+ readonly speculative?: boolean;
92
99
  }
93
100
 
94
101
  export type VoicePipeline<Env> = RealtimePipeline<Env> | CascadedPipeline<Env>;
@@ -163,6 +170,7 @@ export function buildVoiceSession<Env>(
163
170
  ...(wiring.reasonerSessionStore
164
171
  ? { sessionStore: wiring.reasonerSessionStore, sessionId: ctx.sessionId }
165
172
  : {}),
173
+ ...(pipeline.speculative ? { speculative: true } : {}),
166
174
  }),
167
175
  );
168
176
  session.registerPlugin("tts", tts.plugin);
package/src/with-voice.ts CHANGED
@@ -19,6 +19,7 @@
19
19
  import type { Agent, Connection, ConnectionContext, WSMessage } from "agents";
20
20
  import {
21
21
  runVoiceEdgeWebSocketConnection,
22
+ type BackgroundAudioConfig,
22
23
  type EdgeRecorder,
23
24
  } from "@kuralle-syrinx/server-websocket/edge";
24
25
  import { runTwilioEdgeWebSocketConnection } from "@kuralle-syrinx/server-websocket/edge-twilio";
@@ -118,6 +119,14 @@ export interface WithVoiceOptions<Env> {
118
119
  readonly reasoner?: (env: Env, ctx: VoicePipelineContext) => Reasoner | Promise<Reasoner>;
119
120
  /** Optional per-call recorder (e.g. an R2-backed `EdgeRecorder`). Applies to the `"edge"` transport. */
120
121
  readonly recorder?: (env: Env, ctx: VoicePipelineContext) => EdgeRecorder | undefined;
122
+ /**
123
+ * Ambient/thinking bed mixed (ducked) under assistant speech on both transports.
124
+ * On the `"twilio"` transport the bed also fills the gaps between turns as
125
+ * comfort noise — pure digital silence on a phone line reads as "the call died".
126
+ * The thinking loop follows the G3 tool-call cues. Sources are raw mono PCM16
127
+ * (e.g. `ffmpeg -i office.mp3 -ac 1 -ar 16000 -f s16le office.pcm`).
128
+ */
129
+ readonly backgroundAudio?: BackgroundAudioConfig;
121
130
  /**
122
131
  * Fired when the front model starts a delegate tool call, before the reasoner runs — the seam
123
132
  * for a deterministic latency-masking preamble / "thinking" earcon. Throwing here never affects
@@ -416,6 +425,7 @@ export function withVoice<Env, TBase extends AgentLike>(
416
425
  ? { engineSampleRateHz: options.inputSampleRateHz }
417
426
  : {}),
418
427
  ...(options.resumeWindowMs !== undefined ? { resumeWindowMs: options.resumeWindowMs } : {}),
428
+ ...(options.backgroundAudio ? { backgroundAudio: options.backgroundAudio } : {}),
419
429
  }).catch(onRunnerSettled);
420
430
  return;
421
431
  }
@@ -441,6 +451,7 @@ export function withVoice<Env, TBase extends AgentLike>(
441
451
  ? { outputSampleRateHz: options.outputSampleRateHz }
442
452
  : {}),
443
453
  ...(options.resumeWindowMs !== undefined ? { resumeWindowMs: options.resumeWindowMs } : {}),
454
+ ...(options.backgroundAudio ? { backgroundAudio: options.backgroundAudio } : {}),
444
455
  createSession,
445
456
  }).catch(onRunnerSettled);
446
457
  }