@fickydev/pigent 0.1.6 → 0.1.8
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/CHANGELOG.md +6 -0
- package/PLAN.md +64 -1
- package/TODO.md +34 -1
- package/agents/assistant/agent.yaml +2 -0
- package/drizzle/migrations/0001_session_model_overrides.sql +2 -0
- package/drizzle/migrations/meta/0001_snapshot.json +519 -0
- package/drizzle/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/pigent.yaml +9 -0
- package/profiles/assistant.yaml +1 -0
- package/src/agents/AgentRunner.ts +8 -2
- package/src/agents/BotCommandHandler.ts +210 -1
- package/src/channels/telegram/TelegramApi.ts +172 -14
- package/src/channels/telegram/TelegramPollingAdapter.ts +45 -2
- package/src/channels/telegram/types.ts +20 -0
- package/src/channels/types.ts +6 -0
- package/src/config/loadConfig.ts +1 -0
- package/src/config/schemas.ts +14 -1
- package/src/daemon/AgentDaemon.ts +18 -2
- package/src/db/repositories/SessionRepository.ts +33 -0
- package/src/db/schema.ts +2 -0
- package/src/pi/PiAgentRunner.ts +6 -0
- package/src/pi/PiModelResolver.ts +76 -0
|
@@ -24,7 +24,7 @@ export class AgentDaemon {
|
|
|
24
24
|
) {
|
|
25
25
|
this.adapters = createAdapters(repositories);
|
|
26
26
|
this.registry = new AgentRegistry(config, repositories);
|
|
27
|
-
this.commands = new BotCommandHandler(this.registry, repositories);
|
|
27
|
+
this.commands = new BotCommandHandler(this.registry, repositories, config.modelChoices);
|
|
28
28
|
this.router = new MessageRouter(repositories, this.registry);
|
|
29
29
|
this.runner = new AgentRunner(this.registry, repositories);
|
|
30
30
|
}
|
|
@@ -92,6 +92,7 @@ export class AgentDaemon {
|
|
|
92
92
|
chatId: message.chatId,
|
|
93
93
|
threadId: message.threadId,
|
|
94
94
|
text: command.text,
|
|
95
|
+
inlineKeyboard: command.inlineKeyboard,
|
|
95
96
|
});
|
|
96
97
|
return;
|
|
97
98
|
}
|
|
@@ -150,13 +151,28 @@ function createAdapters(repositories: Repositories): ChannelAdapter[] {
|
|
|
150
151
|
if (telegramToken) {
|
|
151
152
|
adapters.push(
|
|
152
153
|
new TelegramPollingAdapter({
|
|
153
|
-
api: new TelegramApi({
|
|
154
|
+
api: new TelegramApi({
|
|
155
|
+
token: telegramToken,
|
|
156
|
+
maxAttempts: Number(process.env.TELEGRAM_API_MAX_ATTEMPTS ?? 3),
|
|
157
|
+
baseRetryDelayMs: Number(process.env.TELEGRAM_API_BASE_RETRY_DELAY_MS ?? 500),
|
|
158
|
+
}),
|
|
154
159
|
runtimeKv: repositories.runtimeKv,
|
|
155
160
|
pollTimeoutSeconds: Number(process.env.TELEGRAM_POLL_TIMEOUT_SECONDS ?? 30),
|
|
156
161
|
pollIntervalMs: Number(process.env.TELEGRAM_POLL_INTERVAL_MS ?? 1000),
|
|
162
|
+
commands: telegramCommands(),
|
|
157
163
|
}),
|
|
158
164
|
);
|
|
159
165
|
}
|
|
160
166
|
|
|
161
167
|
return adapters;
|
|
162
168
|
}
|
|
169
|
+
|
|
170
|
+
function telegramCommands() {
|
|
171
|
+
return [
|
|
172
|
+
{ command: "help", description: "Show Pigent commands" },
|
|
173
|
+
{ command: "agents", description: "List agents available in this chat" },
|
|
174
|
+
{ command: "model", description: "Choose model for this chat session" },
|
|
175
|
+
{ command: "thinking", description: "Choose thinking level for this chat session" },
|
|
176
|
+
{ command: "agent", description: "Send message to a specific agent" },
|
|
177
|
+
];
|
|
178
|
+
}
|
|
@@ -3,6 +3,8 @@ import { nanoid } from "nanoid";
|
|
|
3
3
|
import type { DbClient } from "../client";
|
|
4
4
|
import { agentSessions, type AgentSessionRow } from "../schema";
|
|
5
5
|
|
|
6
|
+
export type SessionThinkingLevel = "off" | "low" | "medium" | "high";
|
|
7
|
+
|
|
6
8
|
export type SessionKey = {
|
|
7
9
|
agentId: string;
|
|
8
10
|
channel: string;
|
|
@@ -38,6 +40,30 @@ export class SessionRepository {
|
|
|
38
40
|
return created;
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
async updateModel(id: string, model: string | null): Promise<AgentSessionRow> {
|
|
44
|
+
await this.db
|
|
45
|
+
.update(agentSessions)
|
|
46
|
+
.set({
|
|
47
|
+
model,
|
|
48
|
+
updatedAt: Date.now(),
|
|
49
|
+
})
|
|
50
|
+
.where(eq(agentSessions.id, id));
|
|
51
|
+
|
|
52
|
+
return this.requireById(id);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async updateThinkingLevel(id: string, thinkingLevel: SessionThinkingLevel | null): Promise<AgentSessionRow> {
|
|
56
|
+
await this.db
|
|
57
|
+
.update(agentSessions)
|
|
58
|
+
.set({
|
|
59
|
+
thinkingLevel,
|
|
60
|
+
updatedAt: Date.now(),
|
|
61
|
+
})
|
|
62
|
+
.where(eq(agentSessions.id, id));
|
|
63
|
+
|
|
64
|
+
return this.requireById(id);
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
async findById(id: string): Promise<AgentSessionRow | null> {
|
|
42
68
|
const row = await this.db.query.agentSessions.findFirst({
|
|
43
69
|
where: eq(agentSessions.id, id),
|
|
@@ -46,6 +72,13 @@ export class SessionRepository {
|
|
|
46
72
|
return row ?? null;
|
|
47
73
|
}
|
|
48
74
|
|
|
75
|
+
private async requireById(id: string): Promise<AgentSessionRow> {
|
|
76
|
+
const row = await this.findById(id);
|
|
77
|
+
if (!row) throw new Error(`session not found ${id}`);
|
|
78
|
+
|
|
79
|
+
return row;
|
|
80
|
+
}
|
|
81
|
+
|
|
49
82
|
private async findByKey(key: SessionKey): Promise<AgentSessionRow | null> {
|
|
50
83
|
const threadPredicate = key.threadId
|
|
51
84
|
? eq(agentSessions.threadId, key.threadId)
|
package/src/db/schema.ts
CHANGED
|
@@ -48,6 +48,8 @@ export const agentSessions = sqliteTable(
|
|
|
48
48
|
userId: text("user_id"),
|
|
49
49
|
piSessionId: text("pi_session_id"),
|
|
50
50
|
instructionsHash: text("instructions_hash"),
|
|
51
|
+
model: text("model"),
|
|
52
|
+
thinkingLevel: text("thinking_level", { enum: ["off", "low", "medium", "high"] }),
|
|
51
53
|
createdAt: integer("created_at").notNull(),
|
|
52
54
|
updatedAt: integer("updated_at").notNull(),
|
|
53
55
|
},
|
package/src/pi/PiAgentRunner.ts
CHANGED
|
@@ -10,10 +10,13 @@ import {
|
|
|
10
10
|
import { mkdir } from "node:fs/promises";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
import type { LoadedAgentConfig, ProfileConfig } from "../config/schemas";
|
|
13
|
+
import type { AgentSessionRow } from "../db/schema";
|
|
14
|
+
import { resolveModelSelection } from "./PiModelResolver";
|
|
13
15
|
|
|
14
16
|
export type PiAgentRunInput = {
|
|
15
17
|
agent: LoadedAgentConfig;
|
|
16
18
|
profile: ProfileConfig | null;
|
|
19
|
+
session: AgentSessionRow;
|
|
17
20
|
prompt: string;
|
|
18
21
|
};
|
|
19
22
|
|
|
@@ -37,10 +40,13 @@ export class PiAgentRunner {
|
|
|
37
40
|
|
|
38
41
|
const authStorage = AuthStorage.create();
|
|
39
42
|
const modelRegistry = ModelRegistry.create(authStorage);
|
|
43
|
+
const modelSelection = resolveModelSelection(modelRegistry, [input.session, input.agent, input.profile ?? {}]);
|
|
40
44
|
const { session } = await createAgentSession({
|
|
41
45
|
cwd: workspace,
|
|
42
46
|
authStorage,
|
|
43
47
|
modelRegistry,
|
|
48
|
+
model: modelSelection.model,
|
|
49
|
+
thinkingLevel: modelSelection.thinkingLevel,
|
|
44
50
|
resourceLoader,
|
|
45
51
|
sessionManager: SessionManager.create(workspace),
|
|
46
52
|
settingsManager,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { logger } from "../logging/logger";
|
|
5
|
+
|
|
6
|
+
export type ModelSelectionConfig = {
|
|
7
|
+
model?: string | null;
|
|
8
|
+
thinkingLevel?: ThinkingLevel | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ResolvedModelSelection = {
|
|
12
|
+
model?: Model<any>;
|
|
13
|
+
thinkingLevel?: ThinkingLevel;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function resolveModelSelection(
|
|
17
|
+
modelRegistry: ModelRegistry,
|
|
18
|
+
configs: ModelSelectionConfig[],
|
|
19
|
+
): ResolvedModelSelection {
|
|
20
|
+
const configuredModel = firstConfigured(configs.map((config) => config.model));
|
|
21
|
+
const thinkingLevel = firstConfigured(configs.map((config) => config.thinkingLevel));
|
|
22
|
+
const resolved: ResolvedModelSelection = {};
|
|
23
|
+
|
|
24
|
+
if (configuredModel) {
|
|
25
|
+
const model = resolveModel(modelRegistry, configuredModel);
|
|
26
|
+
if (model) {
|
|
27
|
+
resolved.model = model;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (thinkingLevel) {
|
|
32
|
+
resolved.thinkingLevel = thinkingLevel;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveModel(modelRegistry: ModelRegistry, modelRef: string): Model<any> | undefined {
|
|
39
|
+
const parsed = parseModelRef(modelRef);
|
|
40
|
+
|
|
41
|
+
if (!parsed) {
|
|
42
|
+
logger.warn("invalid model reference; expected provider/modelId", { model: modelRef });
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const model = modelRegistry.find(parsed.provider, parsed.modelId);
|
|
47
|
+
|
|
48
|
+
if (!model) {
|
|
49
|
+
logger.warn("configured model not found; falling back to Pi default", { model: modelRef });
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return model;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isValidModelRef(modelRef: string): boolean {
|
|
57
|
+
return parseModelRef(modelRef) !== null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseModelRef(modelRef: string): { provider: string; modelId: string } | null {
|
|
61
|
+
const trimmed = modelRef.trim();
|
|
62
|
+
const separatorIndex = trimmed.indexOf("/");
|
|
63
|
+
|
|
64
|
+
if (separatorIndex <= 0 || separatorIndex === trimmed.length - 1) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
provider: trimmed.slice(0, separatorIndex),
|
|
70
|
+
modelId: trimmed.slice(separatorIndex + 1),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function firstConfigured<T>(values: Array<T | null | undefined>): T | undefined {
|
|
75
|
+
return values.find((value): value is T => value !== null && value !== undefined && value !== "");
|
|
76
|
+
}
|