feat(gateway): implement OpenResponses /v1/responses endpoint phase 2
- Add input_image and input_file support with SSRF protection - Add client-side tools (Hosted Tools) support - Add turn-based tool flow with function_call_output handling - Export buildAgentPrompt for testing
This commit is contained in:
committed by
Peter Steinberger
parent
f4b03599f0
commit
a5afe7bc2b
@@ -482,6 +482,17 @@ export async function runEmbeddedPiAgent(
|
||||
agentMeta,
|
||||
aborted,
|
||||
systemPromptReport: attempt.systemPromptReport,
|
||||
// Handle client tool calls (OpenResponses hosted tools)
|
||||
stopReason: attempt.clientToolCall ? "tool_calls" : undefined,
|
||||
pendingToolCalls: attempt.clientToolCall
|
||||
? [
|
||||
{
|
||||
id: `call_${Date.now()}`,
|
||||
name: attempt.clientToolCall.name,
|
||||
arguments: JSON.stringify(attempt.clientToolCall.params),
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
},
|
||||
didSendViaMessagingTool: attempt.didSendViaMessagingTool,
|
||||
messagingToolSentTexts: attempt.messagingToolSentTexts,
|
||||
|
||||
@@ -64,6 +64,7 @@ import { prewarmSessionFile, trackSessionManagerAccess } from "../session-manage
|
||||
import { prepareSessionManagerForRun } from "../session-manager-init.js";
|
||||
import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system-prompt.js";
|
||||
import { splitSdkTools } from "../tool-split.js";
|
||||
import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
|
||||
import { buildSystemPromptParams } from "../../system-prompt-params.js";
|
||||
import { describeUnknownError, mapThinkingLevel } from "../utils.js";
|
||||
import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js";
|
||||
@@ -314,6 +315,16 @@ export async function runEmbeddedAttempt(
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
});
|
||||
|
||||
// Add client tools (OpenResponses hosted tools) to customTools
|
||||
let clientToolCallDetected: { name: string; params: Record<string, unknown> } | null = null;
|
||||
const clientToolDefs = params.clientTools
|
||||
? toClientToolDefinitions(params.clientTools, (toolName, toolParams) => {
|
||||
clientToolCallDetected = { name: toolName, params: toolParams };
|
||||
})
|
||||
: [];
|
||||
|
||||
const allCustomTools = [...customTools, ...clientToolDefs];
|
||||
|
||||
({ session } = await createAgentSession({
|
||||
cwd: resolvedWorkspace,
|
||||
agentDir,
|
||||
@@ -323,7 +334,7 @@ export async function runEmbeddedAttempt(
|
||||
thinkingLevel: mapThinkingLevel(params.thinkLevel),
|
||||
systemPrompt,
|
||||
tools: builtInTools,
|
||||
customTools,
|
||||
customTools: allCustomTools,
|
||||
sessionManager,
|
||||
settingsManager,
|
||||
skills: [],
|
||||
@@ -681,6 +692,8 @@ export async function runEmbeddedAttempt(
|
||||
cloudCodeAssistFormatError: Boolean(
|
||||
lastAssistant?.errorMessage && isCloudCodeAssistFormatError(lastAssistant.errorMessage),
|
||||
),
|
||||
// Client tool call detected (OpenResponses hosted tools)
|
||||
clientToolCall: clientToolCallDetected ?? undefined,
|
||||
};
|
||||
} finally {
|
||||
// Always tear down the session (and release the lock) before we leave this attempt.
|
||||
|
||||
@@ -6,6 +6,16 @@ import type { ExecElevatedDefaults, ExecToolDefaults } from "../../bash-tools.js
|
||||
import type { BlockReplyChunking, ToolResultFormat } from "../../pi-embedded-subscribe.js";
|
||||
import type { SkillSnapshot } from "../../skills.js";
|
||||
|
||||
// Simplified tool definition for client-provided tools (OpenResponses hosted tools)
|
||||
export type ClientToolDefinition = {
|
||||
type: "function";
|
||||
function: {
|
||||
name: string;
|
||||
description?: string;
|
||||
parameters?: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
export type RunEmbeddedPiAgentParams = {
|
||||
sessionId: string;
|
||||
sessionKey?: string;
|
||||
@@ -27,6 +37,8 @@ export type RunEmbeddedPiAgentParams = {
|
||||
skillsSnapshot?: SkillSnapshot;
|
||||
prompt: string;
|
||||
images?: ImageContent[];
|
||||
/** Optional client-provided tools (OpenResponses hosted tools). */
|
||||
clientTools?: ClientToolDefinition[];
|
||||
provider?: string;
|
||||
model?: string;
|
||||
authProfileId?: string;
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { MessagingToolSend } from "../../pi-embedded-messaging.js";
|
||||
import type { BlockReplyChunking, ToolResultFormat } from "../../pi-embedded-subscribe.js";
|
||||
import type { SkillSnapshot } from "../../skills.js";
|
||||
import type { SessionSystemPromptReport } from "../../../config/sessions/types.js";
|
||||
import type { ClientToolDefinition } from "./params.js";
|
||||
|
||||
type AuthStorage = ReturnType<typeof discoverAuthStorage>;
|
||||
type ModelRegistry = ReturnType<typeof discoverModels>;
|
||||
@@ -30,6 +31,8 @@ export type EmbeddedRunAttemptParams = {
|
||||
skillsSnapshot?: SkillSnapshot;
|
||||
prompt: string;
|
||||
images?: ImageContent[];
|
||||
/** Optional client-provided tools (OpenResponses hosted tools). */
|
||||
clientTools?: ClientToolDefinition[];
|
||||
provider: string;
|
||||
modelId: string;
|
||||
model: Model<Api>;
|
||||
@@ -79,4 +82,6 @@ export type EmbeddedRunAttemptResult = {
|
||||
messagingToolSentTexts: string[];
|
||||
messagingToolSentTargets: MessagingToolSend[];
|
||||
cloudCodeAssistFormatError: boolean;
|
||||
/** Client tool call detected (OpenResponses hosted tools). */
|
||||
clientToolCall?: { name: string; params: Record<string, unknown> };
|
||||
};
|
||||
|
||||
@@ -23,6 +23,14 @@ export type EmbeddedPiRunMeta = {
|
||||
kind: "context_overflow" | "compaction_failure" | "role_ordering";
|
||||
message: string;
|
||||
};
|
||||
/** Stop reason for the agent run (e.g., "completed", "tool_calls"). */
|
||||
stopReason?: string;
|
||||
/** Pending tool calls when stopReason is "tool_calls". */
|
||||
pendingToolCalls?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
arguments: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type EmbeddedPiRunResult = {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
AgentToolUpdateCallback,
|
||||
} from "@mariozechner/pi-agent-core";
|
||||
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
||||
import type { ClientToolDefinition } from "./pi-embedded-runner/run/params.js";
|
||||
import { logDebug, logError } from "../logger.js";
|
||||
import { normalizeToolName } from "./tool-policy.js";
|
||||
import { jsonResult } from "./tools/common.js";
|
||||
@@ -65,3 +66,38 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||
} satisfies ToolDefinition;
|
||||
});
|
||||
}
|
||||
|
||||
// Convert client tools (OpenResponses hosted tools) to ToolDefinition format
|
||||
// These tools are intercepted to return a "pending" result instead of executing
|
||||
export function toClientToolDefinitions(
|
||||
tools: ClientToolDefinition[],
|
||||
onClientToolCall?: (toolName: string, params: Record<string, unknown>) => void,
|
||||
): ToolDefinition[] {
|
||||
return tools.map((tool) => {
|
||||
const func = tool.function;
|
||||
return {
|
||||
name: func.name,
|
||||
label: func.name,
|
||||
description: func.description ?? "",
|
||||
parameters: func.parameters as any,
|
||||
execute: async (
|
||||
toolCallId,
|
||||
params,
|
||||
_onUpdate: AgentToolUpdateCallback<unknown> | undefined,
|
||||
_ctx,
|
||||
_signal,
|
||||
): Promise<AgentToolResult<unknown>> => {
|
||||
// Notify handler that a client tool was called
|
||||
if (onClientToolCall) {
|
||||
onClientToolCall(func.name, params as Record<string, unknown>);
|
||||
}
|
||||
// Return a pending result - the client will execute this tool
|
||||
return jsonResult({
|
||||
status: "pending",
|
||||
tool: func.name,
|
||||
message: "Tool execution delegated to client",
|
||||
});
|
||||
},
|
||||
} satisfies ToolDefinition;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user