@vellumai/assistant 0.12.2-dev.202609181315.9aaf787 → 0.12.2-dev.202609181515.7e76c92
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/docs/voice-input-diagnostics.md +8 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/openapi.yaml +41 -0
- package/package.json +1 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
- package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
- package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
- package/src/agent/loop.ts +22 -29
- package/src/api/events/tool-result.ts +1 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
- package/src/cli/lib/bundled-marketplace.json +70 -33
- package/src/cli/lib/plugin-marketplace.ts +8 -0
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/schemas/services.ts +3 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +1 -1
- package/src/daemon/conversation-surface-snapshots.ts +135 -0
- package/src/daemon/message-types/web-activity.ts +2 -1
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +105 -4
- package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
- package/src/live-voice/live-voice-session.ts +23 -4
- package/src/live-voice/microphone-idle-gate.ts +56 -0
- package/src/oauth/provider-serializer.ts +7 -0
- package/src/oauth/seed-providers.ts +30 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
- package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
- package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
- package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
- package/src/plugins/defaults/task-progress/package.json +14 -0
- package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
- package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +9 -0
- package/src/providers/anthropic/client.ts +10 -1
- package/src/providers/anthropic-gateway-shared.ts +6 -0
- package/src/providers/gemini/client.ts +25 -8
- package/src/providers/jev/client.ts +4 -3
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +45 -0
- package/src/providers/openai/chat-completions-provider.ts +49 -15
- package/src/providers/openai/responses-provider.ts +23 -1
- package/src/providers/search-provider-catalog.ts +12 -0
- package/src/providers/types.ts +5 -0
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +6 -0
- package/src/tools/network/__tests__/web-search.test.ts +111 -0
- package/src/tools/network/web-search.ts +242 -4
- package/src/util/errors.ts +13 -3
|
@@ -30,6 +30,7 @@ Search the assistant log for `Live voice input diagnostics` or `voice_input_`. A
|
|
|
30
30
|
- `voice_input_window` summarizes each approximately five-second arrival window: received audio duration, zero-signal duration, duration-weighted mean amplitude, minimum/maximum chunk mean amplitude, classified speech/silence/echo duration, and maximum arrival gap. Window emission requires incoming audio; `voice_input_closed` flushes a partial window.
|
|
31
31
|
- The same window includes `sttSubmittedChunks`, `sttSubmittedAudioMs`, and `sttMaxSubmissionGapMs`. These measure handoff to the transcription adapter, not provider receipt. A probe can buffer audio before submission, and the final chunk of an arrival window can be submitted in the next window. Compare sustained windows to identify input that arrives normally but stops reaching transcription.
|
|
32
32
|
- `voice_input_provider_turn_start` and `voice_input_provider_turn_end` record provider boundaries alongside local VAD state. End records include confidence, the provider's `trigger` (`model`, `timeout`, or `manual`), decoded audio position in seconds from stream start, and elapsed time since locally detected speech. Missing provider fields are null. These records describe received decisions, including ones ignored as stale or after fallback; correlate with the turn metrics to identify the boundary that committed the request.
|
|
33
|
+
- `voice_input_microphone_gate_changed` records when the transcription audio gate closes or reopens. `microphoneGateClosed` is included with the other input diagnostics. Closing replaces room audio with silence; it does not commit a transcript or stop local interruption detection.
|
|
33
34
|
- `voice_input_barge_in_armed`, `voice_input_barge_in_fired`, and `voice_input_barge_in_expired` distinguish a candidate from an accepted or expired interruption. Fired events include accumulated speech duration, guard resets, and a bounded trace of the preceding two seconds (at most 40 input chunks).
|
|
34
35
|
- Each trace entry captures the amplitude and gate/echo state before classification, and classified durations, remaining probe buffer, and correlation result after classification. The classifier can hold a probe and release multiple older chunks together, so a row's classified duration can differ from its received duration. A null correlation means no comparison ran, not a measured zero correlation.
|
|
35
36
|
- `voice_input_speech_routed` binds the speech generation to the input turn that actually receives its audio. At onset, the prior spent cycle may still be installed, so use this routing record for correlation rather than assuming the onset's `inputTurnId` is the new utterance.
|
|
@@ -41,6 +42,12 @@ These are local diagnostic logs, not new analytics events or voice protocol fiel
|
|
|
41
42
|
|
|
42
43
|
## Flux pause handling
|
|
43
44
|
|
|
44
|
-
When Flux owns turn detection,
|
|
45
|
+
When Flux owns turn detection, microphone audio passes through for one second of received audio after the last locally detected speech. After that, the input gate replaces room audio with digital silence while the microphone and local interruption detector keep listening. The cutoff is measured in PCM samples, so network batching does not change it. Flux continues receiving elapsed silence and owns the turn boundary; closing this gate does not release the question.
|
|
46
|
+
|
|
47
|
+
While closed, the gate holds the most recent 200 ms before deciding whether to send those samples as silence or as the lead-in to resumed speech. On speech onset, it forwards that lead-in and the new speech exactly once. The provider feed can lag input by up to 200 ms while idle, then catches up on speech onset. Confirmed playback echo is replaced with silence before entering this buffer. Local VAD still owns barge-in, and idle audio is not accumulated into request recordings. Manual capture and locally endpointed providers retain their existing routing.
|
|
48
|
+
|
|
49
|
+
The gate shares the existing adaptive energy classifier. Quiet speech within the one-second tail passes through; quiet onset in the 200 ms buffer is preserved if followed by detected speech. Sustained speech entirely below the local threshold can be suppressed, and noise classified as speech can reopen the gate. This limits background exposure rather than guaranteeing that every false transcript is eliminated.
|
|
45
50
|
|
|
46
51
|
For a focused reproduction, say an unfinished question such as "Did they help invent", pause briefly, then complete it with "ChatGPT". Repeat with a shorter and a longer pause, plus a complete short question. Note when the assistant starts answering and export the logs. Check that STT submissions continue during the pause, then use the provider end event's time, confidence, and trigger to distinguish model endpointing from a timeout. Tests verify audio delivery and event handling; they do not establish how Flux will segment real speech.
|
|
52
|
+
|
|
53
|
+
For the idle gate, stay silent and type for 20 seconds, then ask a question with a pause longer than one second. Repeat with a quiet voice and with a deliberate interruption during an answer. Check for phantom requests, a clipped first syllable, split questions, and missed interruptions. Correlate those observations with gate transitions and provider turn events.
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"./reactions": "./src/reactions.ts",
|
|
28
28
|
"./guardian-requests": "./src/guardian-requests.ts",
|
|
29
29
|
"./platform-credential": "./src/platform-credential.ts",
|
|
30
|
-
"./plan-credit": "./src/plan-credit.ts"
|
|
30
|
+
"./plan-credit": "./src/plan-credit.ts",
|
|
31
|
+
"./integration-categories": "./src/integration-categories.ts"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
33
34
|
"typecheck": "bunx tsc --noEmit",
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./trust-rules.js";
|
|
|
29
29
|
export * from "./ingress.js";
|
|
30
30
|
export * from "./no-response.js";
|
|
31
31
|
export * from "./plan-credit.js";
|
|
32
|
+
export * from "./integration-categories.js";
|
|
32
33
|
export * from "./platform-credential.js";
|
|
33
34
|
export * from "./remote-web-pairing.js";
|
|
34
35
|
export * from "./twilio-ingress.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Category vocabulary for the integrations catalog, shared by the assistant
|
|
3
|
+
* daemon (which stamps it on OAuth providers and marketplace MCP entries) and
|
|
4
|
+
* the clients (which draw the category filter from it).
|
|
5
|
+
*
|
|
6
|
+
* The order here is the order clients show the categories in. The slug is the
|
|
7
|
+
* wire value; the user-facing label is the client's, so it can be translated.
|
|
8
|
+
*/
|
|
9
|
+
export const INTEGRATION_CATEGORIES = [
|
|
10
|
+
"productivity",
|
|
11
|
+
"communication",
|
|
12
|
+
"meetings",
|
|
13
|
+
"sales",
|
|
14
|
+
"marketing",
|
|
15
|
+
"finance",
|
|
16
|
+
"commerce",
|
|
17
|
+
"engineering",
|
|
18
|
+
"knowledge",
|
|
19
|
+
"recruiting",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export type IntegrationCategory = (typeof INTEGRATION_CATEGORIES)[number];
|
|
23
|
+
|
|
24
|
+
const INTEGRATION_CATEGORY_SET: ReadonlySet<string> = new Set(
|
|
25
|
+
INTEGRATION_CATEGORIES,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether `value` names a category this build knows. A client reading a newer
|
|
30
|
+
* catalog treats an unknown slug as uncategorized rather than failing.
|
|
31
|
+
*/
|
|
32
|
+
export function isIntegrationCategory(
|
|
33
|
+
value: unknown,
|
|
34
|
+
): value is IntegrationCategory {
|
|
35
|
+
return typeof value === "string" && INTEGRATION_CATEGORY_SET.has(value);
|
|
36
|
+
}
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"./reactions": "./src/reactions.ts",
|
|
28
28
|
"./guardian-requests": "./src/guardian-requests.ts",
|
|
29
29
|
"./platform-credential": "./src/platform-credential.ts",
|
|
30
|
-
"./plan-credit": "./src/plan-credit.ts"
|
|
30
|
+
"./plan-credit": "./src/plan-credit.ts",
|
|
31
|
+
"./integration-categories": "./src/integration-categories.ts"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
33
34
|
"typecheck": "bunx tsc --noEmit",
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./trust-rules.js";
|
|
|
29
29
|
export * from "./ingress.js";
|
|
30
30
|
export * from "./no-response.js";
|
|
31
31
|
export * from "./plan-credit.js";
|
|
32
|
+
export * from "./integration-categories.js";
|
|
32
33
|
export * from "./platform-credential.js";
|
|
33
34
|
export * from "./remote-web-pairing.js";
|
|
34
35
|
export * from "./twilio-ingress.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Category vocabulary for the integrations catalog, shared by the assistant
|
|
3
|
+
* daemon (which stamps it on OAuth providers and marketplace MCP entries) and
|
|
4
|
+
* the clients (which draw the category filter from it).
|
|
5
|
+
*
|
|
6
|
+
* The order here is the order clients show the categories in. The slug is the
|
|
7
|
+
* wire value; the user-facing label is the client's, so it can be translated.
|
|
8
|
+
*/
|
|
9
|
+
export const INTEGRATION_CATEGORIES = [
|
|
10
|
+
"productivity",
|
|
11
|
+
"communication",
|
|
12
|
+
"meetings",
|
|
13
|
+
"sales",
|
|
14
|
+
"marketing",
|
|
15
|
+
"finance",
|
|
16
|
+
"commerce",
|
|
17
|
+
"engineering",
|
|
18
|
+
"knowledge",
|
|
19
|
+
"recruiting",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export type IntegrationCategory = (typeof INTEGRATION_CATEGORIES)[number];
|
|
23
|
+
|
|
24
|
+
const INTEGRATION_CATEGORY_SET: ReadonlySet<string> = new Set(
|
|
25
|
+
INTEGRATION_CATEGORIES,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether `value` names a category this build knows. A client reading a newer
|
|
30
|
+
* catalog treats an unknown slug as uncategorized rather than failing.
|
|
31
|
+
*/
|
|
32
|
+
export function isIntegrationCategory(
|
|
33
|
+
value: unknown,
|
|
34
|
+
): value is IntegrationCategory {
|
|
35
|
+
return typeof value === "string" && INTEGRATION_CATEGORY_SET.has(value);
|
|
36
|
+
}
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"./reactions": "./src/reactions.ts",
|
|
28
28
|
"./guardian-requests": "./src/guardian-requests.ts",
|
|
29
29
|
"./platform-credential": "./src/platform-credential.ts",
|
|
30
|
-
"./plan-credit": "./src/plan-credit.ts"
|
|
30
|
+
"./plan-credit": "./src/plan-credit.ts",
|
|
31
|
+
"./integration-categories": "./src/integration-categories.ts"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
33
34
|
"typecheck": "bunx tsc --noEmit",
|
|
@@ -29,6 +29,7 @@ export * from "./trust-rules.js";
|
|
|
29
29
|
export * from "./ingress.js";
|
|
30
30
|
export * from "./no-response.js";
|
|
31
31
|
export * from "./plan-credit.js";
|
|
32
|
+
export * from "./integration-categories.js";
|
|
32
33
|
export * from "./platform-credential.js";
|
|
33
34
|
export * from "./remote-web-pairing.js";
|
|
34
35
|
export * from "./twilio-ingress.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Category vocabulary for the integrations catalog, shared by the assistant
|
|
3
|
+
* daemon (which stamps it on OAuth providers and marketplace MCP entries) and
|
|
4
|
+
* the clients (which draw the category filter from it).
|
|
5
|
+
*
|
|
6
|
+
* The order here is the order clients show the categories in. The slug is the
|
|
7
|
+
* wire value; the user-facing label is the client's, so it can be translated.
|
|
8
|
+
*/
|
|
9
|
+
export const INTEGRATION_CATEGORIES = [
|
|
10
|
+
"productivity",
|
|
11
|
+
"communication",
|
|
12
|
+
"meetings",
|
|
13
|
+
"sales",
|
|
14
|
+
"marketing",
|
|
15
|
+
"finance",
|
|
16
|
+
"commerce",
|
|
17
|
+
"engineering",
|
|
18
|
+
"knowledge",
|
|
19
|
+
"recruiting",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export type IntegrationCategory = (typeof INTEGRATION_CATEGORIES)[number];
|
|
23
|
+
|
|
24
|
+
const INTEGRATION_CATEGORY_SET: ReadonlySet<string> = new Set(
|
|
25
|
+
INTEGRATION_CATEGORIES,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether `value` names a category this build knows. A client reading a newer
|
|
30
|
+
* catalog treats an unknown slug as uncategorized rather than failing.
|
|
31
|
+
*/
|
|
32
|
+
export function isIntegrationCategory(
|
|
33
|
+
value: unknown,
|
|
34
|
+
): value is IntegrationCategory {
|
|
35
|
+
return typeof value === "string" && INTEGRATION_CATEGORY_SET.has(value);
|
|
36
|
+
}
|
package/openapi.yaml
CHANGED
|
@@ -21120,6 +21120,7 @@ paths:
|
|
|
21120
21120
|
- keenable
|
|
21121
21121
|
- firecrawl
|
|
21122
21122
|
- fastcrw
|
|
21123
|
+
- searxng
|
|
21123
21124
|
resultCount:
|
|
21124
21125
|
type: number
|
|
21125
21126
|
durationMs:
|
|
@@ -21637,6 +21638,7 @@ paths:
|
|
|
21637
21638
|
- keenable
|
|
21638
21639
|
- firecrawl
|
|
21639
21640
|
- fastcrw
|
|
21641
|
+
- searxng
|
|
21640
21642
|
resultCount:
|
|
21641
21643
|
type: number
|
|
21642
21644
|
durationMs:
|
|
@@ -24508,6 +24510,22 @@ paths:
|
|
|
24508
24510
|
anyOf:
|
|
24509
24511
|
- type: string
|
|
24510
24512
|
- type: "null"
|
|
24513
|
+
category:
|
|
24514
|
+
anyOf:
|
|
24515
|
+
- type: string
|
|
24516
|
+
enum:
|
|
24517
|
+
- productivity
|
|
24518
|
+
- communication
|
|
24519
|
+
- meetings
|
|
24520
|
+
- sales
|
|
24521
|
+
- marketing
|
|
24522
|
+
- finance
|
|
24523
|
+
- commerce
|
|
24524
|
+
- engineering
|
|
24525
|
+
- knowledge
|
|
24526
|
+
- recruiting
|
|
24527
|
+
- type: "null"
|
|
24528
|
+
description: Where the integrations catalog files the provider; null for a provider without a seed entry.
|
|
24511
24529
|
tenant_host:
|
|
24512
24530
|
anyOf:
|
|
24513
24531
|
- type: object
|
|
@@ -24540,6 +24558,7 @@ paths:
|
|
|
24540
24558
|
- supports_managed_mode
|
|
24541
24559
|
- managed_service_is_paid
|
|
24542
24560
|
- feature_flag
|
|
24561
|
+
- category
|
|
24543
24562
|
- tenant_host
|
|
24544
24563
|
- acts_as
|
|
24545
24564
|
additionalProperties: false
|
|
@@ -25059,6 +25078,22 @@ paths:
|
|
|
25059
25078
|
anyOf:
|
|
25060
25079
|
- type: string
|
|
25061
25080
|
- type: "null"
|
|
25081
|
+
category:
|
|
25082
|
+
anyOf:
|
|
25083
|
+
- type: string
|
|
25084
|
+
enum:
|
|
25085
|
+
- productivity
|
|
25086
|
+
- communication
|
|
25087
|
+
- meetings
|
|
25088
|
+
- sales
|
|
25089
|
+
- marketing
|
|
25090
|
+
- finance
|
|
25091
|
+
- commerce
|
|
25092
|
+
- engineering
|
|
25093
|
+
- knowledge
|
|
25094
|
+
- recruiting
|
|
25095
|
+
- type: "null"
|
|
25096
|
+
description: Where the integrations catalog files the provider; null for a provider without a seed entry.
|
|
25062
25097
|
tenant_host:
|
|
25063
25098
|
anyOf:
|
|
25064
25099
|
- type: object
|
|
@@ -25091,6 +25126,7 @@ paths:
|
|
|
25091
25126
|
- supports_managed_mode
|
|
25092
25127
|
- managed_service_is_paid
|
|
25093
25128
|
- feature_flag
|
|
25129
|
+
- category
|
|
25094
25130
|
- tenant_host
|
|
25095
25131
|
- acts_as
|
|
25096
25132
|
additionalProperties: false
|
|
@@ -27656,6 +27692,11 @@ paths:
|
|
|
27656
27692
|
type: string
|
|
27657
27693
|
oauthProvider:
|
|
27658
27694
|
type: string
|
|
27695
|
+
category:
|
|
27696
|
+
description:
|
|
27697
|
+
"Where the integrations catalog files the entry: a slug from the shared integration-category vocabulary,
|
|
27698
|
+
passed through as the catalog declares it."
|
|
27699
|
+
type: string
|
|
27659
27700
|
required:
|
|
27660
27701
|
- kind
|
|
27661
27702
|
- displayName
|
package/package.json
CHANGED
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* When the provider call throws (provider rejected the request before
|
|
5
5
|
* returning a usable response), the loop must emit a `provider_error` event
|
|
6
|
-
* carrying the
|
|
7
|
-
* consumers can persist an `llm_request_logs`
|
|
8
|
-
* calls leave nothing in the LLM inspector —
|
|
6
|
+
* carrying the inspectable wire request (when the throw attached one) and
|
|
7
|
+
* the thrown error so downstream consumers can persist an `llm_request_logs`
|
|
8
|
+
* row. Without this, rejected calls leave nothing in the LLM inspector —
|
|
9
|
+
* only a pino log line.
|
|
9
10
|
*
|
|
10
11
|
* Coverage:
|
|
11
|
-
* - Emits `provider_error` with `
|
|
12
|
-
*
|
|
13
|
-
* - `rawRequest`
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* - Emits `provider_error` with `error` and `actualProvider` when the
|
|
13
|
+
* provider throws a `ProviderError`.
|
|
14
|
+
* - `rawRequest` is `ProviderError.rawRequest` when present, and stays
|
|
15
|
+
* unset when the throw did not attach a wire payload. The loop does
|
|
16
|
+
* not invent a messages/tools/systemPrompt snapshot.
|
|
17
|
+
* - `actualProvider` echoes `ProviderError.provider` when available, even
|
|
18
|
+
* if the wrapping `provider.name` is a different default transport.
|
|
17
19
|
* - The error is still re-thrown internally (the existing `error` event
|
|
18
20
|
* still fires after the new `provider_error` event), preserving the
|
|
19
21
|
* outer-catch behavior (abort/Sentry/break).
|
|
@@ -73,7 +75,7 @@ function makeThrowingProvider(
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
describe("AgentLoop provider_error event emission", () => {
|
|
76
|
-
test("emits provider_error
|
|
78
|
+
test("emits provider_error without inventing a rawRequest snapshot", async () => {
|
|
77
79
|
const thrown = new ProviderError(
|
|
78
80
|
"Anthropic API error (429): rate limited",
|
|
79
81
|
"anthropic",
|
|
@@ -107,16 +109,81 @@ describe("AgentLoop provider_error event emission", () => {
|
|
|
107
109
|
}
|
|
108
110
|
expect(providerErrorEvent.error).toBe(thrown);
|
|
109
111
|
expect(providerErrorEvent.actualProvider).toBe("anthropic");
|
|
112
|
+
expect(providerErrorEvent.rawRequest).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("forwards ProviderError.rawRequest on the provider_error event", async () => {
|
|
116
|
+
const wirePayload = {
|
|
117
|
+
model: "qwen/qwen3-8b",
|
|
118
|
+
directions: { serious: 0.8 },
|
|
119
|
+
};
|
|
120
|
+
const thrown = new ProviderError(
|
|
121
|
+
"Vellum API error (400): directions are not loaded",
|
|
122
|
+
"vellum",
|
|
123
|
+
400,
|
|
124
|
+
{
|
|
125
|
+
rawRequest: wirePayload,
|
|
126
|
+
rawBody: '{"detail":"directions are not loaded"}',
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
const { provider } = makeThrowingProvider("fireworks", () => thrown);
|
|
130
|
+
|
|
131
|
+
const events: AgentEvent[] = [];
|
|
132
|
+
const loop = new AgentLoop({
|
|
133
|
+
provider: provider,
|
|
134
|
+
systemPrompt: "system",
|
|
135
|
+
conversationId: "test-conversation",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await loop.run({
|
|
139
|
+
requestId: "test-request",
|
|
140
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
141
|
+
onEvent: (e) => {
|
|
142
|
+
events.push(e);
|
|
143
|
+
},
|
|
144
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const providerErrorEvent = events.find((e) => e.type === "provider_error");
|
|
148
|
+
expect(providerErrorEvent).toBeDefined();
|
|
149
|
+
if (providerErrorEvent?.type !== "provider_error") {
|
|
150
|
+
throw new Error("type narrowing");
|
|
151
|
+
}
|
|
152
|
+
expect(providerErrorEvent.actualProvider).toBe("vellum");
|
|
153
|
+
expect(providerErrorEvent.rawRequest).toEqual(wirePayload);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("actualProvider uses ProviderError.provider, not the wrapper name", async () => {
|
|
157
|
+
const thrown = new ProviderError(
|
|
158
|
+
"Vellum API error (400): directions are not loaded",
|
|
159
|
+
"vellum",
|
|
160
|
+
400,
|
|
161
|
+
);
|
|
162
|
+
const { provider } = makeThrowingProvider("fireworks", () => thrown);
|
|
163
|
+
|
|
164
|
+
const events: AgentEvent[] = [];
|
|
165
|
+
const loop = new AgentLoop({
|
|
166
|
+
provider: provider,
|
|
167
|
+
systemPrompt: "system",
|
|
168
|
+
conversationId: "test-conversation",
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await loop.run({
|
|
172
|
+
requestId: "test-request",
|
|
173
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
174
|
+
onEvent: (e) => {
|
|
175
|
+
events.push(e);
|
|
176
|
+
},
|
|
177
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
178
|
+
});
|
|
110
179
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
expect(
|
|
117
|
-
expect(
|
|
118
|
-
expect(Array.isArray(raw.messages)).toBe(true);
|
|
119
|
-
expect((raw.messages as Message[])[0].role).toBe("user");
|
|
180
|
+
const providerErrorEvent = events.find((e) => e.type === "provider_error");
|
|
181
|
+
expect(providerErrorEvent).toBeDefined();
|
|
182
|
+
if (providerErrorEvent?.type !== "provider_error") {
|
|
183
|
+
throw new Error("type narrowing");
|
|
184
|
+
}
|
|
185
|
+
expect(providerErrorEvent.actualProvider).toBe("vellum");
|
|
186
|
+
expect(providerErrorEvent.rawRequest).toBeUndefined();
|
|
120
187
|
});
|
|
121
188
|
|
|
122
189
|
test("error event still fires after provider_error (outer catch behavior unchanged)", async () => {
|
|
@@ -185,6 +252,7 @@ describe("AgentLoop provider_error event emission", () => {
|
|
|
185
252
|
// `provider` column populated even for surprise errors.
|
|
186
253
|
expect(providerErrorEvent.actualProvider).toBe("openai");
|
|
187
254
|
expect(providerErrorEvent.error).toBe(thrown);
|
|
255
|
+
expect(providerErrorEvent.rawRequest).toBeUndefined();
|
|
188
256
|
});
|
|
189
257
|
|
|
190
258
|
test("does NOT emit provider_error on user-aborted runs", async () => {
|
|
@@ -103,6 +103,26 @@ describe("retagDelegateError", () => {
|
|
|
103
103
|
expect(err.apiErrorCode).toBe("restricted");
|
|
104
104
|
expect(err.apiErrorParam).toBe("model");
|
|
105
105
|
expect(err.rawBody).toBe('{"type":"error"}');
|
|
106
|
+
expect(err.rawRequest).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("carries rawRequest across the re-tag", () => {
|
|
110
|
+
const rawRequest = { model: "anthropic/claude-opus-4.6", messages: [] };
|
|
111
|
+
const inner = new ProviderError("rate limited", "anthropic", 429, {
|
|
112
|
+
rawRequest,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let caught: unknown;
|
|
116
|
+
try {
|
|
117
|
+
retagDelegateError(inner, providerName);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
caught = error;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
expect(caught).toBeInstanceOf(ProviderError);
|
|
123
|
+
const err = caught as ProviderError;
|
|
124
|
+
expect(err.provider).toBe(providerName);
|
|
125
|
+
expect(err.rawRequest).toEqual(rawRequest);
|
|
106
126
|
});
|
|
107
127
|
|
|
108
128
|
test("re-tags a delegate ContextOverflowError, preserving token counts", () => {
|
|
@@ -110,6 +130,7 @@ describe("retagDelegateError", () => {
|
|
|
110
130
|
actualTokens: 250_000,
|
|
111
131
|
maxTokens: 200_000,
|
|
112
132
|
statusCode: 400,
|
|
133
|
+
rawRequest: { model: "anthropic/claude-opus-4.6" },
|
|
113
134
|
});
|
|
114
135
|
|
|
115
136
|
let caught: unknown;
|
|
@@ -126,6 +147,7 @@ describe("retagDelegateError", () => {
|
|
|
126
147
|
expect(err.maxTokens).toBe(200_000);
|
|
127
148
|
expect(err.statusCode).toBe(400);
|
|
128
149
|
expect(err.cause).toBe(inner);
|
|
150
|
+
expect(err.rawRequest).toEqual({ model: "anthropic/claude-opus-4.6" });
|
|
129
151
|
});
|
|
130
152
|
|
|
131
153
|
test("rethrows an already-tagged error unchanged", () => {
|