@hienlh/ppm 0.9.29 → 0.9.31

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +4 -7
  2. package/dist/web/assets/{browser-tab-D0o6oSlt.js → browser-tab-DmDrxklj.js} +1 -1
  3. package/dist/web/assets/{chat-tab-Boo_H1k9.js → chat-tab-CMwOy57v.js} +1 -1
  4. package/dist/web/assets/{code-editor-DayGetAZ.js → code-editor-jsL0PK8A.js} +1 -1
  5. package/dist/web/assets/{database-viewer-CaxAp1qK.js → database-viewer-CBo5yPV-.js} +1 -1
  6. package/dist/web/assets/{diff-viewer-BvEXe_B4.js → diff-viewer-Dk-plEOm.js} +1 -1
  7. package/dist/web/assets/{extension-webview-6XProGzB.js → extension-webview-B0tE14-C.js} +1 -1
  8. package/dist/web/assets/{git-graph-CvgIIt2x.js → git-graph-BsYuai5I.js} +1 -1
  9. package/dist/web/assets/{index-DocPzjV6.js → index-DMiaze7L.js} +13 -6
  10. package/dist/web/assets/keybindings-store-B01E0k20.js +1 -0
  11. package/dist/web/assets/{markdown-renderer-UCGYJpI-.js → markdown-renderer-lUfZhpU0.js} +1 -1
  12. package/dist/web/assets/{postgres-viewer-TV6kyo6B.js → postgres-viewer-sZclUhuS.js} +1 -1
  13. package/dist/web/assets/{settings-tab-EziN5Pco.js → settings-tab-CvbLGbR6.js} +1 -1
  14. package/dist/web/assets/{sqlite-viewer-D7LPvSkU.js → sqlite-viewer-BAjul3Ct.js} +1 -1
  15. package/dist/web/assets/{terminal-tab-C7Hdv1nq.js → terminal-tab-Ds9ymO7D.js} +1 -1
  16. package/dist/web/assets/{use-monaco-theme-CI4vTUsh.js → use-monaco-theme-D9bFLaXR.js} +1 -1
  17. package/dist/web/index.html +1 -1
  18. package/dist/web/sw.js +1 -1
  19. package/docs/streaming-input-guide.md +267 -0
  20. package/package.json +1 -1
  21. package/snapshot-state.md +1526 -0
  22. package/src/server/index.ts +1 -1
  23. package/src/server/routes/proxy.ts +0 -15
  24. package/src/server/routes/settings.ts +0 -2
  25. package/src/services/proxy.service.ts +0 -33
  26. package/src/services/supervisor.ts +10 -0
  27. package/src/web/components/settings/proxy-settings-section.tsx +37 -50
  28. package/src/web/components/settings/proxy-test-section.tsx +25 -48
  29. package/src/web/lib/api-settings.ts +0 -2
  30. package/test-session-ops.mjs +444 -0
  31. package/test-tokens.mjs +212 -0
  32. package/.claude.bak/agent-memory/tester/MEMORY.md +0 -3
  33. package/.claude.bak/agent-memory/tester/project-ppm-test-conventions.md +0 -32
  34. package/dist/web/assets/keybindings-store-2KURy8S3.js +0 -1
  35. package/src/services/proxy-openai-bridge.ts +0 -213
