From 4479683cf44980df8b7d670b85c87544138ffa6d Mon Sep 17 00:00:00 2001 From: mptyl Date: Thu, 2 Jul 2026 11:49:11 +0200 Subject: [PATCH] chore(pi): scope AritmoLab provider to project, drop Gemma model Move the AritmoLab provider (Qwen) into harness/.pi/extensions/ so it is auto-discovered only when Pi runs with cwd=harness, keeping it project-local. GLM (~/.pi/agent/models.json) and DeepSeek (built-in) stay user-wide. File is .js (not .mjs) because Pi extension auto-discovery matches only /\.(ts|js)$/. Removes the gemma4-26b-a4b model (404 at the endpoint) from both the provider and the model-matrix default list. Co-Authored-By: Claude Fable 5 --- harness/.pi/extensions/aritmolab-provider.js | 96 ++++++++++++++++++++ harness/scripts/model-matrix.mjs | 2 +- 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 harness/.pi/extensions/aritmolab-provider.js diff --git a/harness/.pi/extensions/aritmolab-provider.js b/harness/.pi/extensions/aritmolab-provider.js new file mode 100644 index 00000000..f9f43f08 --- /dev/null +++ b/harness/.pi/extensions/aritmolab-provider.js @@ -0,0 +1,96 @@ +import { createAssistantMessageEventStream, streamSimpleOpenAICompletions } from "@mariozechner/pi-ai"; + +function convertThinkingBlocks(message) { + return { + ...message, + content: (message.content ?? []).map((block) => { + if (block?.type !== "thinking") return block; + return { type: "text", text: block.thinking ?? "" }; + }), + }; +} + +function convertEvent(event) { + if (event.type === "thinking_start") { + return { type: "text_start", contentIndex: event.contentIndex, partial: convertThinkingBlocks(event.partial) }; + } + if (event.type === "thinking_delta") { + return { type: "text_delta", contentIndex: event.contentIndex, delta: event.delta, partial: convertThinkingBlocks(event.partial) }; + } + if (event.type === "thinking_end") { + return { type: "text_end", contentIndex: event.contentIndex, content: event.content, partial: convertThinkingBlocks(event.partial) }; + } + if (event.type === "done") { + return { ...event, message: convertThinkingBlocks(event.message) }; + } + if (event.type === "error") { + return { ...event, error: convertThinkingBlocks(event.error) }; + } + if (event.partial) { + return { ...event, partial: convertThinkingBlocks(event.partial) }; + } + return event; +} + +function streamAritmolab(model, context, options) { + const source = streamSimpleOpenAICompletions(model, context, options); + const stream = createAssistantMessageEventStream(); + + (async () => { + try { + for await (const event of source) { + stream.push(convertEvent(event)); + } + } catch (error) { + const message = { + role: "assistant", + content: [], + api: model.api, + provider: model.provider, + model: model.id, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: options?.signal?.aborted ? "aborted" : "error", + errorMessage: error instanceof Error ? error.message : String(error), + timestamp: Date.now(), + }; + stream.push({ type: "error", reason: message.stopReason, error: message }); + stream.end(); + } + })(); + + return stream; +} + +export default function (pi) { + pi.registerProvider("aritmolab", { + name: "AritmoLab", + baseUrl: "https://ml-aritmolab.policlinicosandonato.it/v1", + apiKey: "aritmolab", + api: "openai-completions", + streamSimple: streamAritmolab, + models: [ + { + id: "qwen3.6-35b-a3b", + name: "AritmoLab Qwen3.6 35B A3B", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 131072, + maxTokens: 16384, + compat: { + supportsDeveloperRole: false, + supportsReasoningEffort: false, + supportsStore: false, + maxTokensField: "max_tokens", + }, + }, + ], + }); +} diff --git a/harness/scripts/model-matrix.mjs b/harness/scripts/model-matrix.mjs index f0f7c0f1..f555ce61 100644 --- a/harness/scripts/model-matrix.mjs +++ b/harness/scripts/model-matrix.mjs @@ -32,7 +32,7 @@ if (!sessionId) { process.exit(2); } const models = (process.argv[3] ?? - "zai/glm-5.2,deepseek/deepseek-v4-pro,aritmolab/qwen3.6-35b-a3b,aritmolab/gemma4-26b-a4b") + "zai/glm-5.2,deepseek/deepseek-v4-pro,aritmolab/qwen3.6-35b-a3b") .split(",").map((s) => s.trim()).filter(Boolean); const thinking = process.argv[4] ?? "medium"; const outDir = process.argv[5] ?? "/tmp/model-matrix";