@absolutejs/voice 0.0.22-beta.61 → 0.0.22-beta.63
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.d.ts +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +388 -8
- package/dist/toolContract.d.ts +61 -0
- package/dist/toolRuntime.d.ts +50 -0
- package/package.json +1 -1
package/dist/agent.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { VoiceOnTurnObjectHandler, VoiceRouteResult, VoiceSessionHandle, VoiceSessionRecord, VoiceTurnRecord } from './types';
|
|
2
2
|
import type { VoiceTraceEventStore } from './trace';
|
|
3
|
+
import type { VoiceToolRuntime } from './toolRuntime';
|
|
3
4
|
export type VoiceAgentMessageRole = 'assistant' | 'system' | 'tool' | 'user';
|
|
4
5
|
export type VoiceAgentMessage = {
|
|
5
6
|
content: string;
|
|
@@ -86,6 +87,7 @@ export type VoiceAgentOptions<TContext = unknown, TSession extends VoiceSessionR
|
|
|
86
87
|
turn: VoiceTurnRecord;
|
|
87
88
|
}) => Promise<string | undefined> | string | undefined);
|
|
88
89
|
trace?: VoiceTraceEventStore;
|
|
90
|
+
toolRuntime?: VoiceToolRuntime<TContext, TSession, TResult>;
|
|
89
91
|
tools?: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
|
|
90
92
|
};
|
|
91
93
|
export type VoiceAgentSquadOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { compareVoiceEvalBaseline, createVoiceFileEvalBaselineStore, createVoice
|
|
|
7
7
|
export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult } from './workflowContract';
|
|
8
8
|
export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceSessions, summarizeVoiceSessionReplay } from './sessionReplay';
|
|
9
9
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
|
|
10
|
+
export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRuntime';
|
|
11
|
+
export { createVoiceToolContract, createVoiceToolRuntimeContractDefaults, runVoiceToolContract } from './toolContract';
|
|
10
12
|
export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
|
|
11
13
|
export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
|
|
12
14
|
export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
|
|
@@ -53,6 +55,8 @@ export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQua
|
|
|
53
55
|
export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind } from './resilienceRoutes';
|
|
54
56
|
export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
|
|
55
57
|
export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
|
|
58
|
+
export type { VoiceToolRetryDelay, VoiceToolRuntime, VoiceToolRuntimeExecuteInput, VoiceToolRuntimeOptions, VoiceToolRuntimeResult } from './toolRuntime';
|
|
59
|
+
export type { VoiceToolContractCase, VoiceToolContractCaseReport, VoiceToolContractDefinition, VoiceToolContractExpectation, VoiceToolContractIssue, VoiceToolContractReport } from './toolContract';
|
|
56
60
|
export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
|
|
57
61
|
export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
|
|
58
62
|
export type { VoiceOutcomeRecipe, VoiceOutcomeRecipeName, VoiceOutcomeRecipeOptions } from './outcomeRecipes';
|
package/dist/index.js
CHANGED
|
@@ -5627,16 +5627,34 @@ var createVoiceAgent = (options) => {
|
|
|
5627
5627
|
}
|
|
5628
5628
|
try {
|
|
5629
5629
|
const toolStartedAt = Date.now();
|
|
5630
|
-
const
|
|
5630
|
+
const runtimeResult = options.toolRuntime ? await options.toolRuntime.execute({
|
|
5631
|
+
api: input.api,
|
|
5632
|
+
args: toolCall.args,
|
|
5633
|
+
context: input.context,
|
|
5634
|
+
session: input.session,
|
|
5635
|
+
tool,
|
|
5636
|
+
toolCallId: toolCall.id,
|
|
5637
|
+
turn: input.turn
|
|
5638
|
+
}) : undefined;
|
|
5639
|
+
if (runtimeResult?.status === "error") {
|
|
5640
|
+
throw new Error(runtimeResult.error);
|
|
5641
|
+
}
|
|
5642
|
+
const result = runtimeResult?.result ?? await tool.execute({
|
|
5631
5643
|
api: input.api,
|
|
5632
5644
|
args: toolCall.args,
|
|
5633
5645
|
context: input.context,
|
|
5634
5646
|
session: input.session,
|
|
5635
5647
|
turn: input.turn
|
|
5636
5648
|
});
|
|
5637
|
-
const content = tool.resultToMessage?.(result) ?? formatToolResult(result);
|
|
5649
|
+
const content = runtimeResult?.content ?? tool.resultToMessage?.(result) ?? formatToolResult(result);
|
|
5638
5650
|
toolResults.push({
|
|
5639
5651
|
content,
|
|
5652
|
+
metadata: runtimeResult ? {
|
|
5653
|
+
attempts: runtimeResult.attempts,
|
|
5654
|
+
elapsedMs: runtimeResult.elapsedMs,
|
|
5655
|
+
idempotencyKey: runtimeResult.idempotencyKey,
|
|
5656
|
+
timedOut: runtimeResult.timedOut
|
|
5657
|
+
} : undefined,
|
|
5640
5658
|
result,
|
|
5641
5659
|
status: "ok",
|
|
5642
5660
|
toolCallId: toolCall.id,
|
|
@@ -5646,7 +5664,11 @@ var createVoiceAgent = (options) => {
|
|
|
5646
5664
|
agentId: options.id,
|
|
5647
5665
|
event: {
|
|
5648
5666
|
elapsedMs: Date.now() - toolStartedAt,
|
|
5667
|
+
runtimeElapsedMs: runtimeResult?.elapsedMs,
|
|
5668
|
+
attempts: runtimeResult?.attempts,
|
|
5669
|
+
idempotencyKey: runtimeResult?.idempotencyKey,
|
|
5649
5670
|
status: "ok",
|
|
5671
|
+
timedOut: runtimeResult?.timedOut,
|
|
5650
5672
|
toolCallId: toolCall.id,
|
|
5651
5673
|
toolName: tool.name
|
|
5652
5674
|
},
|
|
@@ -9728,6 +9750,359 @@ var createVoiceWorkflowContractHandler = (input) => {
|
|
|
9728
9750
|
return result;
|
|
9729
9751
|
};
|
|
9730
9752
|
};
|
|
9753
|
+
// src/toolRuntime.ts
|
|
9754
|
+
var toErrorMessage4 = (error) => error instanceof Error ? error.message : String(error);
|
|
9755
|
+
var sleep4 = (ms) => new Promise((resolve2) => {
|
|
9756
|
+
setTimeout(resolve2, ms);
|
|
9757
|
+
});
|
|
9758
|
+
var formatToolResult2 = (result) => {
|
|
9759
|
+
if (typeof result === "string") {
|
|
9760
|
+
return result;
|
|
9761
|
+
}
|
|
9762
|
+
if (result === undefined) {
|
|
9763
|
+
return "";
|
|
9764
|
+
}
|
|
9765
|
+
try {
|
|
9766
|
+
return JSON.stringify(result);
|
|
9767
|
+
} catch {
|
|
9768
|
+
return String(result);
|
|
9769
|
+
}
|
|
9770
|
+
};
|
|
9771
|
+
var withTimeout = async (promise, timeoutMs) => {
|
|
9772
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
9773
|
+
return promise;
|
|
9774
|
+
}
|
|
9775
|
+
let timer;
|
|
9776
|
+
try {
|
|
9777
|
+
return await Promise.race([
|
|
9778
|
+
promise,
|
|
9779
|
+
new Promise((_, reject) => {
|
|
9780
|
+
timer = setTimeout(() => {
|
|
9781
|
+
reject(new Error(`Voice tool timed out after ${timeoutMs}ms`));
|
|
9782
|
+
}, timeoutMs);
|
|
9783
|
+
})
|
|
9784
|
+
]);
|
|
9785
|
+
} finally {
|
|
9786
|
+
if (timer) {
|
|
9787
|
+
clearTimeout(timer);
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
};
|
|
9791
|
+
var resolveRetryDelay = (retryDelayMs, input) => typeof retryDelayMs === "function" ? retryDelayMs(input) : retryDelayMs ?? 0;
|
|
9792
|
+
var createVoiceToolRuntime = (options = {}) => {
|
|
9793
|
+
const inFlight = new Map;
|
|
9794
|
+
const completed = new Map;
|
|
9795
|
+
const execute = async (input) => {
|
|
9796
|
+
const idempotencyKey = options.idempotencyKey?.({
|
|
9797
|
+
args: input.args,
|
|
9798
|
+
context: input.context,
|
|
9799
|
+
session: input.session,
|
|
9800
|
+
toolCallId: input.toolCallId,
|
|
9801
|
+
toolName: input.tool.name,
|
|
9802
|
+
turn: input.turn
|
|
9803
|
+
});
|
|
9804
|
+
if (idempotencyKey) {
|
|
9805
|
+
const cached = completed.get(idempotencyKey);
|
|
9806
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
9807
|
+
return cached.result;
|
|
9808
|
+
}
|
|
9809
|
+
if (cached) {
|
|
9810
|
+
completed.delete(idempotencyKey);
|
|
9811
|
+
}
|
|
9812
|
+
const existing = inFlight.get(idempotencyKey);
|
|
9813
|
+
if (existing) {
|
|
9814
|
+
return await existing;
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
const runPromise = (async () => {
|
|
9818
|
+
const startedAt = Date.now();
|
|
9819
|
+
const maxRetries = Math.max(0, options.maxRetries ?? 0);
|
|
9820
|
+
let attempts = 0;
|
|
9821
|
+
let lastError;
|
|
9822
|
+
let timedOut = false;
|
|
9823
|
+
for (let retry = 0;retry <= maxRetries; retry += 1) {
|
|
9824
|
+
attempts = retry + 1;
|
|
9825
|
+
try {
|
|
9826
|
+
const result = await withTimeout(Promise.resolve(input.tool.execute({
|
|
9827
|
+
api: input.api,
|
|
9828
|
+
args: input.args,
|
|
9829
|
+
context: input.context,
|
|
9830
|
+
session: input.session,
|
|
9831
|
+
turn: input.turn
|
|
9832
|
+
})), options.timeoutMs);
|
|
9833
|
+
const elapsedMs2 = Date.now() - startedAt;
|
|
9834
|
+
const content = input.tool.resultToMessage?.(result) ?? formatToolResult2(result);
|
|
9835
|
+
const runtimeResult2 = {
|
|
9836
|
+
attempts,
|
|
9837
|
+
content,
|
|
9838
|
+
elapsedMs: elapsedMs2,
|
|
9839
|
+
idempotencyKey,
|
|
9840
|
+
result,
|
|
9841
|
+
status: "ok",
|
|
9842
|
+
timedOut,
|
|
9843
|
+
toolCallId: input.toolCallId,
|
|
9844
|
+
toolName: input.tool.name
|
|
9845
|
+
};
|
|
9846
|
+
await options.trace?.append({
|
|
9847
|
+
at: Date.now(),
|
|
9848
|
+
payload: {
|
|
9849
|
+
attempts,
|
|
9850
|
+
elapsedMs: elapsedMs2,
|
|
9851
|
+
idempotencyKey,
|
|
9852
|
+
status: "ok",
|
|
9853
|
+
toolCallId: input.toolCallId,
|
|
9854
|
+
toolName: input.tool.name
|
|
9855
|
+
},
|
|
9856
|
+
scenarioId: input.session.scenarioId,
|
|
9857
|
+
sessionId: input.session.id,
|
|
9858
|
+
turnId: input.turn.id,
|
|
9859
|
+
type: "agent.tool"
|
|
9860
|
+
});
|
|
9861
|
+
return runtimeResult2;
|
|
9862
|
+
} catch (error) {
|
|
9863
|
+
lastError = error;
|
|
9864
|
+
timedOut ||= toErrorMessage4(error).includes("timed out");
|
|
9865
|
+
if (retry < maxRetries) {
|
|
9866
|
+
const delay = resolveRetryDelay(options.retryDelayMs, {
|
|
9867
|
+
attempt: attempts,
|
|
9868
|
+
error,
|
|
9869
|
+
toolName: input.tool.name
|
|
9870
|
+
});
|
|
9871
|
+
if (delay > 0) {
|
|
9872
|
+
await sleep4(delay);
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
const elapsedMs = Date.now() - startedAt;
|
|
9878
|
+
const runtimeResult = {
|
|
9879
|
+
attempts,
|
|
9880
|
+
elapsedMs,
|
|
9881
|
+
error: toErrorMessage4(lastError),
|
|
9882
|
+
idempotencyKey,
|
|
9883
|
+
status: "error",
|
|
9884
|
+
timedOut,
|
|
9885
|
+
toolCallId: input.toolCallId,
|
|
9886
|
+
toolName: input.tool.name
|
|
9887
|
+
};
|
|
9888
|
+
await options.trace?.append({
|
|
9889
|
+
at: Date.now(),
|
|
9890
|
+
payload: {
|
|
9891
|
+
attempts,
|
|
9892
|
+
elapsedMs,
|
|
9893
|
+
error: runtimeResult.error,
|
|
9894
|
+
idempotencyKey,
|
|
9895
|
+
status: "error",
|
|
9896
|
+
timedOut,
|
|
9897
|
+
toolCallId: input.toolCallId,
|
|
9898
|
+
toolName: input.tool.name
|
|
9899
|
+
},
|
|
9900
|
+
scenarioId: input.session.scenarioId,
|
|
9901
|
+
sessionId: input.session.id,
|
|
9902
|
+
turnId: input.turn.id,
|
|
9903
|
+
type: "agent.tool"
|
|
9904
|
+
});
|
|
9905
|
+
return runtimeResult;
|
|
9906
|
+
})();
|
|
9907
|
+
if (idempotencyKey) {
|
|
9908
|
+
inFlight.set(idempotencyKey, runPromise);
|
|
9909
|
+
runPromise.then((result) => {
|
|
9910
|
+
if (options.idempotencyTtlMs && options.idempotencyTtlMs > 0) {
|
|
9911
|
+
completed.set(idempotencyKey, {
|
|
9912
|
+
expiresAt: Date.now() + options.idempotencyTtlMs,
|
|
9913
|
+
result
|
|
9914
|
+
});
|
|
9915
|
+
}
|
|
9916
|
+
}).finally(() => {
|
|
9917
|
+
inFlight.delete(idempotencyKey);
|
|
9918
|
+
});
|
|
9919
|
+
}
|
|
9920
|
+
return await runPromise;
|
|
9921
|
+
};
|
|
9922
|
+
return {
|
|
9923
|
+
execute,
|
|
9924
|
+
wrapTool: (tool) => ({
|
|
9925
|
+
...tool,
|
|
9926
|
+
execute: async (input) => {
|
|
9927
|
+
const result = await execute({
|
|
9928
|
+
...input,
|
|
9929
|
+
tool
|
|
9930
|
+
});
|
|
9931
|
+
if (result.status === "error") {
|
|
9932
|
+
throw new Error(result.error);
|
|
9933
|
+
}
|
|
9934
|
+
return result.result;
|
|
9935
|
+
}
|
|
9936
|
+
})
|
|
9937
|
+
};
|
|
9938
|
+
};
|
|
9939
|
+
var createVoiceToolIdempotencyKey = (input) => {
|
|
9940
|
+
const args = typeof input.args === "string" ? input.args : JSON.stringify(input.args ?? null);
|
|
9941
|
+
return [
|
|
9942
|
+
input.sessionId,
|
|
9943
|
+
input.turnId,
|
|
9944
|
+
input.toolCallId ?? "no-call-id",
|
|
9945
|
+
input.toolName,
|
|
9946
|
+
args
|
|
9947
|
+
].join(":");
|
|
9948
|
+
};
|
|
9949
|
+
// src/toolContract.ts
|
|
9950
|
+
var createDefaultSession = (contractId, caseId) => createVoiceSessionRecord(`tool-contract-${contractId}-${caseId}`);
|
|
9951
|
+
var createDefaultTurn = (caseId) => ({
|
|
9952
|
+
committedAt: Date.now(),
|
|
9953
|
+
id: `turn-${caseId}`,
|
|
9954
|
+
text: `Run tool contract case ${caseId}.`,
|
|
9955
|
+
transcripts: []
|
|
9956
|
+
});
|
|
9957
|
+
var defaultApi = {};
|
|
9958
|
+
var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
9959
|
+
var evaluateExpectation = (input) => {
|
|
9960
|
+
const issues = [];
|
|
9961
|
+
const expect = input.expect;
|
|
9962
|
+
if (!expect) {
|
|
9963
|
+
return issues;
|
|
9964
|
+
}
|
|
9965
|
+
if (expect.expectStatus && input.status !== expect.expectStatus) {
|
|
9966
|
+
issues.push({
|
|
9967
|
+
caseId: input.caseId,
|
|
9968
|
+
code: "tool.status_mismatch",
|
|
9969
|
+
message: `Expected ${expect.expectStatus}, saw ${input.status}.`
|
|
9970
|
+
});
|
|
9971
|
+
}
|
|
9972
|
+
if (typeof expect.expectedAttempts === "number" && input.attempts !== expect.expectedAttempts) {
|
|
9973
|
+
issues.push({
|
|
9974
|
+
caseId: input.caseId,
|
|
9975
|
+
code: "tool.attempt_mismatch",
|
|
9976
|
+
message: `Expected ${expect.expectedAttempts} attempts, saw ${input.attempts}.`
|
|
9977
|
+
});
|
|
9978
|
+
}
|
|
9979
|
+
if (expect.expectedResult !== undefined && !sameJSON(input.result, expect.expectedResult)) {
|
|
9980
|
+
issues.push({
|
|
9981
|
+
caseId: input.caseId,
|
|
9982
|
+
code: "tool.result_mismatch",
|
|
9983
|
+
message: "Tool result did not match expected result."
|
|
9984
|
+
});
|
|
9985
|
+
}
|
|
9986
|
+
if (expect.expectedErrorIncludes && !input.error?.includes(expect.expectedErrorIncludes)) {
|
|
9987
|
+
issues.push({
|
|
9988
|
+
caseId: input.caseId,
|
|
9989
|
+
code: "tool.error_mismatch",
|
|
9990
|
+
message: `Expected error to include ${expect.expectedErrorIncludes}.`
|
|
9991
|
+
});
|
|
9992
|
+
}
|
|
9993
|
+
if (typeof expect.expectTimedOut === "boolean" && input.timedOut !== expect.expectTimedOut) {
|
|
9994
|
+
issues.push({
|
|
9995
|
+
caseId: input.caseId,
|
|
9996
|
+
code: "tool.timeout_mismatch",
|
|
9997
|
+
message: `Expected timedOut=${String(expect.expectTimedOut)}, saw ${String(input.timedOut)}.`
|
|
9998
|
+
});
|
|
9999
|
+
}
|
|
10000
|
+
if (typeof expect.maxElapsedMs === "number" && input.elapsedMs > expect.maxElapsedMs) {
|
|
10001
|
+
issues.push({
|
|
10002
|
+
caseId: input.caseId,
|
|
10003
|
+
code: "tool.elapsed_exceeded",
|
|
10004
|
+
message: `Expected elapsed <= ${expect.maxElapsedMs}ms, saw ${input.elapsedMs}ms.`
|
|
10005
|
+
});
|
|
10006
|
+
}
|
|
10007
|
+
return issues;
|
|
10008
|
+
};
|
|
10009
|
+
var runVoiceToolContract = async (definition) => {
|
|
10010
|
+
const cases = [];
|
|
10011
|
+
for (const testCase of definition.cases) {
|
|
10012
|
+
const session = testCase.session ?? createDefaultSession(definition.id, testCase.id);
|
|
10013
|
+
const turn = testCase.turn ?? createDefaultTurn(testCase.id);
|
|
10014
|
+
const context = testCase.context ?? {};
|
|
10015
|
+
const runtimeOptions = {
|
|
10016
|
+
...definition.defaultRuntime,
|
|
10017
|
+
...testCase.runtime
|
|
10018
|
+
};
|
|
10019
|
+
const runtime = createVoiceToolRuntime(runtimeOptions);
|
|
10020
|
+
const toolCall = {
|
|
10021
|
+
args: testCase.args,
|
|
10022
|
+
id: testCase.toolCallId ?? testCase.id,
|
|
10023
|
+
name: definition.tool.name
|
|
10024
|
+
};
|
|
10025
|
+
const executeOnce = () => runtime.execute({
|
|
10026
|
+
api: defaultApi,
|
|
10027
|
+
args: toolCall.args,
|
|
10028
|
+
context,
|
|
10029
|
+
session,
|
|
10030
|
+
tool: definition.tool,
|
|
10031
|
+
toolCallId: toolCall.id,
|
|
10032
|
+
turn
|
|
10033
|
+
});
|
|
10034
|
+
const result = await executeOnce();
|
|
10035
|
+
let issues2 = evaluateExpectation({
|
|
10036
|
+
attempts: result.attempts,
|
|
10037
|
+
caseId: testCase.id,
|
|
10038
|
+
elapsedMs: result.elapsedMs,
|
|
10039
|
+
error: result.error,
|
|
10040
|
+
expect: testCase.expect,
|
|
10041
|
+
result: result.result,
|
|
10042
|
+
status: result.status,
|
|
10043
|
+
timedOut: result.timedOut
|
|
10044
|
+
});
|
|
10045
|
+
if (testCase.expect?.expectIdempotent) {
|
|
10046
|
+
const second = await executeOnce();
|
|
10047
|
+
if (second.result !== result.result && !sameJSON(second.result, result.result)) {
|
|
10048
|
+
issues2.push({
|
|
10049
|
+
caseId: testCase.id,
|
|
10050
|
+
code: "tool.idempotency_result_mismatch",
|
|
10051
|
+
message: "Repeated idempotent execution returned a different result."
|
|
10052
|
+
});
|
|
10053
|
+
}
|
|
10054
|
+
if (second.idempotencyKey !== result.idempotencyKey) {
|
|
10055
|
+
issues2.push({
|
|
10056
|
+
caseId: testCase.id,
|
|
10057
|
+
code: "tool.idempotency_key_mismatch",
|
|
10058
|
+
message: "Repeated idempotent execution used a different idempotency key."
|
|
10059
|
+
});
|
|
10060
|
+
}
|
|
10061
|
+
}
|
|
10062
|
+
cases.push({
|
|
10063
|
+
attempts: result.attempts,
|
|
10064
|
+
caseId: testCase.id,
|
|
10065
|
+
elapsedMs: result.elapsedMs,
|
|
10066
|
+
error: result.error,
|
|
10067
|
+
issues: issues2,
|
|
10068
|
+
label: testCase.label,
|
|
10069
|
+
pass: issues2.length === 0,
|
|
10070
|
+
status: result.status,
|
|
10071
|
+
timedOut: result.timedOut
|
|
10072
|
+
});
|
|
10073
|
+
}
|
|
10074
|
+
const issues = cases.flatMap((testCase) => testCase.issues);
|
|
10075
|
+
return {
|
|
10076
|
+
cases,
|
|
10077
|
+
contractId: definition.id,
|
|
10078
|
+
issues,
|
|
10079
|
+
pass: issues.length === 0,
|
|
10080
|
+
toolName: definition.tool.name
|
|
10081
|
+
};
|
|
10082
|
+
};
|
|
10083
|
+
var createVoiceToolContract = (definition) => ({
|
|
10084
|
+
assert: async () => {
|
|
10085
|
+
const report = await runVoiceToolContract(definition);
|
|
10086
|
+
if (!report.pass) {
|
|
10087
|
+
throw new Error(`Voice tool contract ${definition.id} failed: ${report.issues.map((issue) => issue.message).join(" ")}`);
|
|
10088
|
+
}
|
|
10089
|
+
return report;
|
|
10090
|
+
},
|
|
10091
|
+
definition,
|
|
10092
|
+
run: () => runVoiceToolContract(definition)
|
|
10093
|
+
});
|
|
10094
|
+
var createVoiceToolRuntimeContractDefaults = () => ({
|
|
10095
|
+
idempotencyKey: ({ args, session, toolCallId, toolName, turn }) => createVoiceToolIdempotencyKey({
|
|
10096
|
+
args,
|
|
10097
|
+
sessionId: session.id,
|
|
10098
|
+
toolCallId,
|
|
10099
|
+
toolName,
|
|
10100
|
+
turnId: turn.id
|
|
10101
|
+
}),
|
|
10102
|
+
idempotencyTtlMs: 60000,
|
|
10103
|
+
maxRetries: 1,
|
|
10104
|
+
timeoutMs: 5000
|
|
10105
|
+
});
|
|
9731
10106
|
// src/fileStore.ts
|
|
9732
10107
|
import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
9733
10108
|
import { join } from "path";
|
|
@@ -10193,7 +10568,7 @@ var getMessageToolCalls = (message) => {
|
|
|
10193
10568
|
return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
|
|
10194
10569
|
};
|
|
10195
10570
|
var createHTTPError = (provider, response) => new Error(`${provider} voice assistant model failed: HTTP ${response.status}`);
|
|
10196
|
-
var
|
|
10571
|
+
var sleep5 = (ms) => new Promise((resolve2) => {
|
|
10197
10572
|
setTimeout(resolve2, ms);
|
|
10198
10573
|
});
|
|
10199
10574
|
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -10902,7 +11277,7 @@ var createGeminiVoiceAssistantModel = (options) => {
|
|
|
10902
11277
|
break;
|
|
10903
11278
|
}
|
|
10904
11279
|
const retryAfter = Number(response.headers.get("retry-after"));
|
|
10905
|
-
await
|
|
11280
|
+
await sleep5(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt);
|
|
10906
11281
|
}
|
|
10907
11282
|
if (!response) {
|
|
10908
11283
|
throw new Error("Gemini voice assistant model failed: no response");
|
|
@@ -10957,7 +11332,7 @@ var getTimeoutMs = (options, provider) => {
|
|
|
10957
11332
|
const timeoutMs = options.providerProfiles?.[provider]?.timeoutMs ?? options.timeoutMs;
|
|
10958
11333
|
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
|
|
10959
11334
|
};
|
|
10960
|
-
var
|
|
11335
|
+
var withTimeout2 = async (input) => {
|
|
10961
11336
|
if (!input.timeoutMs) {
|
|
10962
11337
|
return input.run();
|
|
10963
11338
|
}
|
|
@@ -11141,7 +11516,7 @@ var createVoiceSTTProviderRouter = (options) => {
|
|
|
11141
11516
|
}
|
|
11142
11517
|
const startedAt = Date.now();
|
|
11143
11518
|
try {
|
|
11144
|
-
const session = await
|
|
11519
|
+
const session = await withTimeout2({
|
|
11145
11520
|
kind: "stt",
|
|
11146
11521
|
operation: "open",
|
|
11147
11522
|
provider,
|
|
@@ -11218,7 +11593,7 @@ var createVoiceTTSProviderRouter = (options) => {
|
|
|
11218
11593
|
throw new Error(`Voice TTS provider ${provider} is not configured.`);
|
|
11219
11594
|
}
|
|
11220
11595
|
const startedAt = Date.now();
|
|
11221
|
-
const session = await
|
|
11596
|
+
const session = await withTimeout2({
|
|
11222
11597
|
kind: "tts",
|
|
11223
11598
|
operation: "open",
|
|
11224
11599
|
provider,
|
|
@@ -11297,7 +11672,7 @@ var createVoiceTTSProviderRouter = (options) => {
|
|
|
11297
11672
|
}
|
|
11298
11673
|
const startedAt = Date.now();
|
|
11299
11674
|
try {
|
|
11300
|
-
await
|
|
11675
|
+
await withTimeout2({
|
|
11301
11676
|
kind: "tts",
|
|
11302
11677
|
operation: "send",
|
|
11303
11678
|
provider,
|
|
@@ -14116,6 +14491,7 @@ export {
|
|
|
14116
14491
|
startVoiceOpsTask,
|
|
14117
14492
|
shapeTelephonyAssistantText,
|
|
14118
14493
|
selectVoiceTraceEventsForPrune,
|
|
14494
|
+
runVoiceToolContract,
|
|
14119
14495
|
runVoiceSessionEvals,
|
|
14120
14496
|
runVoiceScenarioFixtureEvals,
|
|
14121
14497
|
runVoiceScenarioEvals,
|
|
@@ -14194,6 +14570,10 @@ export {
|
|
|
14194
14570
|
createVoiceTraceHTTPSink,
|
|
14195
14571
|
createVoiceTraceEventId,
|
|
14196
14572
|
createVoiceTraceEvent,
|
|
14573
|
+
createVoiceToolRuntimeContractDefaults,
|
|
14574
|
+
createVoiceToolRuntime,
|
|
14575
|
+
createVoiceToolIdempotencyKey,
|
|
14576
|
+
createVoiceToolContract,
|
|
14197
14577
|
createVoiceTaskUpdatedEvent,
|
|
14198
14578
|
createVoiceTaskSLABreachedEvent,
|
|
14199
14579
|
createVoiceTaskCreatedEvent,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { VoiceAgentTool } from './agent';
|
|
2
|
+
import { type VoiceToolRuntimeOptions } from './toolRuntime';
|
|
3
|
+
import type { VoiceSessionRecord, VoiceTurnRecord } from './types';
|
|
4
|
+
export type VoiceToolContractExpectation = {
|
|
5
|
+
expectedAttempts?: number;
|
|
6
|
+
expectedErrorIncludes?: string;
|
|
7
|
+
expectedResult?: unknown;
|
|
8
|
+
expectIdempotent?: boolean;
|
|
9
|
+
expectStatus?: 'error' | 'ok';
|
|
10
|
+
expectTimedOut?: boolean;
|
|
11
|
+
maxElapsedMs?: number;
|
|
12
|
+
};
|
|
13
|
+
export type VoiceToolContractCase<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown> = {
|
|
14
|
+
args: TArgs;
|
|
15
|
+
context?: TContext;
|
|
16
|
+
expect?: VoiceToolContractExpectation;
|
|
17
|
+
id: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
runtime?: VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>;
|
|
20
|
+
session?: TSession;
|
|
21
|
+
toolCallId?: string;
|
|
22
|
+
turn?: VoiceTurnRecord;
|
|
23
|
+
};
|
|
24
|
+
export type VoiceToolContractDefinition<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown> = {
|
|
25
|
+
cases: Array<VoiceToolContractCase<TContext, TSession, TArgs, TToolResult, TRouteResult>>;
|
|
26
|
+
defaultRuntime?: VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>;
|
|
27
|
+
description?: string;
|
|
28
|
+
id: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
tool: VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>;
|
|
31
|
+
};
|
|
32
|
+
export type VoiceToolContractIssue = {
|
|
33
|
+
caseId: string;
|
|
34
|
+
code: string;
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
export type VoiceToolContractCaseReport = {
|
|
38
|
+
attempts: number;
|
|
39
|
+
caseId: string;
|
|
40
|
+
elapsedMs: number;
|
|
41
|
+
error?: string;
|
|
42
|
+
issues: VoiceToolContractIssue[];
|
|
43
|
+
label?: string;
|
|
44
|
+
pass: boolean;
|
|
45
|
+
status: 'error' | 'ok';
|
|
46
|
+
timedOut: boolean;
|
|
47
|
+
};
|
|
48
|
+
export type VoiceToolContractReport = {
|
|
49
|
+
cases: VoiceToolContractCaseReport[];
|
|
50
|
+
contractId: string;
|
|
51
|
+
issues: VoiceToolContractIssue[];
|
|
52
|
+
pass: boolean;
|
|
53
|
+
toolName: string;
|
|
54
|
+
};
|
|
55
|
+
export declare const runVoiceToolContract: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown>(definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>) => Promise<VoiceToolContractReport>;
|
|
56
|
+
export declare const createVoiceToolContract: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown>(definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>) => {
|
|
57
|
+
assert: () => Promise<VoiceToolContractReport>;
|
|
58
|
+
definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>;
|
|
59
|
+
run: () => Promise<VoiceToolContractReport>;
|
|
60
|
+
};
|
|
61
|
+
export declare const createVoiceToolRuntimeContractDefaults: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown>() => VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { VoiceAgentTool, VoiceAgentToolResult } from './agent';
|
|
2
|
+
import type { VoiceSessionHandle, VoiceSessionRecord, VoiceTurnRecord } from './types';
|
|
3
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
4
|
+
export type VoiceToolRetryDelay = number | ((input: {
|
|
5
|
+
attempt: number;
|
|
6
|
+
error: unknown;
|
|
7
|
+
toolName: string;
|
|
8
|
+
}) => number);
|
|
9
|
+
export type VoiceToolRuntimeOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown> = {
|
|
10
|
+
idempotencyKey?: (input: {
|
|
11
|
+
args: unknown;
|
|
12
|
+
context: TContext;
|
|
13
|
+
session: TSession;
|
|
14
|
+
toolCallId?: string;
|
|
15
|
+
toolName: string;
|
|
16
|
+
turn: VoiceTurnRecord;
|
|
17
|
+
}) => string | undefined;
|
|
18
|
+
idempotencyTtlMs?: number;
|
|
19
|
+
maxRetries?: number;
|
|
20
|
+
retryDelayMs?: VoiceToolRetryDelay;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
trace?: VoiceTraceEventStore;
|
|
23
|
+
};
|
|
24
|
+
export type VoiceToolRuntimeExecuteInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown> = {
|
|
25
|
+
api: VoiceSessionHandle<TContext, TSession, TRouteResult>;
|
|
26
|
+
args: TArgs;
|
|
27
|
+
context: TContext;
|
|
28
|
+
session: TSession;
|
|
29
|
+
tool: VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>;
|
|
30
|
+
toolCallId?: string;
|
|
31
|
+
turn: VoiceTurnRecord;
|
|
32
|
+
};
|
|
33
|
+
export type VoiceToolRuntimeResult<TToolResult = unknown> = VoiceAgentToolResult<TToolResult> & {
|
|
34
|
+
attempts: number;
|
|
35
|
+
elapsedMs: number;
|
|
36
|
+
idempotencyKey?: string;
|
|
37
|
+
timedOut: boolean;
|
|
38
|
+
};
|
|
39
|
+
export type VoiceToolRuntime<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown> = {
|
|
40
|
+
execute: <TArgs = Record<string, unknown>, TToolResult = unknown>(input: VoiceToolRuntimeExecuteInput<TContext, TSession, TArgs, TToolResult, TRouteResult>) => Promise<VoiceToolRuntimeResult<TToolResult>>;
|
|
41
|
+
wrapTool: <TArgs = Record<string, unknown>, TToolResult = unknown>(tool: VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>) => VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>;
|
|
42
|
+
};
|
|
43
|
+
export declare const createVoiceToolRuntime: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown>(options?: VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>) => VoiceToolRuntime<TContext, TSession, TRouteResult>;
|
|
44
|
+
export declare const createVoiceToolIdempotencyKey: (input: {
|
|
45
|
+
args: unknown;
|
|
46
|
+
sessionId: string;
|
|
47
|
+
toolCallId?: string;
|
|
48
|
+
toolName: string;
|
|
49
|
+
turnId: string;
|
|
50
|
+
}) => string;
|