Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
403
packages/ai/src/providers/google-shared.convert.test.ts
Normal file
403
packages/ai/src/providers/google-shared.convert.test.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
// Google shared conversion tests cover runtime-to-Google payload conversion.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Context, Tool } from "../types.js";
|
||||
import { convertMessages, convertTools } from "./google-shared.js";
|
||||
import {
|
||||
asRecord,
|
||||
expectConvertedRoles,
|
||||
getFirstToolParameters,
|
||||
makeGeminiCliAssistantMessage,
|
||||
makeGeminiCliModel,
|
||||
makeGoogleAssistantMessage,
|
||||
makeModel,
|
||||
} from "./google-shared.test-helpers.js";
|
||||
|
||||
type GoogleSharedTestModel = ReturnType<typeof makeModel> | ReturnType<typeof makeGeminiCliModel>;
|
||||
const convertMessagesForTest = convertMessages as unknown as (
|
||||
model: GoogleSharedTestModel,
|
||||
context: Context,
|
||||
) => ReturnType<typeof convertMessages>;
|
||||
|
||||
function requireRecordProperty(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
): Record<string, unknown> {
|
||||
const value = record[key];
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new Error(`expected object property ${key}`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
describe("google-shared convertTools", () => {
|
||||
it("preserves parameters when type is missing", () => {
|
||||
const tools = [
|
||||
{
|
||||
name: "noType",
|
||||
description: "Tool with properties but no type",
|
||||
parameters: {
|
||||
properties: {
|
||||
action: { type: "string" },
|
||||
},
|
||||
required: ["action"],
|
||||
},
|
||||
},
|
||||
] as unknown as Tool[];
|
||||
|
||||
const converted = convertTools(tools);
|
||||
const params = getFirstToolParameters(
|
||||
converted as Parameters<typeof getFirstToolParameters>[0],
|
||||
);
|
||||
|
||||
expect(params.type).toBeUndefined();
|
||||
expect(params.properties).toEqual({
|
||||
action: { type: "string" },
|
||||
});
|
||||
expect(params.required).toEqual(["action"]);
|
||||
});
|
||||
|
||||
it("keeps unsupported JSON Schema keywords intact", () => {
|
||||
const tools = [
|
||||
{
|
||||
name: "example",
|
||||
description: "Example tool",
|
||||
parameters: {
|
||||
type: "object",
|
||||
patternProperties: {
|
||||
"^x-": { type: "string" },
|
||||
},
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
mode: {
|
||||
type: "string",
|
||||
const: "fast",
|
||||
},
|
||||
options: {
|
||||
anyOf: [{ type: "string" }, { type: "number" }],
|
||||
},
|
||||
list: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
const: "item",
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["mode"],
|
||||
},
|
||||
},
|
||||
] as unknown as Tool[];
|
||||
|
||||
const converted = convertTools(tools);
|
||||
const params = getFirstToolParameters(
|
||||
converted as Parameters<typeof getFirstToolParameters>[0],
|
||||
);
|
||||
const properties = asRecord(params.properties);
|
||||
const mode = asRecord(properties.mode);
|
||||
const options = asRecord(properties.options);
|
||||
const list = asRecord(properties.list);
|
||||
const items = asRecord(list.items);
|
||||
|
||||
expect(params.patternProperties).toEqual({ "^x-": { type: "string" } });
|
||||
expect(params.additionalProperties).toBe(false);
|
||||
expect(mode.const).toBe("fast");
|
||||
expect(options.anyOf).toEqual([{ type: "string" }, { type: "number" }]);
|
||||
expect(items.const).toBe("item");
|
||||
expect(params.required).toEqual(["mode"]);
|
||||
});
|
||||
|
||||
it("keeps supported schema fields", () => {
|
||||
const tools = [
|
||||
{
|
||||
name: "settings",
|
||||
description: "Settings tool",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
config: {
|
||||
type: "object",
|
||||
properties: {
|
||||
retries: { type: "number", minimum: 1 },
|
||||
tags: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
},
|
||||
},
|
||||
required: ["retries"],
|
||||
},
|
||||
},
|
||||
required: ["config"],
|
||||
},
|
||||
},
|
||||
] as unknown as Tool[];
|
||||
|
||||
const converted = convertTools(tools);
|
||||
const params = getFirstToolParameters(
|
||||
converted as Parameters<typeof getFirstToolParameters>[0],
|
||||
);
|
||||
const config = asRecord(asRecord(params.properties).config);
|
||||
const configProps = asRecord(config.properties);
|
||||
const retries = asRecord(configProps.retries);
|
||||
const tags = asRecord(configProps.tags);
|
||||
const items = asRecord(tags.items);
|
||||
|
||||
expect(params.type).toBe("object");
|
||||
expect(config.type).toBe("object");
|
||||
expect(retries.minimum).toBe(1);
|
||||
expect(tags.type).toBe("array");
|
||||
expect(items.type).toBe("string");
|
||||
expect(config.required).toEqual(["retries"]);
|
||||
expect(params.required).toEqual(["config"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("google-shared convertMessages", () => {
|
||||
function expectConsecutiveMessagesNotMerged(params: {
|
||||
modelId: string;
|
||||
first: string;
|
||||
second: string;
|
||||
}) {
|
||||
const model = makeModel(params.modelId);
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: params.first,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: params.second,
|
||||
},
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expect(contents).toHaveLength(2);
|
||||
expect(contents[0].role).toBe("user");
|
||||
expect(contents[1].role).toBe("user");
|
||||
expect(contents[0].parts).toHaveLength(1);
|
||||
expect(contents[1].parts).toHaveLength(1);
|
||||
}
|
||||
|
||||
it("keeps thinking blocks when provider/model match", () => {
|
||||
const model = makeModel("gemini-1.5-pro");
|
||||
const context = {
|
||||
messages: [
|
||||
makeGoogleAssistantMessage(model.id, [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "hidden",
|
||||
thinkingSignature: "c2ln",
|
||||
},
|
||||
]),
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expect(contents).toHaveLength(1);
|
||||
expect(contents[0].role).toBe("model");
|
||||
const part = asRecord(contents[0].parts?.[0]);
|
||||
expect(part.thought).toBe(true);
|
||||
expect(part.thoughtSignature).toBe("c2ln");
|
||||
});
|
||||
|
||||
it("keeps thought signatures for Claude models", () => {
|
||||
const model = makeModel("claude-3-opus");
|
||||
const context = {
|
||||
messages: [
|
||||
makeGoogleAssistantMessage(model.id, [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "structured",
|
||||
thinkingSignature: "c2ln",
|
||||
},
|
||||
]),
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
const parts = contents?.[0]?.parts ?? [];
|
||||
expect(parts).toHaveLength(1);
|
||||
const part = asRecord(parts[0]);
|
||||
expect(part.thought).toBe(true);
|
||||
expect(part.thoughtSignature).toBe("c2ln");
|
||||
});
|
||||
|
||||
it("does not merge consecutive user messages for Gemini", () => {
|
||||
expectConsecutiveMessagesNotMerged({
|
||||
modelId: "gemini-1.5-pro",
|
||||
first: "Hello",
|
||||
second: "How are you?",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not merge consecutive user messages for non-Gemini Google models", () => {
|
||||
expectConsecutiveMessagesNotMerged({
|
||||
modelId: "claude-3-opus",
|
||||
first: "First",
|
||||
second: "Second",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not merge consecutive model messages for Gemini", () => {
|
||||
const model = makeModel("gemini-1.5-pro");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
},
|
||||
makeGoogleAssistantMessage(model.id, [{ type: "text", text: "Hi there!" }]),
|
||||
makeGoogleAssistantMessage(model.id, [{ type: "text", text: "How can I help?" }]),
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expectConvertedRoles(contents, ["user", "model", "model"]);
|
||||
expect(contents[1].parts).toHaveLength(1);
|
||||
expect(contents[2].parts).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("handles user message after tool result without model response in between", () => {
|
||||
const model = makeModel("gemini-1.5-pro");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Use a tool",
|
||||
},
|
||||
makeGoogleAssistantMessage(model.id, [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "myTool",
|
||||
arguments: { arg: "value" },
|
||||
},
|
||||
]),
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call_1",
|
||||
toolName: "myTool",
|
||||
content: [{ type: "text", text: "Tool result" }],
|
||||
isError: false,
|
||||
timestamp: 0,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Now do something else",
|
||||
},
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expect(contents).toHaveLength(4);
|
||||
expect(contents[0].role).toBe("user");
|
||||
expect(contents[1].role).toBe("model");
|
||||
expect(contents[2].role).toBe("user");
|
||||
expect(contents[3].role).toBe("user");
|
||||
const toolResponsePart = contents[2].parts?.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionResponse" in part,
|
||||
);
|
||||
const toolResponse = asRecord(toolResponsePart);
|
||||
expect(requireRecordProperty(toolResponse, "functionResponse").name).toBe("myTool");
|
||||
expect(contents[3].role).toBe("user");
|
||||
});
|
||||
|
||||
it("ensures function call comes after user turn, not after model turn", () => {
|
||||
const model = makeModel("gemini-1.5-pro");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
},
|
||||
makeGoogleAssistantMessage(model.id, [{ type: "text", text: "Hi!" }]),
|
||||
makeGoogleAssistantMessage(model.id, [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "myTool",
|
||||
arguments: {},
|
||||
},
|
||||
]),
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
expectConvertedRoles(contents, ["user", "model", "model", "user"]);
|
||||
const toolCallPart = contents[2].parts?.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionCall" in part,
|
||||
);
|
||||
const toolCall = asRecord(toolCallPart);
|
||||
expect(requireRecordProperty(toolCall, "functionCall").name).toBe("myTool");
|
||||
});
|
||||
|
||||
it("strips tool call and response ids for google-gemini-cli", () => {
|
||||
const model = makeGeminiCliModel("gemini-3-flash");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Use a tool",
|
||||
},
|
||||
makeGeminiCliAssistantMessage(model.id, [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "myTool",
|
||||
arguments: { arg: "value" },
|
||||
thoughtSignature: "dGVzdA==",
|
||||
},
|
||||
]),
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call_1",
|
||||
toolName: "myTool",
|
||||
content: [{ type: "text", text: "Tool result" }],
|
||||
isError: false,
|
||||
timestamp: 0,
|
||||
},
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
const parts = contents.flatMap((content) => content.parts ?? []);
|
||||
const toolCallPart = parts.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionCall" in part,
|
||||
);
|
||||
const toolResponsePart = parts.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionResponse" in part,
|
||||
);
|
||||
|
||||
const toolCall = asRecord(toolCallPart);
|
||||
const toolResponse = asRecord(toolResponsePart);
|
||||
|
||||
expect(asRecord(toolCall.functionCall).id).toBeUndefined();
|
||||
expect(asRecord(toolResponse.functionResponse).id).toBeUndefined();
|
||||
});
|
||||
|
||||
it("serializes structured tool results into function responses", () => {
|
||||
const model = makeModel("gemini-1.5-pro");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call_1",
|
||||
toolName: "session_status",
|
||||
content: [{ type: "json", payload: { sessionKey: "current", status: "ok" } }],
|
||||
isError: false,
|
||||
timestamp: 0,
|
||||
},
|
||||
],
|
||||
} as unknown as Context;
|
||||
const contents = convertMessagesForTest(model, context);
|
||||
const toolResponsePart = contents[0]?.parts?.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionResponse" in part,
|
||||
);
|
||||
expect(toolResponsePart).toBeDefined();
|
||||
const toolResponse = requireRecordProperty(asRecord(toolResponsePart), "functionResponse");
|
||||
expect(asRecord(toolResponse.response).output).toBe(
|
||||
'{"type":"json","payload":{"sessionKey":"current","status":"ok"}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user