@hienlh/ppm 0.8.58 → 0.8.60
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 +16 -0
- package/dist/web/assets/chat-tab-C5H74y2z.js +7 -0
- package/dist/web/assets/{code-editor-u6bm6bdq.js → code-editor-DMw26mUm.js} +1 -1
- package/dist/web/assets/{database-viewer-BgPBW1bJ.js → database-viewer-gnj_8u4T.js} +1 -1
- package/dist/web/assets/{diff-viewer-Cho-kjse.js → diff-viewer-DVqfhdBN.js} +1 -1
- package/dist/web/assets/{git-graph-CktRdFwt.js → git-graph-CJy7tOAJ.js} +1 -1
- package/dist/web/assets/index-BAioKo_2.css +2 -0
- package/dist/web/assets/index-Dg6TQ3Iu.js +37 -0
- package/dist/web/assets/keybindings-store-DcxZ6WAa.js +1 -0
- package/dist/web/assets/{markdown-renderer-3_CTktzg.js → markdown-renderer--Ss7hHOm.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CH0JfEQ9.js → postgres-viewer-DMcvp0H7.js} +1 -1
- package/dist/web/assets/{settings-tab-BI0n39LJ.js → settings-tab-lC12I-a1.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DJyT7YZg.js → sqlite-viewer-BK2emL4i.js} +1 -1
- package/dist/web/assets/{tab-store-dpsCvqhH.js → tab-store-DcIBZTD4.js} +1 -1
- package/dist/web/assets/{terminal-tab-OqCohyF0.js → terminal-tab--Ag9kqvS.js} +1 -1
- package/dist/web/index.html +3 -3
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/snapshot-state.md +1526 -0
- package/src/index.ts +0 -0
- package/src/providers/claude-agent-sdk.ts +16 -14
- package/src/providers/mock-provider.ts +6 -1
- package/src/server/index.ts +3 -15
- package/src/server/routes/proxy.ts +46 -53
- package/src/server/ws/chat.ts +194 -139
- package/src/services/account-selector.service.ts +8 -6
- package/src/services/account.service.ts +1 -0
- package/src/services/claude-usage.service.ts +10 -4
- package/src/services/proxy.service.ts +4 -19
- package/src/types/api.ts +9 -1
- package/src/web/components/chat/chat-tab.tsx +14 -5
- package/src/web/components/chat/message-input.tsx +39 -12
- package/src/web/components/chat/message-list.tsx +15 -12
- package/src/web/components/layout/panel-layout.tsx +17 -1
- package/src/web/components/settings/proxy-settings-section.tsx +40 -42
- package/src/web/hooks/use-chat.ts +196 -203
- package/src/web/stores/panel-store.ts +10 -10
- package/test-tokens.mjs +212 -0
- package/.claude.bak/agent-memory/tester/MEMORY.md +0 -3
- package/.claude.bak/agent-memory/tester/project-ppm-test-conventions.md +0 -32
- package/dist/web/assets/chat-tab-cawT08fh.js +0 -7
- package/dist/web/assets/index-CpOYx0qg.js +0 -31
- package/dist/web/assets/index-WKLuYsBY.css +0 -2
- package/dist/web/assets/keybindings-store-vOnSm10D.js +0 -1
package/src/index.ts
CHANGED
|
File without changes
|
|
@@ -502,22 +502,24 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
502
502
|
|
|
503
503
|
// Log all system events for debugging SDK lifecycle
|
|
504
504
|
if (msg.type === "system") {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (oldMeta) {
|
|
518
|
-
this.activeSessions.set(initMsg.session_id, { ...oldMeta, id: initMsg.session_id });
|
|
505
|
+
const subtype = (msg as any).subtype ?? "none";
|
|
506
|
+
console.log(`[sdk] session=${sessionId} system: subtype=${subtype} ${JSON.stringify(msg).slice(0, 500)}`);
|
|
507
|
+
|
|
508
|
+
// Capture SDK session metadata from init message
|
|
509
|
+
if (subtype === "init") {
|
|
510
|
+
const initMsg = msg as any;
|
|
511
|
+
if (initMsg.session_id && initMsg.session_id !== sessionId) {
|
|
512
|
+
setSessionMapping(sessionId, initMsg.session_id);
|
|
513
|
+
const oldMeta = this.activeSessions.get(sessionId);
|
|
514
|
+
if (oldMeta) {
|
|
515
|
+
this.activeSessions.set(initMsg.session_id, { ...oldMeta, id: initMsg.session_id });
|
|
516
|
+
}
|
|
519
517
|
}
|
|
520
518
|
}
|
|
519
|
+
|
|
520
|
+
// Yield system events so streaming loop can transition phases
|
|
521
|
+
// (e.g. connecting → thinking when hooks/init arrive)
|
|
522
|
+
yield { type: "system" as any, subtype } as any;
|
|
521
523
|
continue;
|
|
522
524
|
}
|
|
523
525
|
|
|
@@ -92,8 +92,13 @@ export class MockProvider implements AIProvider {
|
|
|
92
92
|
const abortController = new AbortController();
|
|
93
93
|
this.activeAborts.set(sessionId, abortController);
|
|
94
94
|
|
|
95
|
+
// Simulate SDK system events (hooks, init) — real SDK emits these before content
|
|
96
|
+
yield { type: "system" as any, subtype: "hook_started" } as any;
|
|
97
|
+
await sleep(50);
|
|
98
|
+
yield { type: "system" as any, subtype: "init" } as any;
|
|
99
|
+
|
|
95
100
|
// Simulate thinking delay
|
|
96
|
-
await sleep(
|
|
101
|
+
await sleep(250);
|
|
97
102
|
|
|
98
103
|
// Pick a response
|
|
99
104
|
const responseText =
|
package/src/server/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { postgresRoutes } from "./routes/postgres.ts";
|
|
|
13
13
|
import { databaseRoutes } from "./routes/database.ts";
|
|
14
14
|
import { fsBrowseRoutes } from "./routes/fs-browse.ts";
|
|
15
15
|
import { accountsRoutes } from "./routes/accounts.ts";
|
|
16
|
-
import { proxyRoutes
|
|
16
|
+
import { proxyRoutes } from "./routes/proxy.ts";
|
|
17
17
|
import { initAdapters } from "../services/database/init-adapters.ts";
|
|
18
18
|
import { terminalWebSocket } from "./ws/terminal.ts";
|
|
19
19
|
import { chatWebSocket } from "./ws/chat.ts";
|
|
@@ -344,15 +344,9 @@ export async function startServer(options: {
|
|
|
344
344
|
const server = Bun.serve({
|
|
345
345
|
port,
|
|
346
346
|
hostname: host,
|
|
347
|
-
|
|
347
|
+
fetch(req, server) {
|
|
348
348
|
const url = new URL(req.url);
|
|
349
349
|
|
|
350
|
-
// Proxy: handle before Hono to avoid SPA catch-all conflict
|
|
351
|
-
if (url.pathname.startsWith("/proxy")) {
|
|
352
|
-
const proxyRes = await handleProxyRequest(req);
|
|
353
|
-
if (proxyRes) return proxyRes;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
350
|
// WebSocket upgrade: /ws/project/:projectName/terminal/:id
|
|
357
351
|
if (url.pathname.startsWith("/ws/project/")) {
|
|
358
352
|
const parts = url.pathname.split("/");
|
|
@@ -505,15 +499,9 @@ if (process.argv.includes("__serve__")) {
|
|
|
505
499
|
Bun.serve({
|
|
506
500
|
port,
|
|
507
501
|
hostname: host,
|
|
508
|
-
|
|
502
|
+
fetch(req, server) {
|
|
509
503
|
const url = new URL(req.url);
|
|
510
504
|
|
|
511
|
-
// Proxy: handle before Hono to avoid SPA catch-all conflict
|
|
512
|
-
if (url.pathname.startsWith("/proxy")) {
|
|
513
|
-
const proxyRes = await handleProxyRequest(req);
|
|
514
|
-
if (proxyRes) return proxyRes;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
505
|
if (url.pathname === "/ws/health") {
|
|
518
506
|
const upgraded = server.upgrade(req, { data: { type: "health" } });
|
|
519
507
|
if (upgraded) return undefined;
|
|
@@ -1,86 +1,79 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { proxyService } from "../../services/proxy.service.ts";
|
|
3
|
+
import { ok, err } from "../../types/api.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Proxy routes — Anthropic-compatible API proxy.
|
|
6
|
-
* External tools (
|
|
7
|
+
* External tools (opencode, cursor, etc.) send requests here
|
|
7
8
|
* and PPM forwards them to Anthropic using account rotation.
|
|
8
9
|
*
|
|
9
|
-
* Mounted at /proxy —
|
|
10
|
+
* Mounted at /proxy — so /proxy/v1/messages maps to Anthropic's POST /v1/messages.
|
|
10
11
|
* Uses its own auth (proxy auth key), NOT PPM's auth middleware.
|
|
11
|
-
*
|
|
12
|
-
* Usage with Claude Code CLI:
|
|
13
|
-
* ANTHROPIC_BASE_URL=http://host:port/proxy
|
|
14
|
-
* ANTHROPIC_API_KEY=<proxy-auth-key>
|
|
15
12
|
*/
|
|
13
|
+
export const proxyRoutes = new Hono();
|
|
16
14
|
|
|
17
|
-
/** Validate proxy auth key from Authorization
|
|
15
|
+
/** Validate proxy auth key from Authorization header */
|
|
18
16
|
function validateProxyAuth(authHeader: string | undefined): boolean {
|
|
19
17
|
if (!authHeader) return false;
|
|
20
18
|
const key = proxyService.getAuthKey();
|
|
21
19
|
if (!key) return false;
|
|
20
|
+
// Accept both "Bearer <key>" and raw "<key>" (x-api-key style)
|
|
22
21
|
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
|
|
23
22
|
return token === key;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
/** CORS preflight for external tools */
|
|
26
|
+
proxyRoutes.options("/*", (c) => {
|
|
27
|
+
return new Response(null, {
|
|
28
|
+
status: 204,
|
|
29
|
+
headers: {
|
|
30
|
+
"Access-Control-Allow-Origin": "*",
|
|
31
|
+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
|
|
32
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta",
|
|
33
|
+
"Access-Control-Max-Age": "86400",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
});
|
|
32
37
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
export async function handleProxyRequest(req: Request): Promise<Response | null> {
|
|
39
|
-
const url = new URL(req.url);
|
|
40
|
-
if (!url.pathname.startsWith("/proxy/") && url.pathname !== "/proxy") return null;
|
|
38
|
+
/** POST /proxy/v1/messages — Anthropic Messages API proxy */
|
|
39
|
+
proxyRoutes.post("/v1/messages", async (c) => {
|
|
40
|
+
if (!proxyService.isEnabled()) {
|
|
41
|
+
return c.json({ type: "error", error: { type: "api_error", message: "Proxy is disabled" } }, 503);
|
|
42
|
+
}
|
|
41
43
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// Auth check — accept both Authorization and x-api-key headers
|
|
45
|
+
const authHeader = c.req.header("authorization") || c.req.header("x-api-key");
|
|
46
|
+
if (!validateProxyAuth(authHeader)) {
|
|
47
|
+
return c.json({ type: "error", error: { type: "authentication_error", message: "Invalid proxy auth key" } }, 401);
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
const body = await c.req.text();
|
|
51
|
+
const headers: Record<string, string> = {};
|
|
52
|
+
for (const key of ["anthropic-version", "anthropic-beta", "content-type"]) {
|
|
53
|
+
const val = c.req.header(key);
|
|
54
|
+
if (val) headers[key] = val;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return proxyService.forward("/v1/messages", "POST", headers, body);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** POST /proxy/v1/messages/count_tokens — token counting proxy */
|
|
61
|
+
proxyRoutes.post("/v1/messages/count_tokens", async (c) => {
|
|
47
62
|
if (!proxyService.isEnabled()) {
|
|
48
|
-
return
|
|
49
|
-
{ type: "error", error: { type: "api_error", message: "Proxy is disabled" } },
|
|
50
|
-
{ status: 503, headers: { "Access-Control-Allow-Origin": "*" } },
|
|
51
|
-
);
|
|
63
|
+
return c.json({ type: "error", error: { type: "api_error", message: "Proxy is disabled" } }, 503);
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
const authHeader = req.headers.get("authorization") || req.headers.get("x-api-key") || undefined;
|
|
66
|
+
const authHeader = c.req.header("authorization") || c.req.header("x-api-key");
|
|
56
67
|
if (!validateProxyAuth(authHeader)) {
|
|
57
|
-
return
|
|
58
|
-
{ type: "error", error: { type: "authentication_error", message: "Invalid proxy auth key" } },
|
|
59
|
-
{ status: 401, headers: { "Access-Control-Allow-Origin": "*" } },
|
|
60
|
-
);
|
|
68
|
+
return c.json({ type: "error", error: { type: "authentication_error", message: "Invalid proxy auth key" } }, 401);
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
const path = url.pathname.replace(/^\/proxy/, "") || "/";
|
|
65
|
-
const method = req.method;
|
|
66
|
-
|
|
67
|
-
// Collect relevant headers to forward
|
|
71
|
+
const body = await c.req.text();
|
|
68
72
|
const headers: Record<string, string> = {};
|
|
69
|
-
for (const key of ["anthropic-version", "anthropic-beta", "content-type"
|
|
70
|
-
const val = req.
|
|
73
|
+
for (const key of ["anthropic-version", "anthropic-beta", "content-type"]) {
|
|
74
|
+
const val = c.req.header(key);
|
|
71
75
|
if (val) headers[key] = val;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
const body = ["POST", "PUT", "PATCH"].includes(method) ? await req.text() : null;
|
|
76
|
-
|
|
77
|
-
return proxyService.forward(path, method, headers, body);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Keep Hono sub-router for backward compat with app.route() + tests
|
|
81
|
-
export const proxyRoutes = new Hono();
|
|
82
|
-
proxyRoutes.all("/*", async (c) => {
|
|
83
|
-
const res = await handleProxyRequest(c.req.raw);
|
|
84
|
-
if (res) return res;
|
|
85
|
-
return c.notFound();
|
|
78
|
+
return proxyService.forward("/v1/messages/count_tokens", "POST", headers, body);
|
|
86
79
|
});
|