Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions connectors/gemini/delegate.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import { geminiChat } from "./client.js";
import { githubRequest } from "../github/client.js";
import { readFileViaBlob } from "../github/helpers.js";
import { queryTelemetry } from "../cloudflare/observability.js";
import { notionRequest, notionRichTextToString, notionPageTitle, notionDatabaseTitle } from "../notion/client.js";
import { notionRequest, notionRichTextToString, notionPageTitle, notionDatabaseTitle, notionBlocksToText } from "../notion/client.js";
import { DEFAULT_OWNER } from "../../config.js";

const HARD_MAX_STEPS = 10;
const HARD_MAX_STEPS = 20;

// ---------------------------------------------------------------------------
// Delegated function declarations -- Gemini's "tools" param (a subset of
Expand Down Expand Up @@ -123,6 +123,29 @@ const FUNCTIONS = [
return JSON.stringify(data).slice(0, 30000);
},
},
{
name: "notion_get_page",
description: "Read a Notion page's title and text content by page ID (read-only). Use this after notion_search finds a candidate page, to actually see what's on it -- notion_search only returns titles/ids, not content.",
parameters: {
type: "object",
properties: {
page_id: { type: "string", description: "Notion page ID, e.g. from notion_search results" },
},
required: ["page_id"],
},
execute: async ({ page_id }) => {
const [page, blocksData] = await Promise.all([
notionRequest(`/pages/${page_id}`),
notionRequest(`/blocks/${page_id}/children?page_size=100`),
]);
const title = notionPageTitle(page);
const blocks = blocksData.results || [];
const content = notionBlocksToText(blocks) || "(no content)";
const hasMore = blocksData.has_more ? "\n[note: page has more than 100 blocks, only the first 100 are shown]" : "";
const text = `# ${title}\n${content}${hasMore}`;
return text.length > 20000 ? text.slice(0, 20000) + "\n...[truncated]" : text;
},
},
{
name: "notion_search",
description: "Search pages and databases in the Notion workspace (read-only).",
Expand Down Expand Up @@ -226,7 +249,7 @@ export async function runInvestigation({ task, max_steps = 6 }) {
transcript.push(`[step ${step}] ${name}(${JSON.stringify(args || {})}) -> ${resultText.length > 300 ? resultText.slice(0, 300) + "…" : resultText}`);
responseParts.push({ functionResponse: { name, response: { result: resultText } } });
}
contents.push({ role: "function", parts: responseParts });
contents.push({ role: "user", parts: responseParts });
}

return { answer: `(Investigation stopped after reaching the step cap of ${cappedSteps} without a final answer -- the task may need to be narrowed, or more steps requested up to the hard cap of ${HARD_MAX_STEPS}.)`, steps: cappedSteps, transcript };
Expand Down
2 changes: 1 addition & 1 deletion connectors/gemini/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function register(server) {
"Delegate an open-ended, multi-step READ-ONLY investigation to Gemini instead of doing it yourself one tool call at a time. Gemini runs its own loop server-side -- reading GitHub files/trees/commits, Cloudflare Workers logs, and Notion pages/databases across as many turns as it needs (bounded by max_steps) -- and returns one synthesized answer. Use this for things like \"why is CI failing on PR #42\" or \"summarize what changed in this repo over the last week\" where you'd otherwise need 5-10 separate manual tool calls. Not for anything requiring a write -- this tool is read-only by design.",
{
task: z.string().describe("The investigation task/question, described with enough context (repo names, time ranges, etc.) for Gemini to act without needing to ask you anything back -- it can't."),
max_steps: z.number().optional().describe("Max tool-use turns Gemini gets before being forced to answer (default 6, hard cap 10 regardless of this value)."),
max_steps: z.number().optional().describe("Max tool-use turns Gemini gets before being forced to answer (default 6, hard cap 20 regardless of this value)."),
log_to_notion: z.boolean().optional().describe("Whether to log the task, step-by-step tool calls, and final answer as a page under the Gemini section of Notion (default: true). Write always targets the fixed Gemini root page."),
},
async ({ task, max_steps = 6, log_to_notion = true }) => {
Expand Down
Loading