diff --git a/pi/files.linux/agent/settings.json b/pi/files.linux/agent/settings.json index 43ef1ec..5ab3fc0 100644 --- a/pi/files.linux/agent/settings.json +++ b/pi/files.linux/agent/settings.json @@ -1,5 +1,5 @@ { - "lastChangelogVersion": "0.60.0", + "lastChangelogVersion": "0.63.1", "defaultProvider": "openai-codex", "defaultModel": "gpt-5.3-codex", "defaultThinkingLevel": "high", diff --git a/pi/files/agent/auth.json b/pi/files/agent/auth.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/pi/files/agent/auth.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pi/files/agent/extensions/edit-session.ts b/pi/files/agent/extensions/edit-session.ts index 5887b0f..68a4510 100644 --- a/pi/files/agent/extensions/edit-session.ts +++ b/pi/files/agent/extensions/edit-session.ts @@ -9,8 +9,8 @@ * The editor is determined by $VISUAL, then $EDITOR, then falls back to 'vi'. */ -import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; -import type { TUI, Theme, KeybindingsManager, Component } from "@mariozechner/pi-tui"; +import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent"; +import type { TUI, KeybindingsManager, Component } from "@mariozechner/pi-tui"; import { spawnSync } from "node:child_process"; export default function editSessionExtension(pi: ExtensionAPI) { @@ -59,7 +59,7 @@ export default function editSessionExtension(pi: ExtensionAPI) { ctx.ui.notify(`Editor exited with code ${result.status}`, "warning"); } - done(); + done(undefined); // Return dummy component return createDummyComponent(); @@ -69,7 +69,7 @@ export default function editSessionExtension(pi: ExtensionAPI) { await ctx.ui.custom(factory); // Signal that we're about to reload the session (so confirm-destructive skips) - pi.events.emit("edit-session:reload"); + pi.events.emit("edit-session:reload", undefined); // Reload the session by switching to the same file (forces re-read from disk) ctx.ui.notify("Reloading session...", "info"); diff --git a/pi/files/agent/extensions/handoff.ts b/pi/files/agent/extensions/handoff.ts index feecf8e..0f53975 100644 --- a/pi/files/agent/extensions/handoff.ts +++ b/pi/files/agent/extensions/handoff.ts @@ -80,7 +80,8 @@ export default function (pi: ExtensionAPI) { loader.onAbort = () => done(null); const doGenerate = async () => { - const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!); + const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!); + if (!auth.ok) throw new Error(auth.error); const userMessage: Message = { role: "user", @@ -96,7 +97,7 @@ export default function (pi: ExtensionAPI) { const response = await complete( ctx.model!, { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] }, - { apiKey, signal: loader.signal }, + { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal }, ); if (response.stopReason === "aborted") { diff --git a/pi/files/agent/extensions/package.json b/pi/files/agent/extensions/package.json index 002602b..0143cc8 100644 --- a/pi/files/agent/extensions/package.json +++ b/pi/files/agent/extensions/package.json @@ -13,10 +13,11 @@ "vscode-languageserver-protocol": "^3.17.5" }, "devDependencies": { - "@mariozechner/pi-ai": "^0.56.3", - "@mariozechner/pi-coding-agent": "^0.56.3", - "@mariozechner/pi-tui": "^0.56.3", + "@mariozechner/pi-ai": "^0.63.1", + "@mariozechner/pi-coding-agent": "^0.63.1", + "@mariozechner/pi-tui": "^0.63.1", "@types/node": "^25.3.3", + "@types/turndown": "^5.0.6", "typescript": "^5.7.0" }, "pi": {}, diff --git a/pi/files/agent/extensions/pi-web-access/index.ts b/pi/files/agent/extensions/pi-web-access/index.ts index 834514b..75cbd76 100644 --- a/pi/files/agent/extensions/pi-web-access/index.ts +++ b/pi/files/agent/extensions/pi-web-access/index.ts @@ -211,12 +211,12 @@ function updateWidget(ctx: ExtensionContext): void { (resetMs > 0 ? theme.fg("dim", ` (resets in ${resetSec}s)`) : ""), ); - ctx.ui.setWidget("web-activity", new Text(lines.join("\n"), 0, 0)); + ctx.ui.setWidget("web-activity", lines); } function formatEntryLine( entry: ActivityEntry, - theme: { fg: (color: string, text: string) => string }, + theme: ExtensionContext["ui"]["theme"], ): string { const typeStr = entry.type === "api" ? "API" : "GET"; const target = @@ -550,7 +550,7 @@ export default function (pi: ExtensionAPI) { } else { widgetUnsubscribe?.(); widgetUnsubscribe = null; - ctx.ui.setWidget("web-activity", null); + ctx.ui.setWidget("web-activity", undefined); } }, }); @@ -598,7 +598,7 @@ export default function (pi: ExtensionAPI) { })), }), - async execute(_toolCallId, params, signal, onUpdate, ctx) { + async execute(_toolCallId, params, signal, onUpdate, ctx): Promise { const queryList = params.queries ?? (params.query ? [params.query] : []); const isMultiQuery = queryList.length > 1; const shouldCurate = params.curate !== false && ctx?.hasUI !== false; @@ -613,7 +613,10 @@ export default function (pi: ExtensionAPI) { if (shouldCurate) { closeCurator(); - const { promise, resolve: resolvePromise } = Promise.withResolvers(); + let resolvePromise!: (value: unknown) => void; + const promise = new Promise((resolve) => { + resolvePromise = resolve; + }); const includeContent = params.includeContent ?? false; const searchResults = new Map(); const allUrls: string[] = []; @@ -637,7 +640,7 @@ export default function (pi: ExtensionAPI) { queryList, includeContent, numResults: params.numResults, - recencyFilter: params.recencyFilter, + recencyFilter: params.recencyFilter as "day" | "week" | "month" | "year" | undefined, domainFilter: params.domainFilter, availableProviders, defaultProvider, @@ -684,7 +687,7 @@ export default function (pi: ExtensionAPI) { const { answer, results } = await search(queryList[qi], { provider: defaultProvider as SearchProvider | undefined, numResults: params.numResults, - recencyFilter: params.recencyFilter, + recencyFilter: params.recencyFilter as "day" | "week" | "month" | "year" | undefined, domainFilter: params.domainFilter, signal, }); @@ -754,7 +757,7 @@ export default function (pi: ExtensionAPI) { text = `${searchResults.size} searches (${totalSources} sources) · ${curateLabel} to review · sending in ${remaining}s`; } return { - content: [{ type: "text", text }], + content: [{ type: "text" as const, text }], details: { phase: "curate-window", searchCount: searchResults.size, @@ -824,7 +827,7 @@ export default function (pi: ExtensionAPI) { const { answer, results } = await search(query, { provider: resolvedProvider as SearchProvider | undefined, numResults: params.numResults, - recencyFilter: params.recencyFilter, + recencyFilter: params.recencyFilter as "day" | "week" | "month" | "year" | undefined, domainFilter: params.domainFilter, signal, }); @@ -1117,7 +1120,10 @@ export default function (pi: ExtensionAPI) { `Use get_search_content({ responseId: "${responseId}", urlIndex: 0 }) for full content.`; } - const content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> = []; + const content: Array< + | { type: "image"; data: string; mimeType: string } + | { type: "text"; text: string } + > = []; if (result.frames?.length) { for (const frame of result.frames) { content.push({ type: "image", data: frame.data, mimeType: frame.mimeType }); @@ -1290,7 +1296,7 @@ export default function (pi: ExtensionAPI) { urlIndex: Type.Optional(Type.Number({ description: "Get content for URL at index" })), }), - async execute(_toolCallId, params) { + async execute(_toolCallId, params, _signal, _onUpdate, _ctx): Promise { const data = getResult(params.responseId); if (!data) { return { @@ -1477,7 +1483,7 @@ export default function (pi: ExtensionAPI) { pi.sendMessage({ customType: "web-search-results", content: [{ type: "text", text }], - display: "tool", + display: true, details: { queryCount: results.length, totalResults: urls.length }, }, { triggerTurn: true, deliverAs: "followUp" }); } diff --git a/pi/files/agent/extensions/pi-web-access/pdf-extract.ts b/pi/files/agent/extensions/pi-web-access/pdf-extract.ts index 19c580c..539fae8 100644 --- a/pi/files/agent/extensions/pi-web-access/pdf-extract.ts +++ b/pi/files/agent/extensions/pi-web-access/pdf-extract.ts @@ -42,9 +42,10 @@ export async function extractPDFToMarkdown( const pdf = await getDocumentProxy(new Uint8Array(buffer)); const metadata = await pdf.getMetadata(); + const info = (metadata.info ?? {}) as Record; // Extract title from metadata or URL - const metaTitle = metadata.info?.Title as string | undefined; + const metaTitle = typeof info.Title === "string" ? info.Title : undefined; const urlTitle = extractTitleFromURL(url); const title = metaTitle?.trim() || urlTitle; @@ -79,8 +80,9 @@ export async function extractPDFToMarkdown( lines.push(""); lines.push(`> Source: ${url}`); lines.push(`> Pages: ${pdf.numPages}${truncated ? ` (extracted first ${pagesToExtract})` : ""}`); - if (metadata.info?.Author) { - lines.push(`> Author: ${metadata.info.Author}`); + const author = typeof info.Author === "string" ? info.Author : undefined; + if (author) { + lines.push(`> Author: ${author}`); } lines.push(""); lines.push("---"); diff --git a/pi/files/agent/extensions/pi-web-access/search-filter.ts b/pi/files/agent/extensions/pi-web-access/search-filter.ts index 0c368a2..f34193e 100644 --- a/pi/files/agent/extensions/pi-web-access/search-filter.ts +++ b/pi/files/agent/extensions/pi-web-access/search-filter.ts @@ -245,8 +245,8 @@ export async function condenseSearchResults( const model = ctx.modelRegistry.find(provider, modelId); if (!model) return null; - const apiKey = await ctx.modelRegistry.getApiKey(model); - if (!apiKey) return null; + const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); + if (!auth.ok) return null; const queryData = [...results.entries()] .sort((a, b) => a[0] - b[0]) @@ -281,7 +281,8 @@ export async function condenseSearchResults( : timeoutSignal; const response = await complete(model, aiContext, { - apiKey, + apiKey: auth.apiKey, + headers: auth.headers, signal: combinedSignal, max_tokens: MAX_TOKENS, } as any); diff --git a/pi/files/agent/extensions/pnpm-lock.yaml b/pi/files/agent/extensions/pnpm-lock.yaml index 752b136..8274043 100644 --- a/pi/files/agent/extensions/pnpm-lock.yaml +++ b/pi/files/agent/extensions/pnpm-lock.yaml @@ -34,17 +34,20 @@ importers: version: 3.17.5 devDependencies: '@mariozechner/pi-ai': - specifier: ^0.56.3 - version: 0.56.3(ws@8.19.0)(zod@4.3.6) + specifier: ^0.63.1 + version: 0.63.1(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-coding-agent': - specifier: ^0.56.3 - version: 0.56.3(ws@8.19.0)(zod@4.3.6) + specifier: ^0.63.1 + version: 0.63.1(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-tui': - specifier: ^0.56.3 - version: 0.56.3 + specifier: ^0.63.1 + version: 0.63.1 '@types/node': specifier: ^25.3.3 version: 25.3.3 + '@types/turndown': + specifier: ^5.0.6 + version: 5.0.6 typescript: specifier: ^5.7.0 version: 5.9.3 @@ -289,22 +292,22 @@ packages: resolution: {integrity: sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==} hasBin: true - '@mariozechner/pi-agent-core@0.56.3': - resolution: {integrity: sha512-TsI1zENf3wqqKPaERnj486Q4i6Y/y6lAZipLNcfDYUDxDrLwNfQ9EW9xukkbJfTZ8zjG3VZ2pBZe3C7wM51dVQ==} + '@mariozechner/pi-agent-core@0.63.1': + resolution: {integrity: sha512-h0B20xfs/iEVR2EC4gwiE8hKI1TPeB8REdRJMgV+uXKH7gpeIZ9+s8Dp9nX35ZR0QUjkNey2+ULk2DxQtdg14Q==} engines: {node: '>=20.0.0'} - '@mariozechner/pi-ai@0.56.3': - resolution: {integrity: sha512-l4J+cVyVeBLAlGOY/osGDvsbTz0DySCQmR171G6SdbPvIeLGhIi6siZ+zHwq91GJYjv/wtu/08M08ag2mGZKeA==} + '@mariozechner/pi-ai@0.63.1': + resolution: {integrity: sha512-wjgwY+yfrFO6a9QdAfjWpH7iSrDean6GsKDDMohNcLCy6PreMxHOZvNM0NwJARL1tZoZovr7ikAQfLGFZbnjsw==} engines: {node: '>=20.0.0'} hasBin: true - '@mariozechner/pi-coding-agent@0.56.3': - resolution: {integrity: sha512-yHgnadye+TT/4NWKBirZUjw/LWdNWTa7M4HJdX2RxRbwuj4q7RZ0Aqy+lQbOHEPDQYhxK3kZb9hjiAbbGficZQ==} + '@mariozechner/pi-coding-agent@0.63.1': + resolution: {integrity: sha512-XSoMyLtuMA7ePK1UBWqSJ/BBdtBdJUHY9nbtnNyG6GeW7Gbgd+iqljIuwmAUf8wlYL981UIfYM/WIPQ6t/dIxw==} engines: {node: '>=20.6.0'} hasBin: true - '@mariozechner/pi-tui@0.56.3': - resolution: {integrity: sha512-eZ1P9QRKHp78hwx+lITr/mujZqe+eCwL/bOS9vXXkFP070RW4VYum0j7TJ4BrFEH/nNkXRS1tYCXYU05une1bA==} + '@mariozechner/pi-tui@0.63.1': + resolution: {integrity: sha512-G5p+eh1EPkFCNaaggX6vRrqttnDscK6npgmEOknoCQXZtch8XNgh9Lf3VJ0A2lZXSgR7IntG5dfXHPH/Ki64wA==} engines: {node: '>=20.0.0'} '@mistralai/mistralai@1.14.1': @@ -568,6 +571,9 @@ packages: '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/turndown@5.0.6': + resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -1722,9 +1728,9 @@ snapshots: std-env: 3.10.0 yoctocolors: 2.1.2 - '@mariozechner/pi-agent-core@0.56.3(ws@8.19.0)(zod@4.3.6)': + '@mariozechner/pi-agent-core@0.63.1(ws@8.19.0)(zod@4.3.6)': dependencies: - '@mariozechner/pi-ai': 0.56.3(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.63.1(ws@8.19.0)(zod@4.3.6) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - aws-crt @@ -1734,7 +1740,7 @@ snapshots: - ws - zod - '@mariozechner/pi-ai@0.56.3(ws@8.19.0)(zod@4.3.6)': + '@mariozechner/pi-ai@0.63.1(ws@8.19.0)(zod@4.3.6)': dependencies: '@anthropic-ai/sdk': 0.73.0(zod@4.3.6) '@aws-sdk/client-bedrock-runtime': 3.1002.0 @@ -1758,13 +1764,14 @@ snapshots: - ws - zod - '@mariozechner/pi-coding-agent@0.56.3(ws@8.19.0)(zod@4.3.6)': + '@mariozechner/pi-coding-agent@0.63.1(ws@8.19.0)(zod@4.3.6)': dependencies: '@mariozechner/jiti': 2.6.5 - '@mariozechner/pi-agent-core': 0.56.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.56.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-tui': 0.56.3 + '@mariozechner/pi-agent-core': 0.63.1(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.63.1(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-tui': 0.63.1 '@silvia-odwyer/photon-node': 0.3.4 + ajv: 8.18.0 chalk: 5.6.2 cli-highlight: 2.1.11 diff: 8.0.3 @@ -1790,7 +1797,7 @@ snapshots: - ws - zod - '@mariozechner/pi-tui@0.56.3': + '@mariozechner/pi-tui@0.63.1': dependencies: '@types/mime-types': 2.1.4 chalk: 5.6.2 @@ -2166,6 +2173,8 @@ snapshots: '@types/retry@0.12.0': {} + '@types/turndown@5.0.6': {} + '@types/yauzl@2.10.3': dependencies: '@types/node': 25.3.3 diff --git a/pi/files/agent/extensions/session-name.ts b/pi/files/agent/extensions/session-name.ts index 954f12f..80f0b76 100644 --- a/pi/files/agent/extensions/session-name.ts +++ b/pi/files/agent/extensions/session-name.ts @@ -135,11 +135,11 @@ export default function(pi: ExtensionAPI) { // Fire-and-forget: run auto-naming in background without blocking const doAutoName = async () => { - const apiKey = await ctx.modelRegistry.getApiKey(AUTO_NAME_MODEL); - log(`Got API key: ${apiKey ? "yes" : "no"}`); + const auth = await ctx.modelRegistry.getApiKeyAndHeaders(AUTO_NAME_MODEL); + log(`Got API key: ${auth.ok ? "yes" : "no"}`); - if (!apiKey) { - log("No API key available, aborting"); + if (!auth.ok) { + log(`No API key available, aborting: ${auth.error}`); return; } @@ -157,7 +157,7 @@ export default function(pi: ExtensionAPI) { const response = await complete( AUTO_NAME_MODEL, { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] }, - { apiKey }, + { apiKey: auth.apiKey, headers: auth.headers }, ); log(`Response received, stopReason: ${response.stopReason}`); @@ -273,7 +273,8 @@ export default function(pi: ExtensionAPI) { loader.onAbort = () => done(null); const doGenerate = async () => { - const apiKey = await ctx.modelRegistry.getApiKey(AUTO_NAME_MODEL); + const auth = await ctx.modelRegistry.getApiKeyAndHeaders(AUTO_NAME_MODEL); + if (!auth.ok) throw new Error(auth.error); const userMessage: Message = { role: "user", @@ -289,7 +290,7 @@ export default function(pi: ExtensionAPI) { const response = await complete( AUTO_NAME_MODEL, { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] }, - { apiKey, signal: loader.signal }, + { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal }, ); if (response.stopReason === "aborted") { diff --git a/pi/files/agent/extensions/timestamps.ts b/pi/files/agent/extensions/timestamps.ts index 9b68624..7ba7f9e 100644 --- a/pi/files/agent/extensions/timestamps.ts +++ b/pi/files/agent/extensions/timestamps.ts @@ -6,7 +6,7 @@ * - Injects timestamp markers without triggering extra turns */ -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import { Box, Text } from "@mariozechner/pi-tui"; // Track session time @@ -41,12 +41,7 @@ function formatDuration(ms: number): string { } export default function (pi: ExtensionAPI) { - const updateStatus = (ctx: { - ui: { - setStatus: (id: string, text: string | undefined) => void; - theme: { fg: (color: string, text: string) => string }; - }; - }) => { + const updateStatus = (ctx: ExtensionContext) => { const elapsed = Date.now() - sessionStart; let status = ctx.ui.theme.fg("dim", `⏱ ${formatElapsed(elapsed)}`); if (lastTurnDuration !== null) { diff --git a/pi/files/agent/keybindings.json b/pi/files/agent/keybindings.json index 5531cf2..d22eb76 100644 --- a/pi/files/agent/keybindings.json +++ b/pi/files/agent/keybindings.json @@ -1,3 +1,3 @@ { - "selectModel": "ctrl+space" + "app.model.select": "ctrl+space" }