Skip to content

Ensamblaje createAgentSession

源码版本v0.73.1

createAgentSession es la entrada central del SDK. Toma AuthStorage, ModelRegistry, SettingsManager, SessionManager, ResourceLoader como servicios externos, más las definiciones de herramientas y el runner de extensiones, y los ensambla en un AgentSession usable. main.ts no la llama directamente, sino a través de createAgentSessionRuntime que pasa por createAgentSessionServices; pero los llamadores del SDK (que incrustan pi en otros programas) la usan tal cual.

Responsabilidades

  1. Resolución de servicios: cada servicio externo puede pasarse o crearse automáticamente; las rutas por defecto se basan en agentDir (~/.pi/agent). Ver packages/coding-agent/src/core/sdk.ts:193-211.
  2. Recuperación de modelo: si ya hay datos de sesión, recupera el model del header de la sesión; si no, del settings; si no se puede recuperar, rellena modelFallbackMessage. Ver packages/coding-agent/src/core/sdk.ts:214-248.
  3. Clampeo de thinking level: toma el thinking level de la sesión o de settings, y luego clampThinkingLevel lo acota al rango soportado por el modelo. Ver packages/coding-agent/src/core/sdk.ts:250-269.
  4. Inyección de stream function: el streamFn del Agent no llama a streamSimple directamente, sino que primero pide auth con modelRegistry.getApiKeyAndHeaders y luego suma las cabeceras de telemetría de getAttributionHeaders. Ver packages/coding-agent/src/core/sdk.ts:328-346.
  5. Envoltura de convertToLlm: cuando blockImages está activo, reemplaza todo ImageContent por texto placeholder, leyendo settings de forma dinámica para que el cambio a mitad de sesión también aplique. Ver packages/coding-agent/src/core/sdk.ts:282-316.
  6. Cabeceras de attribution: OpenRouter y Cloudflare reciben cabeceras HTTP específicas para clasificación. Ver packages/coding-agent/src/core/sdk.ts:128-156.

Motivación de diseño

¿Por qué no dejar que el llamador haga new Agent y luego new AgentSession? Porque el ensamblaje involucra más de una decena de servicios con dependencias mutuas: auth determina si el modelo está disponible, modelo determina el rango de thinking level, settings determina retry/transport/steering, el resource loader carga extensiones e inyecta el extensionRunnerRef. Exponer todo eso al llamador es invitar a cada uno a pisar las mismas piedras. createAgentSession concentra las reglas de ensamblaje en una sola función; el llamador del SDK sólo pasa cwd y (opcional) model.

getAttributionHeaders se extrae aparte porque cada provider necesita cabeceras distintas: OpenRouter usa HTTP-Referer + X-OpenRouter-*, Cloudflare usa User-Agent. Centralizarlo evita que se desparrame por la ruta de stream.

Archivos clave

En el punto de inyección de streamFn se ve cómo un fallo de auth cortocircuita y cómo se combinan las cabeceras de attribution:

typescript
// packages/coding-agent/src/core/sdk.ts:328-346
streamFn: async (model, context, options) => {
	const auth = await modelRegistry.getApiKeyAndHeaders(model);
	if (!auth.ok) {
		throw new Error(auth.error);
	}
	const providerRetrySettings = settingsManager.getProviderRetrySettings();
	const attributionHeaders = getAttributionHeaders(model, settingsManager);
	return streamSimple(model, context, {
		...options,
		apiKey: auth.apiKey,
		timeoutMs: options?.timeoutMs ?? providerRetrySettings.timeoutMs,
		// ...
		headers:
			attributionHeaders || auth.headers || options?.headers
				? { ...attributionHeaders, ...auth.headers, ...options?.headers }
				: undefined,
	});
},

La envoltura de convertToLlm reemplaza imágenes por un placeholder cuando blockImages está activo, y deduplica placeholders consecutivos:

typescript
// packages/coding-agent/src/core/sdk.ts:282-298
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
	const converted = convertToLlm(messages);
	if (!settingsManager.getBlockImages()) {
		return converted;
	}
	return converted.map((msg) => {
		if (msg.role === "user" || msg.role === "toolResult") {
			const content = msg.content;
			if (Array.isArray(content)) {
				const hasImages = content.some((c) => c.type === "image");
				if (hasImages) {
					const filteredContent = content
						.map((c) =>
							c.type === "image" ? { type: "text" as const, text: "Image reading is disabled." } : c,
						)
						// ... deduplica ...

Flujo de datos

Orden de ensamblaje:

Límites y fallos

  • Modelo no recuperable: modelFallbackMessage avisa de "no se pudo recuperar el modelo guardado" y también de "no hay ningún modelo configurado"; ambos se devuelven y la UI decide cómo mostrarlo. Ver packages/coding-agent/src/core/sdk.ts:243-247.
  • Sin modelo, thinking off: if (!model) thinkingLevel = "off", evita enviar parámetros de thinking con modelo vacío.
  • Sesión ya existente sin entrada thinking: al recuperar, se añade un appendThinkingLevelChange para que fork/resume posteriores tengan estado. Ver packages/coding-agent/src/core/sdk.ts:378-390.
  • transformContext de extensiones: si extensionRunnerRef.current no existe, devuelve los messages originales sin lanzar, permitiendo que funcione sin extensión atada.

Resumen

createAgentSession concentra las reglas de ensamblaje de más de una decena de servicios en una sola función; el llamador del SDK sólo pasa cwd y un model opcional. Los detalles hacia abajo en capa de orquestación AgentSession; las fábricas de herramientas en conjunto de herramientas read/bash/edit/write/grep/find/ls; la conversión de mensajes en tipos de mensaje y convertToLlm.