@@ -1,213 +0,0 @@
1
- /**
2
- * OpenAI-compatible proxy bridge — converts OpenAI Chat Completions
3
- * requests into SDK query() calls and returns OpenAI-format responses.
4
- *
5
- * Endpoint: POST /proxy/v1/chat/completions
6
- * Reference: https://github.com/fuergaosi233/claude-code-proxy
7
- */
8
- import { query } from "@anthropic-ai/claude-agent-sdk";
9
- import { accountSelector } from "./account-selector.service.ts";
10
-
11
- // ── Helpers ──────────────────────────────────────────────────────────
12
-
13
- function mapModelToSdkModel(model: string): "sonnet" | "opus" | "haiku" {
14
- if (model.includes("opus")) return "opus";
15
- if (model.includes("haiku")) return "haiku";
16
- return "sonnet";
17
- }
18
-
19
- function buildSdkEnv(accessToken: string): Record<string, string | undefined> {
20
- const isOAuth = accessToken.startsWith("sk-ant-oat");
21
- return {
22
- ...process.env,
23
- ANTHROPIC_API_KEY: isOAuth ? "" : accessToken,
24
- CLAUDE_CODE_OAUTH_TOKEN: isOAuth ? accessToken : "",
25
- ANTHROPIC_BASE_URL: "",
26
- };
27
- }
28
-
29
- /** Extract system prompt and build text prompt from OpenAI messages format */
30
- function buildPromptFromOpenAiMessages(body: any): { prompt: string; systemPrompt?: string } {
31
- const messages: any[] = body.messages ?? [];
32
- let systemPrompt: string | undefined;
33
- const conversationParts: string[] = [];
34
-
35
- for (const m of messages) {
36
- const text = typeof m.content === "string"
37
- ? m.content
38
- : Array.isArray(m.content)
39
- ? m.content.filter((b: any) => b.type === "text").map((b: any) => b.text).join("\n")
40
- : String(m.content ?? "");
41
-
42
- if (m.role === "system") {
43
- systemPrompt = systemPrompt ? `${systemPrompt}\n${text}` : text;
44
- } else {
45
- const role = m.role === "assistant" ? "Assistant" : "Human";
46
- conversationParts.push(`${role}: ${text}`);
47
- }
48
- }
49
-
50
- return { prompt: conversationParts.join("\n\n"), systemPrompt };
51
- }
52
-
53
- function openAiError(status: number, message: string): Response {
54
- return new Response(JSON.stringify({
55
- error: { message, type: "server_error", code: String(status) },
56
- }), { status, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" } });
57
- }
58
-
59
- // ── Public API ───────────────────────────────────────────────────────
60
-
61
- interface SdkAccount {
62
- id: string;
63
- email?: string | null;
64
- accessToken: string;
65
- }
66
-
67
- /** Forward an OpenAI-format chat completions request via SDK query() */
68
- export async function forwardOpenAiViaSdk(body: any, account: SdkAccount): Promise<Response> {
69
- const model = mapModelToSdkModel(body.model || "sonnet");
70
- const stream = body.stream ?? false;
71
- const { prompt, systemPrompt } = buildPromptFromOpenAiMessages(body);
72
- const env = buildSdkEnv(account.accessToken);
73
-
74
- console.log(`[proxy-openai] ${stream ? "stream" : "non-stream"} → ${model} via ${account.email ?? account.id}`);
75
-
76
- if (!stream) return handleNonStreaming(prompt, systemPrompt, model, env, body, account);
77
- return handleStreaming(prompt, systemPrompt, model, env, body, account);
78
- }
79
-
80
- // ── Non-streaming ────────────────────────────────────────────────────
81
-
82
- async function handleNonStreaming(
83
- prompt: string, systemPrompt: string | undefined,
84
- model: "sonnet" | "opus" | "haiku",
85
- env: Record<string, string | undefined>,
86
- body: any, account: SdkAccount,
87
- ): Promise<Response> {
88
- try {
89
- let fullContent = "";
90
- const response = query({
91
- prompt,
92
- options: { maxTurns: 1, model, env, ...(systemPrompt && { systemPrompt }) },
93
- });
94
-
95
- for await (const message of response) {
96
- if (message.type === "assistant") {
97
- for (const block of (message as any).message?.content ?? []) {
98
- if (block.type === "text") fullContent += block.text;
99
- }
100
- }
101
- }
102
-
103
- accountSelector.onSuccess(account.id);
104
-
105
- return new Response(JSON.stringify({
106
- id: `chatcmpl-${Date.now()}`,
107
- object: "chat.completion",
108
- created: Math.floor(Date.now() / 1000),
109
- model: body.model || "claude-sonnet-4-6",
110
- choices: [{
111
- index: 0,
112
- message: { role: "assistant", content: fullContent },
113
- finish_reason: "stop",
114
- }],
115
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
116
- }), {
117
- status: 200,
118
- headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
119
- });
120
- } catch (error) {
121
- console.error(`[proxy-openai] Non-stream error:`, (error as Error).message);
122
- accountSelector.onRateLimit(account.id);
123
- return openAiError(502, (error as Error).message);
124
- }
125
- }
126
-
127
- // ── Streaming ────────────────────────────────────────────────────────
128
-
129
- async function handleStreaming(
130
- prompt: string, systemPrompt: string | undefined,
131
- model: "sonnet" | "opus" | "haiku",
132
- env: Record<string, string | undefined>,
133
- body: any, account: SdkAccount,
134
- ): Promise<Response> {
135
- const encoder = new TextEncoder();
136
- const chatId = `chatcmpl-${Date.now()}`;
137
- const created = Math.floor(Date.now() / 1000);
138
- const modelName = body.model || "claude-sonnet-4-6";
139
-
140
- const chunk = (delta: any, finishReason: string | null) => ({
141
- id: chatId, object: "chat.completion.chunk", created, model: modelName,
142
- choices: [{ index: 0, delta, finish_reason: finishReason }],
143
- });
144
-
145
- const readable = new ReadableStream({
146
- async start(controller) {
147
- const send = (data: any) => {
148
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
149
- };
150
-
151
- try {
152
- const response = query({
153
- prompt,
154
- options: {
155
- maxTurns: 1, model, env,
156
- ...(systemPrompt && { systemPrompt }),
157
- includePartialMessages: true,
158
- },
159
- });
160
-
161
- // Initial chunk with role
162
- send(chunk({ role: "assistant", content: "" }, null));
163
-
164
- const skipBlockIndices = new Set<number>();
165
-
166
- for await (const message of response) {
167
- if (message.type !== "stream_event") continue;
168
-
169
- const event = (message as any).event;
170
- const eventType = event.type as string;
171
- const eventIndex = event.index as number | undefined;
172
-
173
- // Track and skip tool_use blocks
174
- if (eventType === "content_block_start" && event.content_block?.type === "tool_use") {
175
- if (eventIndex !== undefined) skipBlockIndices.add(eventIndex);
176
- continue;
177
- }
178
- if (eventIndex !== undefined && skipBlockIndices.has(eventIndex)) continue;
179
-
180
- // Text deltas → OpenAI content chunks
181
- if (eventType === "content_block_delta" && event.delta?.type === "text_delta") {
182
- const text = event.delta.text ?? "";
183
- if (text) send(chunk({ content: text }, null));
184
- }
185
-
186
- // Message complete → finish chunk
187
- if (eventType === "message_stop") {
188
- send(chunk({}, "stop"));
189
- }
190
- }
191
-
192
- accountSelector.onSuccess(account.id);
193
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
194
- controller.close();
195
- } catch (error) {
196
- console.error(`[proxy-openai] Stream error:`, (error as Error).message);
197
- accountSelector.onRateLimit(account.id);
198
- send(chunk({ content: `\n\nError: ${(error as Error).message}` }, "stop"));
199
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
200
- controller.close();
201
- }
202
- },
203
- });
204
-
205
- return new Response(readable, {
206
- headers: {
207
- "Content-Type": "text/event-stream",
208
- "Cache-Control": "no-cache",
209
- "Connection": "keep-alive",
210
- "Access-Control-Allow-Origin": "*",
211
- },
212
- });
213
- }