@vellumai/assistant 0.11.2-staging.1 → 0.11.2-staging.3
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/openapi.yaml +5 -2
- package/package.json +1 -1
- package/src/__tests__/compactor-truncation-pairing.test.ts +368 -0
- package/src/__tests__/history-repair.test.ts +39 -0
- package/src/agent/history-repair/history-repair.ts +8 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -1
- package/src/config/bundled-skills/media-processing/__tests__/media-analysis-default-model.test.ts +177 -0
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +4 -3
- package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/media-analysis-model.ts +12 -0
- package/src/context/compactor.ts +41 -4
- package/src/live-voice/__tests__/activity-label.test.ts +27 -0
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +125 -0
- package/src/live-voice/activity-label.ts +28 -0
- package/src/live-voice/live-voice-session.ts +128 -17
- package/src/live-voice/protocol.ts +12 -0
- package/src/providers/openai/__tests__/orphan-tool-result-guard.test.ts +478 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -30
- package/src/providers/openai/orphaned-tool-result.ts +79 -0
- package/src/providers/openai/responses-provider.ts +50 -24
- package/src/runtime/routes/secret-routes.ts +8 -2
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
normalizeOpenAIAPIError,
|
|
27
27
|
} from "./api-error-normalization.js";
|
|
28
28
|
import { detectOpenAICompatibleContextOverflow } from "./chat-completions-provider.js";
|
|
29
|
+
import { serializeToolResult } from "./orphaned-tool-result.js";
|
|
29
30
|
|
|
30
31
|
const log = getLogger("openai-responses");
|
|
31
32
|
|
|
@@ -758,11 +759,16 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
758
759
|
messages = resolveMediaReferences(messages);
|
|
759
760
|
const result: unknown[] = [];
|
|
760
761
|
|
|
762
|
+
// `call_id`s emitted as `function_call` items earlier in this request.
|
|
763
|
+
// The API rejects a `function_call_output` whose `call_id` has no
|
|
764
|
+
// preceding `function_call`, so tool results are only serialized as
|
|
765
|
+
// outputs when their call was emitted first (backward matches only).
|
|
766
|
+
const emittedCallIds = new Set<string>();
|
|
761
767
|
for (const msg of messages) {
|
|
762
768
|
if (msg.role === "assistant") {
|
|
763
|
-
this.appendAssistantItems(result, msg);
|
|
769
|
+
this.appendAssistantItems(result, msg, emittedCallIds);
|
|
764
770
|
} else {
|
|
765
|
-
this.appendUserItems(result, msg);
|
|
771
|
+
this.appendUserItems(result, msg, emittedCallIds);
|
|
766
772
|
}
|
|
767
773
|
}
|
|
768
774
|
|
|
@@ -887,8 +893,16 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
887
893
|
}
|
|
888
894
|
}
|
|
889
895
|
|
|
890
|
-
/**
|
|
891
|
-
|
|
896
|
+
/**
|
|
897
|
+
* Convert an assistant message's content blocks to Responses input items.
|
|
898
|
+
* Every `function_call` emitted is recorded in `emittedCallIds` so the
|
|
899
|
+
* user-item conversion can pair `tool_result` blocks against it.
|
|
900
|
+
*/
|
|
901
|
+
private appendAssistantItems(
|
|
902
|
+
result: unknown[],
|
|
903
|
+
msg: Message,
|
|
904
|
+
emittedCallIds: Set<string>,
|
|
905
|
+
): void {
|
|
892
906
|
const textParts: string[] = [];
|
|
893
907
|
|
|
894
908
|
for (const block of msg.content) {
|
|
@@ -912,6 +926,7 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
912
926
|
name: block.name,
|
|
913
927
|
arguments: JSON.stringify(block.input),
|
|
914
928
|
});
|
|
929
|
+
emittedCallIds.add(block.id);
|
|
915
930
|
break;
|
|
916
931
|
case "server_tool_use":
|
|
917
932
|
textParts.push(`[Web search: ${block.name}]`);
|
|
@@ -932,8 +947,18 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
932
947
|
}
|
|
933
948
|
}
|
|
934
949
|
|
|
935
|
-
/**
|
|
936
|
-
|
|
950
|
+
/**
|
|
951
|
+
* Convert a user message's content blocks to Responses input items.
|
|
952
|
+
* A `tool_result` whose `tool_use_id` was not emitted as a `function_call`
|
|
953
|
+
* earlier in this request (`emittedCallIds`) is orphaned; the API rejects
|
|
954
|
+
* an unmatched `function_call_output`, so its content is degraded into the
|
|
955
|
+
* plain user message instead of dropped.
|
|
956
|
+
*/
|
|
957
|
+
private appendUserItems(
|
|
958
|
+
result: unknown[],
|
|
959
|
+
msg: Message,
|
|
960
|
+
emittedCallIds: Set<string>,
|
|
961
|
+
): void {
|
|
937
962
|
// Separate tool results from other blocks
|
|
938
963
|
const toolResults = msg.content.filter(
|
|
939
964
|
(b): b is Extract<ContentBlock, { type: "tool_result" }> =>
|
|
@@ -948,33 +973,34 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
948
973
|
|
|
949
974
|
// Emit tool results as function_call_output items
|
|
950
975
|
const toolResultImages: ContentBlock[] = [];
|
|
976
|
+
const orphanedResultBlocks: ContentBlock[] = [];
|
|
951
977
|
for (const tr of toolResults) {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
cb.type === "text",
|
|
958
|
-
)
|
|
959
|
-
.map((cb) => cb.text);
|
|
960
|
-
if (extraText.length > 0) {
|
|
961
|
-
textContent = textContent + "\n" + extraText.join("\n");
|
|
962
|
-
}
|
|
963
|
-
for (const cb of tr.contentBlocks) {
|
|
964
|
-
if (cb.type === "image") {
|
|
965
|
-
toolResultImages.push(cb);
|
|
966
|
-
}
|
|
978
|
+
// This transport carries images only; the text payload and the orphan
|
|
979
|
+
// decision are the shared cross-transport rule.
|
|
980
|
+
for (const cb of tr.contentBlocks ?? []) {
|
|
981
|
+
if (cb.type === "image") {
|
|
982
|
+
toolResultImages.push(cb);
|
|
967
983
|
}
|
|
968
984
|
}
|
|
985
|
+
const serialized = serializeToolResult(tr, emittedCallIds);
|
|
986
|
+
if (serialized.kind === "orphaned") {
|
|
987
|
+
orphanedResultBlocks.push(serialized.block);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
969
990
|
result.push({
|
|
970
991
|
type: "function_call_output",
|
|
971
992
|
call_id: tr.tool_use_id,
|
|
972
|
-
output:
|
|
993
|
+
output: serialized.payload,
|
|
973
994
|
});
|
|
974
995
|
}
|
|
975
996
|
|
|
976
|
-
// Emit remaining content
|
|
977
|
-
|
|
997
|
+
// Emit remaining content, degraded orphaned results, and any tool result
|
|
998
|
+
// images as a user message
|
|
999
|
+
const userContent = [
|
|
1000
|
+
...otherBlocks,
|
|
1001
|
+
...orphanedResultBlocks,
|
|
1002
|
+
...toolResultImages,
|
|
1003
|
+
];
|
|
978
1004
|
if (userContent.length > 0) {
|
|
979
1005
|
result.push(this.toResponsesUserMessage(userContent));
|
|
980
1006
|
}
|
|
@@ -756,8 +756,14 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
756
756
|
}),
|
|
757
757
|
responseBody: z.object({
|
|
758
758
|
success: z.boolean(),
|
|
759
|
-
type: z.string(),
|
|
760
|
-
name: z.string(),
|
|
759
|
+
type: z.string().optional(),
|
|
760
|
+
name: z.string().optional(),
|
|
761
|
+
error: z
|
|
762
|
+
.string()
|
|
763
|
+
.optional()
|
|
764
|
+
.describe(
|
|
765
|
+
"Why the secret was not stored (e.g. provider-side API key validation failed). Present only when success is false.",
|
|
766
|
+
),
|
|
761
767
|
}),
|
|
762
768
|
handler: handleAddSecret,
|
|
763
769
|
},
|