@pedrohnas/opencode-telegram 1.2.0 → 1.3.0
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/.claude/skills/playwright-cli/data/page-2026-02-09T01-41-55-194Z.yml +36 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-42-17-115Z.yml +36 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-43-15-988Z.yml +26 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-43-26-107Z.yml +26 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-45-03-139Z.yml +29 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-45-21-579Z.yml +29 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-45-48-051Z.yml +30 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-46-27-632Z.yml +33 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-46-46-519Z.yml +33 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-47-28-491Z.yml +349 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-47-34-834Z.yml +349 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-47-54-066Z.yml +168 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-48-19-667Z.yml +219 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-49-32-311Z.yml +221 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-49-57-109Z.yml +230 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-50-24-052Z.yml +235 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-50-41-148Z.yml +248 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-51-10-916Z.yml +234 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-51-28-271Z.yml +234 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-52-32-324Z.yml +234 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-52-47-801Z.yml +196 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-56-07-361Z.yml +203 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-56-35-534Z.yml +49 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-57-04-658Z.yml +52 -0
- package/docs/AUDIT.md +193 -0
- package/docs/PROGRESS.md +188 -0
- package/docs/plans/phase-5.md +410 -0
- package/docs/plans/phase-6.5.md +426 -0
- package/docs/plans/phase-6.md +349 -0
- package/e2e/helpers.ts +34 -0
- package/e2e/phase-5.test.ts +295 -0
- package/e2e/phase-6.5.test.ts +239 -0
- package/e2e/phase-6.test.ts +302 -0
- package/package.json +6 -3
- package/src/api-server.test.ts +309 -0
- package/src/api-server.ts +201 -0
- package/src/bot.test.ts +354 -0
- package/src/bot.ts +200 -2
- package/src/config.test.ts +16 -0
- package/src/config.ts +4 -0
- package/src/event-bus.test.ts +337 -1
- package/src/event-bus.ts +83 -3
- package/src/handlers/agents.test.ts +122 -0
- package/src/handlers/agents.ts +93 -0
- package/src/handlers/media.test.ts +264 -0
- package/src/handlers/media.ts +168 -0
- package/src/handlers/models.test.ts +319 -0
- package/src/handlers/models.ts +191 -0
- package/src/index.ts +15 -0
- package/src/send/draft-stream.test.ts +76 -0
- package/src/send/draft-stream.ts +13 -1
- package/src/session-manager.test.ts +46 -0
- package/src/session-manager.ts +10 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock } from "bun:test"
|
|
2
|
+
import { createApiApp, type ApiDeps } from "./api-server"
|
|
3
|
+
import { SessionManager } from "./session-manager"
|
|
4
|
+
import { TurnManager } from "./turn-manager"
|
|
5
|
+
|
|
6
|
+
function createMockSdk() {
|
|
7
|
+
return {
|
|
8
|
+
provider: {
|
|
9
|
+
list: mock(async () => ({
|
|
10
|
+
data: {
|
|
11
|
+
all: [
|
|
12
|
+
{
|
|
13
|
+
id: "anthropic",
|
|
14
|
+
name: "Anthropic",
|
|
15
|
+
models: {
|
|
16
|
+
"opus-4.6": {
|
|
17
|
+
id: "opus-4.6",
|
|
18
|
+
name: "Claude Opus 4.6",
|
|
19
|
+
family: "opus",
|
|
20
|
+
release_date: "2025-12-01",
|
|
21
|
+
},
|
|
22
|
+
"opus-4.5": {
|
|
23
|
+
id: "opus-4.5",
|
|
24
|
+
name: "Claude Opus 4.5",
|
|
25
|
+
family: "opus",
|
|
26
|
+
release_date: "2025-09-01",
|
|
27
|
+
},
|
|
28
|
+
"sonnet-4.5": {
|
|
29
|
+
id: "sonnet-4.5",
|
|
30
|
+
name: "Claude Sonnet 4.5",
|
|
31
|
+
family: "sonnet",
|
|
32
|
+
release_date: "2025-09-01",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
connected: ["anthropic"],
|
|
38
|
+
default: { anthropic: "sonnet-4.5" },
|
|
39
|
+
},
|
|
40
|
+
})),
|
|
41
|
+
},
|
|
42
|
+
app: {
|
|
43
|
+
agents: mock(async () => ({
|
|
44
|
+
data: [
|
|
45
|
+
{ name: "code", description: "Coding agent" },
|
|
46
|
+
{ name: "build", description: "Build agent" },
|
|
47
|
+
{ name: "hidden-agent", description: "Secret", hidden: true },
|
|
48
|
+
],
|
|
49
|
+
})),
|
|
50
|
+
},
|
|
51
|
+
session: {
|
|
52
|
+
create: mock(async (params: any) => ({
|
|
53
|
+
data: {
|
|
54
|
+
id: "new-session-id",
|
|
55
|
+
title: params.title,
|
|
56
|
+
directory: "/tmp",
|
|
57
|
+
},
|
|
58
|
+
})),
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createDeps(sdkOverride?: any): { deps: ApiDeps; sm: SessionManager; tm: TurnManager } {
|
|
64
|
+
const sm = new SessionManager({ maxEntries: 100, ttlMs: 60000 })
|
|
65
|
+
const tm = new TurnManager()
|
|
66
|
+
const sdk = sdkOverride ?? createMockSdk()
|
|
67
|
+
return { deps: { sessionManager: sm, turnManager: tm, sdk: sdk as any }, sm, tm }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Session endpoints ---
|
|
71
|
+
|
|
72
|
+
describe("GET /api/sessions", () => {
|
|
73
|
+
test("returns all active sessions", async () => {
|
|
74
|
+
const { deps, sm } = createDeps()
|
|
75
|
+
sm.set("123", { sessionId: "s1", directory: "/tmp", modelOverride: { providerID: "anthropic", modelID: "opus" } })
|
|
76
|
+
sm.set("456", { sessionId: "s2", directory: "/tmp", agentOverride: "code" })
|
|
77
|
+
|
|
78
|
+
const app = createApiApp(deps)
|
|
79
|
+
const res = await app.request("/api/sessions")
|
|
80
|
+
expect(res.status).toBe(200)
|
|
81
|
+
|
|
82
|
+
const data = await res.json()
|
|
83
|
+
expect(data.length).toBe(2)
|
|
84
|
+
expect(data.find((s: any) => s.chatId === "123").model).toEqual({ providerID: "anthropic", modelID: "opus" })
|
|
85
|
+
expect(data.find((s: any) => s.chatId === "456").agent).toBe("code")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test("returns empty array when no sessions", async () => {
|
|
89
|
+
const { deps } = createDeps()
|
|
90
|
+
const app = createApiApp(deps)
|
|
91
|
+
const res = await app.request("/api/sessions")
|
|
92
|
+
const data = await res.json()
|
|
93
|
+
expect(data).toEqual([])
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe("GET /api/session/:sessionId", () => {
|
|
98
|
+
test("returns session info", async () => {
|
|
99
|
+
const { deps, sm, tm } = createDeps()
|
|
100
|
+
sm.set("123", {
|
|
101
|
+
sessionId: "s1",
|
|
102
|
+
directory: "/tmp",
|
|
103
|
+
modelOverride: { providerID: "google", modelID: "gemini" },
|
|
104
|
+
agentOverride: "build",
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const app = createApiApp(deps)
|
|
108
|
+
const res = await app.request("/api/session/s1")
|
|
109
|
+
expect(res.status).toBe(200)
|
|
110
|
+
|
|
111
|
+
const data = await res.json()
|
|
112
|
+
expect(data.chatId).toBe("123")
|
|
113
|
+
expect(data.sessionId).toBe("s1")
|
|
114
|
+
expect(data.model).toEqual({ providerID: "google", modelID: "gemini" })
|
|
115
|
+
expect(data.agent).toBe("build")
|
|
116
|
+
expect(data.hasTurn).toBe(false)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("returns 404 for unknown session", async () => {
|
|
120
|
+
const { deps } = createDeps()
|
|
121
|
+
const app = createApiApp(deps)
|
|
122
|
+
const res = await app.request("/api/session/nonexistent")
|
|
123
|
+
expect(res.status).toBe(404)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test("shows hasTurn=true when turn is active", async () => {
|
|
127
|
+
const { deps, sm, tm } = createDeps()
|
|
128
|
+
sm.set("123", { sessionId: "s1", directory: "/tmp" })
|
|
129
|
+
tm.start("s1", 123)
|
|
130
|
+
|
|
131
|
+
const app = createApiApp(deps)
|
|
132
|
+
const res = await app.request("/api/session/s1")
|
|
133
|
+
const data = await res.json()
|
|
134
|
+
expect(data.hasTurn).toBe(true)
|
|
135
|
+
|
|
136
|
+
tm.end("s1")
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// --- Model/Agent override ---
|
|
141
|
+
|
|
142
|
+
describe("POST /api/session/:sessionId/model", () => {
|
|
143
|
+
test("sets modelOverride in SessionManager", async () => {
|
|
144
|
+
const { deps, sm } = createDeps()
|
|
145
|
+
sm.set("123", { sessionId: "s1", directory: "/tmp" })
|
|
146
|
+
|
|
147
|
+
const app = createApiApp(deps)
|
|
148
|
+
const res = await app.request("/api/session/s1/model", {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { "Content-Type": "application/json" },
|
|
151
|
+
body: JSON.stringify({ providerID: "anthropic", modelID: "opus-4.6" }),
|
|
152
|
+
})
|
|
153
|
+
expect(res.status).toBe(200)
|
|
154
|
+
|
|
155
|
+
const data = await res.json()
|
|
156
|
+
expect(data.model).toEqual({ providerID: "anthropic", modelID: "opus-4.6" })
|
|
157
|
+
|
|
158
|
+
// Verify SessionManager was updated
|
|
159
|
+
expect(sm.get("123")?.modelOverride).toEqual({ providerID: "anthropic", modelID: "opus-4.6" })
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test("returns 404 for unknown session", async () => {
|
|
163
|
+
const { deps } = createDeps()
|
|
164
|
+
const app = createApiApp(deps)
|
|
165
|
+
const res = await app.request("/api/session/nonexistent/model", {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "Content-Type": "application/json" },
|
|
168
|
+
body: JSON.stringify({ providerID: "anthropic", modelID: "opus" }),
|
|
169
|
+
})
|
|
170
|
+
expect(res.status).toBe(404)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test("returns 400 when providerID or modelID missing", async () => {
|
|
174
|
+
const { deps, sm } = createDeps()
|
|
175
|
+
sm.set("123", { sessionId: "s1", directory: "/tmp" })
|
|
176
|
+
|
|
177
|
+
const app = createApiApp(deps)
|
|
178
|
+
const res = await app.request("/api/session/s1/model", {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json" },
|
|
181
|
+
body: JSON.stringify({ providerID: "anthropic" }),
|
|
182
|
+
})
|
|
183
|
+
expect(res.status).toBe(400)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
describe("POST /api/session/:sessionId/agent", () => {
|
|
188
|
+
test("sets agentOverride in SessionManager", async () => {
|
|
189
|
+
const { deps, sm } = createDeps()
|
|
190
|
+
sm.set("123", { sessionId: "s1", directory: "/tmp" })
|
|
191
|
+
|
|
192
|
+
const app = createApiApp(deps)
|
|
193
|
+
const res = await app.request("/api/session/s1/agent", {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: { "Content-Type": "application/json" },
|
|
196
|
+
body: JSON.stringify({ agent: "code" }),
|
|
197
|
+
})
|
|
198
|
+
expect(res.status).toBe(200)
|
|
199
|
+
|
|
200
|
+
const data = await res.json()
|
|
201
|
+
expect(data.agent).toBe("code")
|
|
202
|
+
expect(sm.get("123")?.agentOverride).toBe("code")
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test("returns 404 for unknown session", async () => {
|
|
206
|
+
const { deps } = createDeps()
|
|
207
|
+
const app = createApiApp(deps)
|
|
208
|
+
const res = await app.request("/api/session/nonexistent/agent", {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: { "Content-Type": "application/json" },
|
|
211
|
+
body: JSON.stringify({ agent: "code" }),
|
|
212
|
+
})
|
|
213
|
+
expect(res.status).toBe(404)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test("returns 400 when agent missing", async () => {
|
|
217
|
+
const { deps, sm } = createDeps()
|
|
218
|
+
sm.set("123", { sessionId: "s1", directory: "/tmp" })
|
|
219
|
+
|
|
220
|
+
const app = createApiApp(deps)
|
|
221
|
+
const res = await app.request("/api/session/s1/agent", {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: { "Content-Type": "application/json" },
|
|
224
|
+
body: JSON.stringify({}),
|
|
225
|
+
})
|
|
226
|
+
expect(res.status).toBe(400)
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// --- Session management ---
|
|
231
|
+
|
|
232
|
+
describe("POST /api/session/:sessionId/new", () => {
|
|
233
|
+
test("creates new session for the chat", async () => {
|
|
234
|
+
const { deps, sm } = createDeps()
|
|
235
|
+
sm.set("123", {
|
|
236
|
+
sessionId: "old-session",
|
|
237
|
+
directory: "/tmp",
|
|
238
|
+
modelOverride: { providerID: "anthropic", modelID: "opus" },
|
|
239
|
+
agentOverride: "code",
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const app = createApiApp(deps)
|
|
243
|
+
const res = await app.request("/api/session/old-session/new", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
})
|
|
246
|
+
expect(res.status).toBe(200)
|
|
247
|
+
|
|
248
|
+
const data = await res.json()
|
|
249
|
+
expect(data.sessionId).toBe("new-session-id")
|
|
250
|
+
expect(data.oldSessionId).toBe("old-session")
|
|
251
|
+
expect(data.chatId).toBe("123")
|
|
252
|
+
// Overrides should be preserved
|
|
253
|
+
expect(data.model).toEqual({ providerID: "anthropic", modelID: "opus" })
|
|
254
|
+
expect(data.agent).toBe("code")
|
|
255
|
+
|
|
256
|
+
// Verify SessionManager was updated
|
|
257
|
+
const entry = sm.get("123")
|
|
258
|
+
expect(entry?.sessionId).toBe("new-session-id")
|
|
259
|
+
expect(entry?.modelOverride).toEqual({ providerID: "anthropic", modelID: "opus" })
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test("returns 404 for unknown session", async () => {
|
|
263
|
+
const { deps } = createDeps()
|
|
264
|
+
const app = createApiApp(deps)
|
|
265
|
+
const res = await app.request("/api/session/nonexistent/new", {
|
|
266
|
+
method: "POST",
|
|
267
|
+
})
|
|
268
|
+
expect(res.status).toBe(404)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// --- Provider/agent listing ---
|
|
273
|
+
|
|
274
|
+
describe("GET /api/models", () => {
|
|
275
|
+
test("returns connected providers with filtered models", async () => {
|
|
276
|
+
const { deps } = createDeps()
|
|
277
|
+
const app = createApiApp(deps)
|
|
278
|
+
const res = await app.request("/api/models")
|
|
279
|
+
expect(res.status).toBe(200)
|
|
280
|
+
|
|
281
|
+
const data = await res.json()
|
|
282
|
+
expect(data.length).toBe(1) // Only anthropic (connected)
|
|
283
|
+
expect(data[0].id).toBe("anthropic")
|
|
284
|
+
expect(data[0].default).toBe("sonnet-4.5")
|
|
285
|
+
|
|
286
|
+
// Should be filtered (opus family → only opus-4.6, sonnet family → sonnet-4.5)
|
|
287
|
+
expect(data[0].models.length).toBe(2)
|
|
288
|
+
const modelIds = data[0].models.map((m: any) => m.id)
|
|
289
|
+
expect(modelIds).toContain("opus-4.6")
|
|
290
|
+
expect(modelIds).toContain("sonnet-4.5")
|
|
291
|
+
expect(modelIds).not.toContain("opus-4.5") // filtered out by filterModels
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
describe("GET /api/agents", () => {
|
|
296
|
+
test("returns available agents (excludes hidden)", async () => {
|
|
297
|
+
const { deps } = createDeps()
|
|
298
|
+
const app = createApiApp(deps)
|
|
299
|
+
const res = await app.request("/api/agents")
|
|
300
|
+
expect(res.status).toBe(200)
|
|
301
|
+
|
|
302
|
+
const data = await res.json()
|
|
303
|
+
expect(data.length).toBe(2) // code + build, hidden excluded
|
|
304
|
+
const names = data.map((a: any) => a.name)
|
|
305
|
+
expect(names).toContain("code")
|
|
306
|
+
expect(names).toContain("build")
|
|
307
|
+
expect(names).not.toContain("hidden-agent")
|
|
308
|
+
})
|
|
309
|
+
})
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot Control API — Hono HTTP server exposing bot state for AI-driven control.
|
|
3
|
+
*
|
|
4
|
+
* Endpoints:
|
|
5
|
+
* GET /api/sessions — List all active sessions
|
|
6
|
+
* GET /api/session/:sessionId — Get session info
|
|
7
|
+
* POST /api/session/:sessionId/model — Set model override
|
|
8
|
+
* POST /api/session/:sessionId/agent — Set agent override
|
|
9
|
+
* POST /api/session/:sessionId/new — Create new session for the chat
|
|
10
|
+
* GET /api/models — Connected providers + filtered models
|
|
11
|
+
* GET /api/agents — Available agents
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Hono } from "hono"
|
|
15
|
+
import type { SessionManager } from "./session-manager"
|
|
16
|
+
import type { TurnManager } from "./turn-manager"
|
|
17
|
+
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
|
|
18
|
+
import { filterModels } from "./handlers/models"
|
|
19
|
+
|
|
20
|
+
export type ApiDeps = {
|
|
21
|
+
sessionManager: SessionManager
|
|
22
|
+
turnManager: TurnManager
|
|
23
|
+
sdk: OpencodeClient
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createApiApp(deps: ApiDeps) {
|
|
27
|
+
const { sessionManager, turnManager, sdk } = deps
|
|
28
|
+
|
|
29
|
+
const app = new Hono()
|
|
30
|
+
|
|
31
|
+
app.get("/api/sessions", (c) => {
|
|
32
|
+
const sessions: any[] = []
|
|
33
|
+
// SessionManager doesn't expose iteration — use getBySessionId reverse map
|
|
34
|
+
// We need to access internal state. Add a list method or iterate differently.
|
|
35
|
+
// For now, use the approach of listing all from SDK and matching
|
|
36
|
+
// Actually, let's expose what we need: all active sessions in memory
|
|
37
|
+
const entries = (sessionManager as any).map as Map<string, any>
|
|
38
|
+
for (const [chatKey, entry] of entries) {
|
|
39
|
+
sessions.push({
|
|
40
|
+
chatId: chatKey,
|
|
41
|
+
sessionId: entry.sessionId,
|
|
42
|
+
model: entry.modelOverride ?? null,
|
|
43
|
+
agent: entry.agentOverride ?? null,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
return c.json(sessions)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
app.get("/api/session/:sessionId", (c) => {
|
|
50
|
+
const sessionId = c.req.param("sessionId")
|
|
51
|
+
const lookup = sessionManager.getBySessionId(sessionId)
|
|
52
|
+
if (!lookup) {
|
|
53
|
+
return c.json({ error: "Session not found" }, 404)
|
|
54
|
+
}
|
|
55
|
+
const turn = turnManager.get(sessionId)
|
|
56
|
+
return c.json({
|
|
57
|
+
chatId: lookup.chatKey,
|
|
58
|
+
sessionId: lookup.entry.sessionId,
|
|
59
|
+
model: lookup.entry.modelOverride ?? null,
|
|
60
|
+
agent: lookup.entry.agentOverride ?? null,
|
|
61
|
+
hasTurn: !!turn,
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
app.post("/api/session/:sessionId/model", async (c) => {
|
|
66
|
+
const sessionId = c.req.param("sessionId")
|
|
67
|
+
const lookup = sessionManager.getBySessionId(sessionId)
|
|
68
|
+
if (!lookup) {
|
|
69
|
+
return c.json({ error: "Session not found" }, 404)
|
|
70
|
+
}
|
|
71
|
+
const body = await c.req.json()
|
|
72
|
+
const { providerID, modelID } = body
|
|
73
|
+
if (!providerID || !modelID) {
|
|
74
|
+
return c.json({ error: "providerID and modelID are required" }, 400)
|
|
75
|
+
}
|
|
76
|
+
sessionManager.set(lookup.chatKey, {
|
|
77
|
+
...lookup.entry,
|
|
78
|
+
modelOverride: { providerID, modelID },
|
|
79
|
+
})
|
|
80
|
+
return c.json({
|
|
81
|
+
chatId: lookup.chatKey,
|
|
82
|
+
sessionId,
|
|
83
|
+
model: { providerID, modelID },
|
|
84
|
+
agent: lookup.entry.agentOverride ?? null,
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
app.post("/api/session/:sessionId/agent", async (c) => {
|
|
89
|
+
const sessionId = c.req.param("sessionId")
|
|
90
|
+
const lookup = sessionManager.getBySessionId(sessionId)
|
|
91
|
+
if (!lookup) {
|
|
92
|
+
return c.json({ error: "Session not found" }, 404)
|
|
93
|
+
}
|
|
94
|
+
const body = await c.req.json()
|
|
95
|
+
const { agent } = body
|
|
96
|
+
if (!agent) {
|
|
97
|
+
return c.json({ error: "agent is required" }, 400)
|
|
98
|
+
}
|
|
99
|
+
sessionManager.set(lookup.chatKey, {
|
|
100
|
+
...lookup.entry,
|
|
101
|
+
agentOverride: agent,
|
|
102
|
+
})
|
|
103
|
+
return c.json({
|
|
104
|
+
chatId: lookup.chatKey,
|
|
105
|
+
sessionId,
|
|
106
|
+
model: lookup.entry.modelOverride ?? null,
|
|
107
|
+
agent,
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
app.post("/api/session/:sessionId/new", async (c) => {
|
|
112
|
+
const sessionId = c.req.param("sessionId")
|
|
113
|
+
const lookup = sessionManager.getBySessionId(sessionId)
|
|
114
|
+
if (!lookup) {
|
|
115
|
+
return c.json({ error: "Session not found" }, 404)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const chatKey = lookup.chatKey
|
|
119
|
+
const oldOverrides = {
|
|
120
|
+
modelOverride: lookup.entry.modelOverride,
|
|
121
|
+
agentOverride: lookup.entry.agentOverride,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Remove old session mapping
|
|
125
|
+
sessionManager.remove(chatKey)
|
|
126
|
+
|
|
127
|
+
// Create new session via SDK
|
|
128
|
+
const result = await sdk.session.create({
|
|
129
|
+
title: `Telegram ${chatKey}`,
|
|
130
|
+
})
|
|
131
|
+
const session = result.data!
|
|
132
|
+
|
|
133
|
+
// Set new session with preserved overrides
|
|
134
|
+
sessionManager.set(chatKey, {
|
|
135
|
+
sessionId: session.id,
|
|
136
|
+
directory: session.directory ?? "",
|
|
137
|
+
...oldOverrides,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return c.json({
|
|
141
|
+
chatId: chatKey,
|
|
142
|
+
sessionId: session.id,
|
|
143
|
+
oldSessionId: sessionId,
|
|
144
|
+
model: oldOverrides.modelOverride ?? null,
|
|
145
|
+
agent: oldOverrides.agentOverride ?? null,
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
app.get("/api/models", async (c) => {
|
|
150
|
+
const result = await sdk.provider.list()
|
|
151
|
+
const data = (result as any).data ?? {}
|
|
152
|
+
const providers = data.all ?? []
|
|
153
|
+
const connected = data.connected as string[] | undefined
|
|
154
|
+
const defaults = data.default ?? {}
|
|
155
|
+
|
|
156
|
+
const connectedProviders = connected
|
|
157
|
+
? providers.filter((p: any) => connected.includes(p.id))
|
|
158
|
+
: providers
|
|
159
|
+
|
|
160
|
+
const output = connectedProviders.map((p: any) => {
|
|
161
|
+
const allModels = Object.values(p.models ?? {}) as any[]
|
|
162
|
+
const filtered = filterModels(allModels)
|
|
163
|
+
return {
|
|
164
|
+
id: p.id,
|
|
165
|
+
name: p.name,
|
|
166
|
+
default: defaults[p.id] ?? null,
|
|
167
|
+
models: filtered.map((m: any) => ({
|
|
168
|
+
id: m.id,
|
|
169
|
+
name: m.name,
|
|
170
|
+
family: m.family ?? null,
|
|
171
|
+
release_date: m.release_date ?? null,
|
|
172
|
+
})),
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
return c.json(output)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
app.get("/api/agents", async (c) => {
|
|
180
|
+
const result = await sdk.app.agents()
|
|
181
|
+
const agents = (result as any).data ?? []
|
|
182
|
+
const visible = agents.filter((a: any) => !a.hidden)
|
|
183
|
+
return c.json(
|
|
184
|
+
visible.map((a: any) => ({
|
|
185
|
+
name: a.name,
|
|
186
|
+
description: a.description ?? null,
|
|
187
|
+
})),
|
|
188
|
+
)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
return app
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function createApiServer(deps: ApiDeps, port: number) {
|
|
195
|
+
const app = createApiApp(deps)
|
|
196
|
+
return Bun.serve({
|
|
197
|
+
hostname: "127.0.0.1",
|
|
198
|
+
port,
|
|
199
|
+
fetch: app.fetch,
|
|
200
|
+
})
|
|
201
|
+
}
|