惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
有赞技术团队
有赞技术团队
Simon Willison's Weblog
Simon Willison's Weblog
人人都是产品经理
人人都是产品经理
L
LINUX DO - 最新话题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
小众软件
小众软件
Jina AI
Jina AI
The Cloudflare Blog
P
Palo Alto Networks Blog
AWS News Blog
AWS News Blog
阮一峰的网络日志
阮一峰的网络日志
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
NISL@THU
NISL@THU
G
GRAHAM CLULEY
Project Zero
Project Zero
博客园_首页
博客园 - 三生石上(FineUI控件)
罗磊的独立博客
Spread Privacy
Spread Privacy
WordPress大学
WordPress大学
Hugging Face - Blog
Hugging Face - Blog
Latest news
Latest news
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tor Project blog
S
Securelist
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
C
CERT Recently Published Vulnerability Notes
IT之家
IT之家
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Last Watchdog
The Last Watchdog
T
Tenable Blog
宝玉的分享
宝玉的分享
S
Secure Thoughts
P
Privacy & Cybersecurity Law Blog
量子位
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Security Archives - TechRepublic
Security Archives - TechRepublic

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4 test: trim doctor command hotspots · openclaw/openclaw@c66f16a test: isolate agent auth and spawn hotspots · openclaw/openclaw@9285935 test: stabilize MCP startup disposal race · openclaw/openclaw@dd9d2eb test: merge browser contract server suites · openclaw/openclaw@5817a76 test: narrow ollama provider discovery setup · openclaw/openclaw@a0d9598 build: declare qa-lab aimock runtime dependency · openclaw/openclaw@24431e5 test: speed up safe-bins exec harness · openclaw/openclaw@ee856ab test: preserve tool helpers in embedded runner mocks · openclaw/openclaw@acd86a0 refactor: move memory embeddings into provider plugins · openclaw/openclaw@77e6e4c test: reuse system-run temp fixtures · openclaw/openclaw@7e9ff0f test: trim hotspot wait overhead · openclaw/openclaw@12a59b0 Check: avoid duplicate boundary prep · openclaw/openclaw@baf11b8 test: reduce hotspot fixture overhead · openclaw/openclaw@3a59edd feat(ui): overhaul settings and slash command UX (#67819) thanks @Bun… · openclaw/openclaw@2cfb660 QA Matrix: exit cleanly on failure · openclaw/openclaw@42805d2 QA Matrix: isolate scenario coverage · openclaw/openclaw@7e659e1 Matrix: refresh crypto bootstrap state · openclaw/openclaw@94081d8 QA Lab: add provider registry · openclaw/openclaw@bb7e982 Matrix: add plugin changelog · openclaw/openclaw@4acab55 test: trim more hotspot overhead · openclaw/openclaw@f485311 test: trim remaining hotspot tests · openclaw/openclaw@6ba8626 test: narrow hotspot mocks · openclaw/openclaw@dbc8179 test: isolate gemini embedding request helpers · openclaw/openclaw@cd330f5 test: trim memory and mcp hotspots · openclaw/openclaw@fd48dfa test: slim provider registry mocks · openclaw/openclaw@2e08c77 test: harden Parallels update smoke · openclaw/openclaw@1a98090 feat: default Anthropic to Opus 4.7 · openclaw/openclaw@628b454 fix: harden node-host shell payload mutability checks · openclaw/openclaw@75c551e fix: land node-host approval binding for native binaries (#66731) (th… · openclaw/openclaw@29919bb CI: add daily schedule to CodeQL workflow (#67645) · openclaw/openclaw@69d25f5 fix(gateway): capture config hash after plugin auto-enable to prevent… · openclaw/openclaw@8c11210 fix: repair sanitized replay tool results before send (#67620) (thank… · openclaw/openclaw@c3c7a99 fix: restrict HTML timeout short-circuit to transient statuses · openclaw/openclaw@de129a6 fix: keep TUI watchdog bound to active run (#67401) (thanks @xantorres) · openclaw/openclaw@3525273 Gateway/skills: dedupe skills prefix-match + drop dead fallback on log · openclaw/openclaw@d7f489f Extensions/lmstudio: back off inference preload after consecutive fai… · openclaw/openclaw@b555214 TUI/streaming: add watchdog that resets the activity indicator after … · openclaw/openclaw@f44ab20 Agents/tool-loop: enable unknown-tool stream guard by default · openclaw/openclaw@36ed367 Gateway/skills: invalidate session skills snapshot on config write · openclaw/openclaw@b23d59a fix: classify HTML provider error pages correctly (#67642) (thanks @s… · openclaw/openclaw@e588e90 fix(skills): remove unused model-usage import (#67641) · openclaw/openclaw@55f05df docs(changelog): credit codex fix superseded PRs · openclaw/openclaw@e485f24 fix(openai-codex): normalize stale transport metadata in resolution a… · openclaw/openclaw@90801ba CI: pin Docker-related GitHub Actions (#67632) · openclaw/openclaw@f697b01 Android: modernize WebView and discovery API usage (#67627) · openclaw/openclaw@44a6e50 fix(deps): bump hono to 4.12.14 and @hono/node-server to 1.19.14 (GHS… · openclaw/openclaw@fbccc18 fix(deps): bump dompurify to 3.4.0 (#67614) · openclaw/openclaw@2c2dc00 CI: add explicit permissions to all workflow jobs (fixes code-scannin… · openclaw/openclaw@01b7516 fix: register bundled TTS providers and route overrides correctly (#6… · openclaw/openclaw@6ea3cdd fix: align host tilde paths with OS home (#62804) (thanks @stainlu) · openclaw/openclaw@ecfaf64 fix: flush creds queue before reconnect socket open (#67464) (thanks … · openclaw/openclaw@405c63f fix: strip standalone <function> tool call tags from visible text (#6… · openclaw/openclaw@78df859 fix(agents): preserve cli session metadata before transcript persist … · openclaw/openclaw@898fd04 docs(changelog): move cli transcript entry · openclaw/openclaw@c1817c6 fix(agents): normalize cli transcript api field · openclaw/openclaw@3a3fae0 docs(changelog): note cli transcript persistence · openclaw/openclaw@6c343f1 fix(agents): persist cli transcript turns · openclaw/openclaw@b8ef507 fix(msteams): harden security-sensitive flows (#65841) · openclaw/openclaw@c56b56e [Dashboard] Fix exec approval modal overflow for long command content… · openclaw/openclaw@053c5b0 Docs: remove QA changelog entry · openclaw/openclaw@7fd5771 QA: fix private runtime source loading (#67428) · openclaw/openclaw@d5933af docs(gateway): correct protocol.md schema path, hello-ok example, aut… · openclaw/openclaw@489404d CI: pin Node 22 runners to 22.18.0 · openclaw/openclaw@4ffa621 models.authStatus: normalize provider ids + tighten env-backed escape… · openclaw/openclaw@f2fdb9d Update CHANGELOG.md · openclaw/openclaw@7694a92 test(parallels): clean up npm update guard jobs · openclaw/openclaw@045ea7b Plugins: prefer scanDir override paths · openclaw/openclaw@b2974da fix(dreaming): default storage.mode to "separate" so phase blocks sto… · openclaw/openclaw@8c392f0 fix(memory-core): skip dreaming transcript ingestion via session stor… · openclaw/openclaw@a1b01f0 fix: dedupe replayed exec.finished node events (#67281) · openclaw/openclaw@5dcf526
refactor: share google provider stream helpers · openclaw/openclaw@a18bc56
vincentkoc · 2026-05-29 · via Recent Commits to openclaw:main

@@ -6,26 +6,34 @@ import {

66

type Content,

77

FinishReason,

88

FunctionCallingConfigMode,

9+

type GenerateContentConfig,

10+

type GenerateContentParameters,

911

type GenerateContentResponse,

1012

type Part,

13+

type ThinkingConfig,

1114

} from "@google/genai";

12-

import { calculateCost } from "../model-utils.js";

15+

import { calculateCost, clampThinkingLevel } from "../model-utils.js";

1316

import type {

17+

Api,

1418

AssistantMessage,

1519

Context,

1620

ImageContent,

1721

Model,

22+

SimpleStreamOptions,

1823

StopReason,

1924

TextContent,

25+

ThinkingBudgets,

2026

ThinkingContent,

27+

ThinkingLevel as AgentThinkingLevel,

2128

Tool,

2229

ToolCall,

30+

StreamOptions,

2331

} from "../types.js";

2432

import type { AssistantMessageEventStream } from "../utils/event-stream.js";

2533

import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";

2634

import { transformMessages } from "./transform-messages.js";

273528-

type GoogleApiType = "google-generative-ai" | "google-vertex";

36+

export type GoogleApiType = "google-generative-ai" | "google-vertex";

29373038

/**

3139

* Thinking level for Gemini 3 models.

@@ -38,6 +46,29 @@ export type GoogleThinkingLevel =

3846

| "MEDIUM"

3947

| "HIGH";

404849+

export type GoogleToolChoice = "auto" | "none" | "any";

50+51+

export type GoogleThinkingOptions = {

52+

enabled: boolean;

53+

budgetTokens?: number;

54+

level?: GoogleThinkingLevel;

55+

};

56+57+

export type GoogleProviderOptions = StreamOptions & {

58+

toolChoice?: GoogleToolChoice;

59+

thinking?: GoogleThinkingOptions;

60+

};

61+62+

type GoogleGenerateContentClient = {

63+

models: {

64+

generateContentStream(

65+

params: GenerateContentParameters,

66+

): Promise<AsyncIterable<GenerateContentResponse>> | AsyncIterable<GenerateContentResponse>;

67+

};

68+

};

69+70+

type ClampedGoogleThinkingLevel = Exclude<AgentThinkingLevel, "xhigh" | "max">;

71+4172

/**

4273

* Determines whether a streamed Gemini `Part` should be treated as "thinking".

4374

*

@@ -369,6 +400,294 @@ export function mapToolChoice(choice: string): FunctionCallingConfigMode {

369400

}

370401

}

371402403+

export function createGoogleAssistantOutput<T extends GoogleApiType>(

404+

model: Model<T>,

405+

api: Api = model.api,

406+

): AssistantMessage {

407+

return {

408+

role: "assistant",

409+

content: [],

410+

api,

411+

provider: model.provider,

412+

model: model.id,

413+

usage: {

414+

input: 0,

415+

output: 0,

416+

cacheRead: 0,

417+

cacheWrite: 0,

418+

totalTokens: 0,

419+

cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },

420+

},

421+

stopReason: "stop",

422+

timestamp: Date.now(),

423+

};

424+

}

425+426+

export async function runGoogleGenerateContentLifecycle<T extends GoogleApiType>(params: {

427+

stream: AssistantMessageEventStream;

428+

model: Model<T>;

429+

output: AssistantMessage;

430+

options?: Pick<StreamOptions, "signal" | "onPayload">;

431+

createClient: () => GoogleGenerateContentClient;

432+

buildParams: () => GenerateContentParameters;

433+

nextToolCallId: (name: string | undefined) => string;

434+

}): Promise<void> {

435+

const { stream, model, output, options } = params;

436+437+

try {

438+

const client = params.createClient();

439+

let requestParams = params.buildParams();

440+

const nextParams = await options?.onPayload?.(requestParams, model);

441+

if (nextParams !== undefined) {

442+

requestParams = nextParams as GenerateContentParameters;

443+

}

444+

const googleStream = await client.models.generateContentStream(requestParams);

445+

await consumeGoogleGenerateContentStream({

446+

chunks: googleStream,

447+

model,

448+

output,

449+

stream,

450+

signal: options?.signal,

451+

nextToolCallId: params.nextToolCallId,

452+

});

453+

} catch (error) {

454+

for (const block of output.content) {

455+

if ("index" in block) {

456+

delete (block as { index?: number }).index;

457+

}

458+

}

459+

output.stopReason = options?.signal?.aborted ? "aborted" : "error";

460+

output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);

461+

stream.push({ type: "error", reason: output.stopReason, error: output });

462+

stream.end();

463+

}

464+

}

465+466+

export function buildGoogleGenerateContentParams<T extends GoogleApiType>(

467+

model: Model<T>,

468+

context: Context,

469+

options: GoogleProviderOptions = {},

470+

configHooks?: {

471+

mapThinkingLevel?: (level: GoogleThinkingLevel) => ThinkingConfig["thinkingLevel"];

472+

getDisabledThinkingConfig?: (model: Model<T>) => ThinkingConfig;

473+

},

474+

): GenerateContentParameters {

475+

const contents = convertMessages(model, context);

476+477+

const generationConfig: GenerateContentConfig = {};

478+

if (options.temperature !== undefined) {

479+

generationConfig.temperature = options.temperature;

480+

}

481+

if (options.maxTokens !== undefined) {

482+

generationConfig.maxOutputTokens = options.maxTokens;

483+

}

484+485+

const config: GenerateContentConfig = {

486+

...(Object.keys(generationConfig).length > 0 && generationConfig),

487+

...(context.systemPrompt && { systemInstruction: sanitizeSurrogates(context.systemPrompt) }),

488+

...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools) }),

489+

};

490+491+

if (context.tools && context.tools.length > 0 && options.toolChoice) {

492+

config.toolConfig = {

493+

functionCallingConfig: {

494+

mode: mapToolChoice(options.toolChoice),

495+

},

496+

};

497+

} else {

498+

config.toolConfig = undefined;

499+

}

500+501+

if (options.thinking?.enabled && model.reasoning) {

502+

const thinkingConfig: ThinkingConfig = { includeThoughts: true };

503+

if (options.thinking.level !== undefined) {

504+

thinkingConfig.thinkingLevel = configHooks?.mapThinkingLevel

505+

? configHooks.mapThinkingLevel(options.thinking.level)

506+

: (options.thinking.level as ThinkingConfig["thinkingLevel"]);

507+

} else if (options.thinking.budgetTokens !== undefined) {

508+

thinkingConfig.thinkingBudget = options.thinking.budgetTokens;

509+

}

510+

config.thinkingConfig = thinkingConfig;

511+

} else if (model.reasoning && options.thinking && !options.thinking.enabled) {

512+

config.thinkingConfig = configHooks?.getDisabledThinkingConfig

513+

? configHooks.getDisabledThinkingConfig(model)

514+

: getDisabledGoogleThinkingConfig(model);

515+

}

516+517+

if (options.signal) {

518+

if (options.signal.aborted) {

519+

throw new Error("Request aborted");

520+

}

521+

config.abortSignal = options.signal;

522+

}

523+524+

return {

525+

model: model.id,

526+

contents,

527+

config,

528+

};

529+

}

530+531+

export function buildGoogleSimpleThinking<T extends GoogleApiType>(

532+

model: Model<T>,

533+

options: SimpleStreamOptions | undefined,

534+

config?: {

535+

includeGemma4ThinkingLevel?: boolean;

536+

useFlashLiteBudgets?: boolean;

537+

},

538+

): GoogleThinkingOptions {

539+

if (!options?.reasoning) {

540+

return { enabled: false };

541+

}

542+543+

const clampedReasoning = clampThinkingLevel(model, options.reasoning);

544+

const effort = (

545+

clampedReasoning === "off" || clampedReasoning === "max" ? "high" : clampedReasoning

546+

) as ClampedGoogleThinkingLevel;

547+548+

if (

549+

isGemini3ProModel(model) ||

550+

isGemini3FlashModel(model) ||

551+

(config?.includeGemma4ThinkingLevel && isGemma4Model(model))

552+

) {

553+

return {

554+

enabled: true,

555+

level: getGoogleThinkingLevel(effort, model, {

556+

includeGemma4: config?.includeGemma4ThinkingLevel,

557+

}),

558+

};

559+

}

560+561+

return {

562+

enabled: true,

563+

budgetTokens: getGoogleBudget(model, effort, options.thinkingBudgets, {

564+

useFlashLiteBudgets: config?.useFlashLiteBudgets,

565+

}),

566+

};

567+

}

568+569+

export function getDisabledGoogleThinkingConfig<T extends GoogleApiType>(

570+

model: Model<T>,

571+

config?: {

572+

includeGemma4?: boolean;

573+

mapThinkingLevel?: (level: GoogleThinkingLevel) => ThinkingConfig["thinkingLevel"];

574+

},

575+

): ThinkingConfig {

576+

const mapThinkingLevel = (level: GoogleThinkingLevel): ThinkingConfig["thinkingLevel"] =>

577+

config?.mapThinkingLevel

578+

? config.mapThinkingLevel(level)

579+

: (level as ThinkingConfig["thinkingLevel"]);

580+581+

// Google docs: Gemini 3.1 Pro cannot disable thinking, and Gemini 3 Flash / Flash-Lite

582+

// do not support full thinking-off either. For Gemini 3 models, use the lowest supported

583+

// thinkingLevel without includeThoughts so hidden thinking remains invisible to OpenClaw.

584+

if (isGemini3ProModel(model)) {

585+

return { thinkingLevel: mapThinkingLevel("LOW") };

586+

}

587+

if (isGemini3FlashModel(model)) {

588+

return { thinkingLevel: mapThinkingLevel("MINIMAL") };

589+

}

590+

if (config?.includeGemma4 && isGemma4Model(model)) {

591+

return { thinkingLevel: mapThinkingLevel("MINIMAL") };

592+

}

593+594+

// Gemini 2.x supports disabling via thinkingBudget = 0.

595+

return { thinkingBudget: 0 };

596+

}

597+598+

export function isGemma4Model<T extends GoogleApiType>(model: Model<T>): boolean {

599+

return /gemma-?4/.test(model.id.toLowerCase());

600+

}

601+602+

export function isGemini3ProModel<T extends GoogleApiType>(model: Model<T>): boolean {

603+

return /gemini-3(?:\.\d+)?-pro/.test(model.id.toLowerCase());

604+

}

605+606+

export function isGemini3FlashModel<T extends GoogleApiType>(model: Model<T>): boolean {

607+

return /gemini-3(?:\.\d+)?-flash/.test(model.id.toLowerCase());

608+

}

609+610+

function getGoogleThinkingLevel<T extends GoogleApiType>(

611+

effort: ClampedGoogleThinkingLevel,

612+

model: Model<T>,

613+

config?: { includeGemma4?: boolean },

614+

): GoogleThinkingLevel {

615+

if (isGemini3ProModel(model)) {

616+

switch (effort) {

617+

case "minimal":

618+

case "low":

619+

return "LOW";

620+

case "medium":

621+

case "high":

622+

return "HIGH";

623+

}

624+

}

625+

if (config?.includeGemma4 && isGemma4Model(model)) {

626+

switch (effort) {

627+

case "minimal":

628+

case "low":

629+

return "MINIMAL";

630+

case "medium":

631+

case "high":

632+

return "HIGH";

633+

}

634+

}

635+

switch (effort) {

636+

case "minimal":

637+

return "MINIMAL";

638+

case "low":

639+

return "LOW";

640+

case "medium":

641+

return "MEDIUM";

642+

case "high":

643+

return "HIGH";

644+

}

645+

return "HIGH";

646+

}

647+648+

function getGoogleBudget<T extends GoogleApiType>(

649+

model: Model<T>,

650+

effort: ClampedGoogleThinkingLevel,

651+

customBudgets?: ThinkingBudgets,

652+

config?: { useFlashLiteBudgets?: boolean },

653+

): number {

654+

if (customBudgets?.[effort] !== undefined) {

655+

return customBudgets[effort];

656+

}

657+658+

if (model.id.includes("2.5-pro")) {

659+

const budgets: Record<ClampedGoogleThinkingLevel, number> = {

660+

minimal: 128,

661+

low: 2048,

662+

medium: 8192,

663+

high: 32768,

664+

};

665+

return budgets[effort];

666+

}

667+668+

if (config?.useFlashLiteBudgets && model.id.includes("2.5-flash-lite")) {

669+

const budgets: Record<ClampedGoogleThinkingLevel, number> = {

670+

minimal: 512,

671+

low: 2048,

672+

medium: 8192,

673+

high: 24576,

674+

};

675+

return budgets[effort];

676+

}

677+678+

if (model.id.includes("2.5-flash")) {

679+

const budgets: Record<ClampedGoogleThinkingLevel, number> = {

680+

minimal: 128,

681+

low: 2048,

682+

medium: 8192,

683+

high: 24576,

684+

};

685+

return budgets[effort];

686+

}

687+688+

return -1;

689+

}

690+372691

/**

373692

* Map Gemini FinishReason to our StopReason.

374693

*/