pi-codebuddy-sdk 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +5 -9
- package/src/provider.ts +4 -77
- package/src/provider-debug.patch +0 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Two files: index.ts (registration + model discovery), provider.ts (streaming).
|
|
11
11
|
*/
|
|
12
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import type { ProviderApi
|
|
13
|
+
import type { ProviderApi } from "@earendil-works/pi-ai/compat";
|
|
14
14
|
import { streamCodebuddy } from "./provider.js";
|
|
15
15
|
|
|
16
16
|
// ── provider registration ──
|
|
@@ -25,7 +25,6 @@ export default function activate(api: ExtensionAPI) {
|
|
|
25
25
|
streamSimple: streamCodebuddy,
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
// Lazy model discovery at startup
|
|
29
28
|
queueMicrotask(async () => {
|
|
30
29
|
try {
|
|
31
30
|
const models = await discoverModels();
|
|
@@ -38,8 +37,7 @@ export default function activate(api: ExtensionAPI) {
|
|
|
38
37
|
streamSimple: streamCodebuddy,
|
|
39
38
|
});
|
|
40
39
|
} catch {
|
|
41
|
-
// Model discovery failed — provider
|
|
42
|
-
// user can still try manually.
|
|
40
|
+
// Model discovery failed — provider stays with empty models, user can try manually.
|
|
43
41
|
}
|
|
44
42
|
});
|
|
45
43
|
}
|
|
@@ -52,13 +50,13 @@ async function getSdk() {
|
|
|
52
50
|
return _sdk;
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
async function discoverModels(): Promise<ProviderApi["models"]> {
|
|
56
54
|
const sdk = await getSdk();
|
|
57
55
|
const q = sdk.query({ prompt: " ", options: { maxTurns: 0 } });
|
|
58
56
|
const supported = await q.supportedModels();
|
|
59
57
|
await q.interrupt();
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
return supported.map((m) => {
|
|
62
60
|
const caps = detectCapabilities(m.id);
|
|
63
61
|
return {
|
|
64
62
|
id: m.id,
|
|
@@ -72,11 +70,9 @@ export async function discoverModels(): Promise<ProviderApi["models"]> {
|
|
|
72
70
|
maxTokens: caps.maxTokens,
|
|
73
71
|
};
|
|
74
72
|
});
|
|
75
|
-
|
|
76
|
-
return models;
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
// ── capability detection
|
|
75
|
+
// ── capability detection ──
|
|
80
76
|
|
|
81
77
|
const THINKING_PATTERNS = [/claude/i, /gemini/i, /gpt-5/i];
|
|
82
78
|
const IMAGE_PATTERNS = [/claude/i, /gemini/i, /gpt/i];
|
package/src/provider.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Provider entry point — pi-claude-bridge pattern.
|
|
3
3
|
*
|
|
4
4
|
* For each Pi turn:
|
|
5
|
-
* 1. Extract the latest user message
|
|
5
|
+
* 1. Extract the latest user message
|
|
6
6
|
* 2. Call sdk.query() — stateless, CodeBuddy uses its own system prompt
|
|
7
7
|
* 3. Stream events back to Pi (text, thinking, toolcalls)
|
|
8
8
|
*/
|
|
@@ -54,39 +54,16 @@ function makeErrorPartial(model: Model<any>, error: unknown): AssistantMessage {
|
|
|
54
54
|
|
|
55
55
|
// ── extract user message ──
|
|
56
56
|
|
|
57
|
-
/**
|
|
58
|
-
* Extract the latest user message from Pi Context.
|
|
59
|
-
* Strips context-mode session state injection.
|
|
60
|
-
*
|
|
61
|
-
* Like pi-claude-bridge's extractUserPrompt() but with context-mode defense.
|
|
62
|
-
*/
|
|
63
57
|
function extractUserMessage(context: Context): string {
|
|
64
58
|
const msgs = context.messages;
|
|
65
|
-
|
|
66
|
-
// Pi injects context-mode state as the LAST user message (after the real one).
|
|
67
|
-
// Search from the end, skip obvious context-mode injections.
|
|
68
59
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
69
60
|
if (msgs[i].role !== "user") continue;
|
|
70
61
|
const raw = contentToText((msgs[i] as any).content);
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
// Skip messages that are entirely context-mode injection
|
|
74
|
-
if (isContextModeInjection(raw)) continue;
|
|
75
|
-
|
|
76
|
-
return stripPiInjection(raw);
|
|
62
|
+
if (raw) return raw.trim();
|
|
77
63
|
}
|
|
78
64
|
return "Continue.";
|
|
79
65
|
}
|
|
80
66
|
|
|
81
|
-
/** Detect if a user message is a Pi context-mode state injection (not a real message). */
|
|
82
|
-
function isContextModeInjection(text: string): boolean {
|
|
83
|
-
const t = text.trim();
|
|
84
|
-
if (t.startsWith("context-mode active.")) return true;
|
|
85
|
-
if (t.includes("<session_state") || t.includes("<session_mode")) return true;
|
|
86
|
-
if (t.startsWith("<summary>") && t.includes("</summary>")) return true;
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
67
|
function contentToText(content: unknown): string {
|
|
91
68
|
if (typeof content === "string") return content;
|
|
92
69
|
if (!Array.isArray(content)) return "";
|
|
@@ -96,55 +73,6 @@ function contentToText(content: unknown): string {
|
|
|
96
73
|
.join("\n");
|
|
97
74
|
}
|
|
98
75
|
|
|
99
|
-
/**
|
|
100
|
-
* Strip context-mode session state that Pi prepends to user messages.
|
|
101
|
-
*
|
|
102
|
-
* Pi injects several formats:
|
|
103
|
-
* context-mode active. Hierarchy: ...
|
|
104
|
-
* <session_state source="compaction"> ... </session_state>
|
|
105
|
-
* <summary>Compacted: ...</summary>
|
|
106
|
-
*
|
|
107
|
-
* Strip all of them, keep only the actual user message.
|
|
108
|
-
*/
|
|
109
|
-
function stripPiInjection(text: string): string {
|
|
110
|
-
// Fast path
|
|
111
|
-
if (!text.includes("context-mode") && !text.includes("<session_state") && !text.includes("<summary>")) {
|
|
112
|
-
return text;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 1. Remove <summary> blocks (compaction summaries contain context-mode state)
|
|
116
|
-
text = text.replace(/<summary>[\s\S]*?<\/summary>\s*/g, "");
|
|
117
|
-
|
|
118
|
-
// 2. Remove <session_state> block and everything between "context-mode" and </session_state>
|
|
119
|
-
const sessionEnd = text.lastIndexOf("</session_state>");
|
|
120
|
-
if (sessionEnd >= 0) {
|
|
121
|
-
const after = text.slice(sessionEnd + "</session_state>".length).trimStart();
|
|
122
|
-
if (after) {
|
|
123
|
-
text = after;
|
|
124
|
-
} else {
|
|
125
|
-
// All content was in the session_state block
|
|
126
|
-
const before = text.slice(0, text.indexOf("<session_state")).trim();
|
|
127
|
-
if (before) text = before;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 3. Remove "context-mode active." line and everything after until a blank line
|
|
132
|
-
// Pattern: "context-mode active. ... Hierarchy: ..." followed by blank line, then real message
|
|
133
|
-
const cmRe = /context-mode active\.[\s\S]*?\n\s*\n([\s\S]*)$/;
|
|
134
|
-
const cmMatch = text.match(cmRe);
|
|
135
|
-
if (cmMatch?.[1]?.trim()) {
|
|
136
|
-
text = cmMatch[1].trim();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 4. If "context-mode" is still the only content, return empty
|
|
140
|
-
if (text.trim() === "context-mode active." || text.trim().startsWith("context-mode active.")) {
|
|
141
|
-
const afterCm = text.replace(/context-mode active\.[\s\S]*/, "").trim();
|
|
142
|
-
if (afterCm) text = afterCm;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return text.trim() || text;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
76
|
// ── thinking config ──
|
|
149
77
|
|
|
150
78
|
const THINKING_BUDGETS: Record<string, number> = {
|
|
@@ -228,7 +156,7 @@ export function streamCodebuddy(
|
|
|
228
156
|
if (options.signal.aborted) { await onAbort(); return; }
|
|
229
157
|
}
|
|
230
158
|
|
|
231
|
-
// ── per-block tracking
|
|
159
|
+
// ── per-block tracking ──
|
|
232
160
|
type BlockKind = "text" | "thinking" | "tool_use" | "redacted_thinking";
|
|
233
161
|
const blockKind = new Map<number, BlockKind>();
|
|
234
162
|
const blockAccum = new Map<number, string>();
|
|
@@ -304,7 +232,6 @@ export function streamCodebuddy(
|
|
|
304
232
|
continue;
|
|
305
233
|
}
|
|
306
234
|
|
|
307
|
-
// Token-level streaming via stream_event (preferred)
|
|
308
235
|
if (msg.type === "stream_event") {
|
|
309
236
|
hasStreamEvents = true;
|
|
310
237
|
const evt = msg.event;
|
|
@@ -326,7 +253,7 @@ export function streamCodebuddy(
|
|
|
326
253
|
continue;
|
|
327
254
|
}
|
|
328
255
|
|
|
329
|
-
// Fallback: full assistant messages
|
|
256
|
+
// Fallback: full assistant messages (when model doesn't support streaming)
|
|
330
257
|
if (msg.type === "assistant" && !hasStreamEvents) {
|
|
331
258
|
let ci = 0;
|
|
332
259
|
for (const block of msg.message.content) {
|
package/src/provider-debug.patch
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Temporary: add debug logging to extractUserMessage
|