pi-auto-session-title 0.1.0 → 0.1.2
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/index.ts +97 -0
- package/package.json +4 -4
- package/tsconfig.json +2 -2
- package/src/index.ts +0 -91
package/index.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
function textFromContent(content: unknown): string {
|
|
4
|
+
if (typeof content === "string") return content;
|
|
5
|
+
if (!Array.isArray(content)) return "";
|
|
6
|
+
|
|
7
|
+
return content
|
|
8
|
+
.filter(
|
|
9
|
+
(block): block is { type: "text"; text: string } =>
|
|
10
|
+
typeof block === "object" &&
|
|
11
|
+
block !== null &&
|
|
12
|
+
(block as { type?: unknown }).type === "text" &&
|
|
13
|
+
typeof (block as { text?: unknown }).text === "string",
|
|
14
|
+
)
|
|
15
|
+
.map((block) => block.text)
|
|
16
|
+
.join("\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cleanTitle(raw: string): string | undefined {
|
|
20
|
+
const title = raw
|
|
21
|
+
.split("\n")
|
|
22
|
+
.map((line) => line.trim())
|
|
23
|
+
.find(Boolean)
|
|
24
|
+
?.replace(/^(?:session\s+)?title\s*:\s*/i, "")
|
|
25
|
+
.replace(/^#{1,6}\s*/, "")
|
|
26
|
+
.replace(/^["'`]+|["'`]+$/g, "")
|
|
27
|
+
.replace(/[.!?]+$/g, "")
|
|
28
|
+
.trim();
|
|
29
|
+
|
|
30
|
+
if (!title) return undefined;
|
|
31
|
+
return title.length <= 72 ? title : `${title.slice(0, 71).trimEnd()}…`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function (pi: ExtensionAPI) {
|
|
35
|
+
let attempted = false;
|
|
36
|
+
|
|
37
|
+
pi.on("session_start", () => {
|
|
38
|
+
attempted = false;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
42
|
+
if (attempted || pi.getSessionName() || !ctx.model) return;
|
|
43
|
+
attempted = true;
|
|
44
|
+
|
|
45
|
+
const conversation = ctx.sessionManager
|
|
46
|
+
.getBranch()
|
|
47
|
+
.filter(
|
|
48
|
+
(
|
|
49
|
+
entry,
|
|
50
|
+
): entry is typeof entry & {
|
|
51
|
+
message: { role: string; content?: unknown };
|
|
52
|
+
} =>
|
|
53
|
+
entry.type === "message" &&
|
|
54
|
+
"role" in entry.message &&
|
|
55
|
+
(entry.message.role === "user" || entry.message.role === "assistant"),
|
|
56
|
+
)
|
|
57
|
+
.slice(-10)
|
|
58
|
+
.map((entry) => {
|
|
59
|
+
const text = textFromContent(entry.message.content)
|
|
60
|
+
.trim()
|
|
61
|
+
.slice(0, 2_000);
|
|
62
|
+
return `${entry.message.role}: ${text}`;
|
|
63
|
+
})
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.join("\n\n")
|
|
66
|
+
.slice(0, 12_000);
|
|
67
|
+
|
|
68
|
+
if (!conversation) return;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const response = await ctx.modelRegistry.complete(
|
|
72
|
+
ctx.model,
|
|
73
|
+
{
|
|
74
|
+
systemPrompt:
|
|
75
|
+
"Write a specific, searchable 3-7 word title for this coding session. " +
|
|
76
|
+
"Return only the title with no quotes, markdown, or prefix.",
|
|
77
|
+
messages: [
|
|
78
|
+
{
|
|
79
|
+
role: "user",
|
|
80
|
+
content: [{ type: "text", text: conversation }],
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{ maxTokens: 80, signal: AbortSignal.timeout(30_000) },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const title = cleanTitle(textFromContent(response.content));
|
|
89
|
+
if (title && !pi.getSessionName()) {
|
|
90
|
+
pi.setSessionName(title);
|
|
91
|
+
if (ctx.hasUI) ctx.ui.notify(`Session named: ${title}`, "info");
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error("[auto-session-title]", error);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-auto-session-title",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Automatically generates concise, descriptive session titles for Pi coding agent sessions",
|
|
5
5
|
"author": "Shakthi Sagar",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/shakthi-sagar/pi-extensions/issues"
|
|
15
15
|
},
|
|
16
16
|
"type": "module",
|
|
17
|
-
"main": "./
|
|
17
|
+
"main": "./index.ts",
|
|
18
18
|
"keywords": [
|
|
19
19
|
"pi-package",
|
|
20
20
|
"pi-extension",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"LICENSE",
|
|
27
27
|
"README.md",
|
|
28
|
-
"
|
|
28
|
+
"index.ts",
|
|
29
29
|
"tsconfig.json"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"pi": {
|
|
35
35
|
"extensions": [
|
|
36
|
-
"./
|
|
36
|
+
"./index.ts"
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
package/tsconfig.json
CHANGED
package/src/index.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
function textFromContent(content: unknown): string {
|
|
4
|
-
if (typeof content === "string") return content;
|
|
5
|
-
if (!Array.isArray(content)) return "";
|
|
6
|
-
|
|
7
|
-
return content
|
|
8
|
-
.filter(
|
|
9
|
-
(block): block is { type: "text"; text: string } =>
|
|
10
|
-
typeof block === "object" &&
|
|
11
|
-
block !== null &&
|
|
12
|
-
(block as { type?: unknown }).type === "text" &&
|
|
13
|
-
typeof (block as { text?: unknown }).text === "string",
|
|
14
|
-
)
|
|
15
|
-
.map((block) => block.text)
|
|
16
|
-
.join("\n");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function cleanTitle(raw: string): string | undefined {
|
|
20
|
-
const title = raw
|
|
21
|
-
.split("\n")
|
|
22
|
-
.map((line) => line.trim())
|
|
23
|
-
.find(Boolean)
|
|
24
|
-
?.replace(/^(?:session\s+)?title\s*:\s*/i, "")
|
|
25
|
-
.replace(/^#{1,6}\s*/, "")
|
|
26
|
-
.replace(/^["'`]+|["'`]+$/g, "")
|
|
27
|
-
.replace(/[.!?]+$/g, "")
|
|
28
|
-
.trim();
|
|
29
|
-
|
|
30
|
-
if (!title) return undefined;
|
|
31
|
-
return title.length <= 72 ? title : `${title.slice(0, 71).trimEnd()}…`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default function (pi: ExtensionAPI) {
|
|
35
|
-
let attempted = false;
|
|
36
|
-
|
|
37
|
-
pi.on("session_start", () => {
|
|
38
|
-
attempted = false;
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
pi.on("agent_settled", async (_event, ctx) => {
|
|
42
|
-
if (attempted || pi.getSessionName() || !ctx.model) return;
|
|
43
|
-
attempted = true;
|
|
44
|
-
|
|
45
|
-
const conversation = ctx.sessionManager
|
|
46
|
-
.getBranch()
|
|
47
|
-
.filter(
|
|
48
|
-
(entry): entry is typeof entry & { message: { role: string; content?: unknown } } =>
|
|
49
|
-
entry.type === "message" &&
|
|
50
|
-
"role" in entry.message &&
|
|
51
|
-
(entry.message.role === "user" || entry.message.role === "assistant"),
|
|
52
|
-
)
|
|
53
|
-
.slice(-10)
|
|
54
|
-
.map((entry) => {
|
|
55
|
-
const text = textFromContent(entry.message.content).trim().slice(0, 2_000);
|
|
56
|
-
return `${entry.message.role}: ${text}`;
|
|
57
|
-
})
|
|
58
|
-
.filter(Boolean)
|
|
59
|
-
.join("\n\n")
|
|
60
|
-
.slice(0, 12_000);
|
|
61
|
-
|
|
62
|
-
if (!conversation) return;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const response = await ctx.modelRegistry.complete(
|
|
66
|
-
ctx.model,
|
|
67
|
-
{
|
|
68
|
-
systemPrompt:
|
|
69
|
-
"Write a specific, searchable 3-7 word title for this coding session. " +
|
|
70
|
-
"Return only the title with no quotes, markdown, or prefix.",
|
|
71
|
-
messages: [
|
|
72
|
-
{
|
|
73
|
-
role: "user",
|
|
74
|
-
content: [{ type: "text", text: conversation }],
|
|
75
|
-
timestamp: Date.now(),
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
{ maxTokens: 80, signal: AbortSignal.timeout(30_000) },
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const title = cleanTitle(textFromContent(response.content));
|
|
83
|
-
if (title && !pi.getSessionName()) {
|
|
84
|
-
pi.setSessionName(title);
|
|
85
|
-
if (ctx.hasUI) ctx.ui.notify(`Session named: ${title}`, "info");
|
|
86
|
-
}
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error("[auto-session-title]", error);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|