@hienlh/ppm 0.19.4 → 0.19.5

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 (79) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +54 -1
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +6 -1
  5. package/dist/web/assets/{accounts-settings-section-CCFQV-RB.js → accounts-settings-section-CmLGNwUl.js} +1 -1
  6. package/dist/web/assets/{ai-resource-editor-BTWds-aC.js → ai-resource-editor-TVV8rOsT.js} +1 -1
  7. package/dist/web/assets/{ai-settings-section-CmoBsSpY.js → ai-settings-section-CutZAI-J.js} +1 -1
  8. package/dist/web/assets/{ai-settings-section-DjbYj4wK.js → ai-settings-section-Whh5k2zL.js} +1 -1
  9. package/dist/web/assets/{appearance-settings-section-vLmakLtA.js → appearance-settings-section-DbkXixUe.js} +1 -1
  10. package/dist/web/assets/{chat-tab-CLRlS8Jc.js → chat-tab-BYuPaeUJ.js} +3 -3
  11. package/dist/web/assets/{code-editor-CCwVu5F5.js → code-editor-4Lk47HbX.js} +2 -2
  12. package/dist/web/assets/{conflict-editor-CvWYcbwD.js → conflict-editor-B-HnPZX7.js} +1 -1
  13. package/dist/web/assets/{csv-preview-DzuDuyE_.js → csv-preview-CMIxkng3.js} +1 -1
  14. package/dist/web/assets/{database-viewer-D4XGEDHm.js → database-viewer-CM6O4tar.js} +1 -1
  15. package/dist/web/assets/{diff-viewer-B5N1X61R.js → diff-viewer-nL4cAp1u.js} +1 -1
  16. package/dist/web/assets/{explorer-body-BxLLjthF.js → explorer-body-CuLBY282.js} +1 -1
  17. package/dist/web/assets/{explorer-window-content-CL1Cipj3.js → explorer-window-content-Cr8HYEIZ.js} +1 -1
  18. package/dist/web/assets/{extension-manager-section-BpyGq15A.js → extension-manager-section-D_E1LteV.js} +1 -1
  19. package/dist/web/assets/{extension-webview-BM8FXhuN.js → extension-webview-BKVWrl6R.js} +1 -1
  20. package/dist/web/assets/{files-settings-section-B0_aR1nL.js → files-settings-section-DzuWb19a.js} +1 -1
  21. package/dist/web/assets/{general-settings-section-C1EJzjhU.js → general-settings-section-c096J38r.js} +1 -1
  22. package/dist/web/assets/{git-log-panel-D1yGTUE_.js → git-log-panel-DRLGWrO2.js} +1 -1
  23. package/dist/web/assets/{glide-data-grid-Cu0STKML.js → glide-data-grid-CUtOJQyB.js} +1 -1
  24. package/dist/web/assets/{group-chat-tab-BfcvkRX5.js → group-chat-tab-Cano41wo.js} +1 -1
  25. package/dist/web/assets/{index-B1bzKRjJ.js → index-C_s5DxFQ.js} +4 -4
  26. package/dist/web/assets/{jira-watcher-section-DZh-Z2MK.js → jira-watcher-section-B2-_4QW_.js} +1 -1
  27. package/dist/web/assets/{keybindings-store-CFwgcnpZ.js → keybindings-store-ehg0D6FR.js} +1 -1
  28. package/dist/web/assets/{keyboard-shortcuts-section-JBtcJGM3.js → keyboard-shortcuts-section-DMVcFwLJ.js} +1 -1
  29. package/dist/web/assets/{markdown-renderer-DY60UZNW.js → markdown-renderer-CKIBtz-k.js} +1 -1
  30. package/dist/web/assets/{markdown-renderer-Bo8GgGXl.js → markdown-renderer-pDOFHEG7.js} +1 -1
  31. package/dist/web/assets/{mobile-explorer-sheet-C59rPw7Z.js → mobile-explorer-sheet-DFGPwNp0.js} +1 -1
  32. package/dist/web/assets/{notification-store-Bd6L0K_Q.js → notification-store-CwLJtN2t.js} +1 -1
  33. package/dist/web/assets/{notifications-settings-section-ARQlF-6E.js → notifications-settings-section-C66s2JId.js} +1 -1
  34. package/dist/web/assets/{pdf-preview-CRShdbBS.js → pdf-preview-0AwTBfeC.js} +1 -1
  35. package/dist/web/assets/{postgres-viewer-BNT-4yYl.js → postgres-viewer-hDHYMHmG.js} +1 -1
  36. package/dist/web/assets/{ppmbot-settings-section-Bq4YAcFW.js → ppmbot-settings-section-DV6TMOfs.js} +1 -1
  37. package/dist/web/assets/{process-table-CjiXW0rg.js → process-table-CmvwqGrQ.js} +1 -1
  38. package/dist/web/assets/proxy-settings-section-DjtXwOV_.js +1 -0
  39. package/dist/web/assets/{query-audit-section-C5RmhRSu.js → query-audit-section-BgXdBaE1.js} +1 -1
  40. package/dist/web/assets/{remote-desktop-mobile-sheet-BF-75_03.js → remote-desktop-mobile-sheet-Cakf7i6s.js} +2 -2
  41. package/dist/web/assets/{remote-desktop-mobile-view-C9KR9I9G.js → remote-desktop-mobile-view-DO5NJufM.js} +1 -1
  42. package/dist/web/assets/{remote-desktop-readiness-gate-BnKJicB2.js → remote-desktop-readiness-gate-CODu8fsP.js} +1 -1
  43. package/dist/web/assets/{remote-desktop-window-content-awXsNw_R.js → remote-desktop-window-content-DVhp7Y9T.js} +1 -1
  44. package/dist/web/assets/{schedules-settings-section-BOecKJzl.js → schedules-settings-section-IfwILuXE.js} +1 -1
  45. package/dist/web/assets/{settings-body-DGi5DXY6.js → settings-body-NYJVObzu.js} +2 -2
  46. package/dist/web/assets/{settings-tab-GM9zTRul.js → settings-tab-CPOI69SL.js} +1 -1
  47. package/dist/web/assets/{settings-window-content-Dvhxo4_H.js → settings-window-content-BuuLt4qg.js} +1 -1
  48. package/dist/web/assets/{sql-query-editor-BmkGFzcL.js → sql-query-editor-BSwL4jf9.js} +1 -1
  49. package/dist/web/assets/{sqlite-viewer-DbziVQXQ.js → sqlite-viewer-Cy-H-h03.js} +1 -1
  50. package/dist/web/assets/{system-monitor-body-CbHW5aYm.js → system-monitor-body-B3jG3f5P.js} +2 -2
  51. package/dist/web/assets/{system-monitor-tab-C7EZLMIo.js → system-monitor-tab-Dt4wtPR0.js} +1 -1
  52. package/dist/web/assets/{system-monitor-window-content-C_w4w81e.js → system-monitor-window-content-5Kkti6yp.js} +1 -1
  53. package/dist/web/assets/{tab-host-window-content-BSP1SEXS.js → tab-host-window-content-BnwmQM0I.js} +1 -1
  54. package/dist/web/assets/{team-member-sheet-C9N0iA1-.js → team-member-sheet-B4TEEtVT.js} +2 -2
  55. package/dist/web/assets/{team-member-window-content-CM2QFv-s.js → team-member-window-content-CHevmy9F.js} +1 -1
  56. package/dist/web/assets/{terminal-tab-jWYELm8y.js → terminal-tab-DVb1AfrI.js} +1 -1
  57. package/dist/web/assets/{tool-cards-jefdOWVJ.js → tool-cards-DIF_JcFj.js} +2 -2
  58. package/dist/web/assets/{use-accounts-data-MhMl0Y4Z.js → use-accounts-data--JHaCIoT.js} +1 -1
  59. package/dist/web/assets/{use-monaco-theme-BbAd7DXr.js → use-monaco-theme-xqDIVJcg.js} +1 -1
  60. package/dist/web/assets/{use-remote-desktop-display-choice-DlfMIWq3.js → use-remote-desktop-display-choice-DJFX_arw.js} +1 -1
  61. package/dist/web/assets/{use-websocket-Bq8X_SUI.js → use-websocket-DufdN3ZA.js} +1 -1
  62. package/dist/web/assets/{video-preview-BGh3B0Lu.js → video-preview-DExfNDxI.js} +1 -1
  63. package/dist/web/index.html +1 -1
  64. package/dist/web/sw.js +1 -1
  65. package/package.json +1 -1
  66. package/src/providers/codex-app-server/codex-event-mapper.ts +42 -1
  67. package/src/providers/codex-app-server/codex-provider.ts +22 -4
  68. package/src/server/routes/proxy.ts +60 -0
  69. package/src/services/codex-account.service.ts +4 -2
  70. package/src/services/prod-db-guard.ts +98 -0
  71. package/src/services/proxy-agent-anthropic-bridge.ts +14 -3
  72. package/src/services/proxy-agent-bridge.ts +51 -4
  73. package/src/services/proxy-agent-turn.ts +12 -1
  74. package/src/services/proxy-image-bridge.ts +186 -0
  75. package/src/services/proxy-openai-bridge.ts +4 -38
  76. package/src/services/proxy-openai-format.ts +29 -3
  77. package/src/services/proxy.service.ts +54 -0
  78. package/src/web/components/settings/proxy-settings-section.tsx +55 -23
  79. package/dist/web/assets/proxy-settings-section-wRbiyQiA.js +0 -3
@@ -3,6 +3,8 @@ import { accountSelector } from "./account-selector.service.ts";
3
3
  import { accountService } from "./account.service.ts";
4
4
  import { forwardViaSdk } from "./proxy-sdk-bridge.ts";
5
5
  import { forwardOpenAiViaSdk } from "./proxy-openai-bridge.ts";
6
+ import { forwardAgentChatCompletions } from "./proxy-agent-bridge.ts";
7
+ import { forwardAgentMessages } from "./proxy-agent-anthropic-bridge.ts";
6
8
  import { randomBytes } from "node:crypto";
7
9
 
8
10
  const PROXY_ENABLED_KEY = "proxy_enabled";
@@ -168,6 +170,58 @@ class ProxyService {
168
170
  }
169
171
  }
170
172
 
173
+ /**
174
+ * Run a provider's agent behind one of the two API dialects. Account rotation
175
+ * is the provider's own concern here — codex picks a CODEX_HOME — so unlike
176
+ * forwardOpenAi this must not claim a Claude account.
177
+ */
178
+ private async forwardAgent(
179
+ providerId: string,
180
+ body: string,
181
+ path: string,
182
+ run: (providerId: string, parsed: any) => Promise<Response>,
183
+ invalidJson: Response,
184
+ caller?: ProxyCallerMeta,
185
+ ): Promise<Response> {
186
+ const endpoint = `/${providerId}/v1/${path}`;
187
+ let parsed: { model?: string };
188
+ try {
189
+ parsed = JSON.parse(body);
190
+ } catch {
191
+ return invalidJson;
192
+ }
193
+
194
+ const start = performance.now();
195
+ this.requestCount++;
196
+ const response = await run(providerId, parsed);
197
+ const durationMs = Math.round(performance.now() - start);
198
+ insertProxyRequest({
199
+ endpoint, model: parsed.model, accountLabel: providerId,
200
+ callerIp: caller?.callerIp, callerUa: caller?.callerUa,
201
+ status: response.ok ? "success" : "error", durationMs,
202
+ });
203
+ console.log(`[proxy] POST ${endpoint} → ${response.status} ${durationMs}ms caller=${caller?.callerIp ?? "unknown"}`);
204
+ return response;
205
+ }
206
+
207
+ /** OpenAI dialect: `/proxy/<provider>/v1/chat/completions`. */
208
+ async forwardAgentChat(providerId: string, body: string, caller?: ProxyCallerMeta): Promise<Response> {
209
+ return this.forwardAgent(providerId, body, "chat/completions", forwardAgentChatCompletions,
210
+ new Response(
211
+ JSON.stringify({ error: { message: "Invalid JSON body", type: "invalid_request_error" } }),
212
+ { status: 400, headers: { "Content-Type": "application/json" } },
213
+ ), caller);
214
+ }
215
+
216
+ /** Anthropic dialect: `/proxy/<provider>/v1/messages`. */
217
+ async forwardAgentMessages(providerId: string, body: string, caller?: ProxyCallerMeta): Promise<Response> {
218
+ return this.forwardAgent(providerId, body, "messages", forwardAgentMessages,
219
+ new Response(
220
+ JSON.stringify({ type: "error", error: { type: "invalid_request_error", message: "Invalid JSON body" } }),
221
+ { status: 400, headers: { "Content-Type": "application/json" } },
222
+ ), caller);
223
+ }
224
+
171
225
  /** Direct HTTP forward for API key accounts */
172
226
  private async forwardDirect(
173
227
  path: string,
@@ -4,11 +4,22 @@ import { Button } from "@/components/ui/button";
4
4
  import { Input } from "@/components/ui/input";
5
5
  import { Label } from "@/components/ui/label";
6
6
  import { Switch } from "@/components/ui/switch";
7
- import { getProxySettings, updateProxySettings, type ProxySettings } from "@/lib/api-settings";
7
+ import { getProxySettings, updateProxySettings, getAISettings, type ProxySettings } from "@/lib/api-settings";
8
8
  import { copyToClipboard } from "@/lib/clipboard";
9
9
  import { ProxyTestButton } from "./proxy-test-section";
10
10
 
11
11
  export function ProxySettingsSection() {
12
+ /** Provider the connection info targets; "" is the unscoped Claude path. */
13
+ const [provider, setProvider] = useState("");
14
+ /** Providers reachable as agents, minus the internal mock. */
15
+ const [agentProviders, setAgentProviders] = useState<string[]>([]);
16
+
17
+ useEffect(() => {
18
+ getAISettings()
19
+ .then((s) => setAgentProviders(Object.keys(s.providers ?? {}).filter((id) => id !== "mock" && id !== "claude")))
20
+ .catch(() => setAgentProviders([]));
21
+ }, []);
22
+
12
23
  const [settings, setSettings] = useState<ProxySettings | null>(null);
13
24
  const [loading, setLoading] = useState(true);
14
25
  const [saving, setSaving] = useState(false);
@@ -59,6 +70,16 @@ export function ProxySettingsSection() {
59
70
  const localEndpoint = settings.localEndpoint;
60
71
  const localBaseUrl = localEndpoint.replace(/\/proxy\/v1\/messages$/, "");
61
72
 
73
+ // Both dialects hang off one prefix, so selecting a provider moves them
74
+ // together and the card cannot show a mismatched pair.
75
+ // default: <root>/proxy → /v1/messages, /v1/chat/completions
76
+ // provider: <root>/proxy/codex → same two paths underneath
77
+ const prefix = `${hasTunnel ? settings.tunnelUrl : localBaseUrl}/proxy${provider ? `/${provider}` : ""}`;
78
+ const anthropicEndpoint = `${prefix}/v1/messages`;
79
+ const openAiEndpoint = `${prefix}/v1/chat/completions`;
80
+ const anthropicEnv = `ANTHROPIC_BASE_URL=${prefix}\nANTHROPIC_API_KEY=${settings.authKey}`;
81
+ const openAiEnv = `OPENAI_BASE_URL=${prefix}/v1\nOPENAI_API_KEY=${settings.authKey}`;
82
+
62
83
  return (
63
84
  <div className="space-y-4">
64
85
  <div className="space-y-1.5">
@@ -135,18 +156,43 @@ export function ProxySettingsSection() {
135
156
  <ProxyTestButton authKey={settings.authKey!} baseUrl={window.location.origin} />
136
157
  </div>
137
158
 
159
+ {/* Target provider — the only thing that differs between the two
160
+ dialects below, so both are derived from one prefix. */}
161
+ <div className="space-y-1">
162
+ <Label className="text-[10px] text-muted-foreground">Provider</Label>
163
+ <div className="flex flex-wrap gap-1.5">
164
+ {[{ id: "", label: "Claude (default)" }, ...agentProviders.map((id) => ({ id, label: id }))].map((p) => (
165
+ <button
166
+ key={p.id || "default"}
167
+ type="button"
168
+ onClick={() => setProvider(p.id)}
169
+ className={`text-[10px] px-2.5 min-h-[36px] rounded-md border transition-colors cursor-pointer ${
170
+ provider === p.id ? "border-primary bg-primary/15" : "border-border bg-muted/40 hover:bg-muted"
171
+ }`}
172
+ >
173
+ {p.label}
174
+ </button>
175
+ ))}
176
+ </div>
177
+ {provider && (
178
+ <p className="text-[10px] text-muted-foreground">
179
+ Runs the {provider} agent per request (read-only sandbox, one turn). Slower than the default path.
180
+ </p>
181
+ )}
182
+ </div>
183
+
138
184
  {/* Anthropic endpoint */}
139
185
  <div className="space-y-1">
140
186
  <Label className="text-[10px] text-muted-foreground">Anthropic Endpoint</Label>
141
187
  <div className="flex gap-1.5 items-center">
142
188
  <code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
143
- {hasTunnel ? settings.proxyEndpoint : localEndpoint}
189
+ {anthropicEndpoint}
144
190
  </code>
145
191
  <Button
146
192
  variant="ghost"
147
193
  size="sm"
148
194
  className="h-6 px-1.5 cursor-pointer shrink-0"
149
- onClick={() => handleCopy(hasTunnel ? settings.proxyEndpoint! : localEndpoint, "anthropic")}
195
+ onClick={() => handleCopy(anthropicEndpoint, "anthropic")}
150
196
  >
151
197
  {copied === "anthropic" ? "Copied!" : <Copy className="size-3" />}
152
198
  </Button>
@@ -158,15 +204,13 @@ export function ProxySettingsSection() {
158
204
  <Label className="text-[10px] text-muted-foreground">OpenAI-Compatible Endpoint</Label>
159
205
  <div className="flex gap-1.5 items-center">
160
206
  <code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
161
- {hasTunnel ? settings.openAiEndpoint : settings.localOpenAiEndpoint}
207
+ {openAiEndpoint}
162
208
  </code>
163
209
  <Button
164
210
  variant="ghost"
165
211
  size="sm"
166
212
  className="h-6 px-1.5 cursor-pointer shrink-0"
167
- onClick={() => handleCopy(
168
- hasTunnel ? settings.openAiEndpoint! : settings.localOpenAiEndpoint, "openai",
169
- )}
213
+ onClick={() => handleCopy(openAiEndpoint, "openai")}
170
214
  >
171
215
  {copied === "openai" ? "Copied!" : <Copy className="size-3" />}
172
216
  </Button>
@@ -183,18 +227,12 @@ export function ProxySettingsSection() {
183
227
  <div className="space-y-1 pt-1">
184
228
  <Label className="text-[10px] text-muted-foreground">Anthropic Format</Label>
185
229
  <div className="relative">
186
- <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
187
- {`ANTHROPIC_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy" : localBaseUrl + "/proxy"}
188
- ANTHROPIC_API_KEY=${settings.authKey}`}
189
- </pre>
230
+ <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">{anthropicEnv}</pre>
190
231
  <Button
191
232
  variant="ghost"
192
233
  size="sm"
193
234
  className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
194
- onClick={() => handleCopy(
195
- `ANTHROPIC_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy" : localBaseUrl + "/proxy"}\nANTHROPIC_API_KEY=${settings.authKey}`,
196
- "anthropic-env",
197
- )}
235
+ onClick={() => handleCopy(anthropicEnv, "anthropic-env")}
198
236
  >
199
237
  {copied === "anthropic-env" ? "Copied!" : <Copy className="size-2.5" />}
200
238
  </Button>
@@ -204,18 +242,12 @@ ANTHROPIC_API_KEY=${settings.authKey}`}
204
242
  <div className="space-y-1">
205
243
  <Label className="text-[10px] text-muted-foreground">OpenAI Format</Label>
206
244
  <div className="relative">
207
- <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
208
- {`OPENAI_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy/v1" : localBaseUrl + "/proxy/v1"}
209
- OPENAI_API_KEY=${settings.authKey}`}
210
- </pre>
245
+ <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">{openAiEnv}</pre>
211
246
  <Button
212
247
  variant="ghost"
213
248
  size="sm"
214
249
  className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
215
- onClick={() => handleCopy(
216
- `OPENAI_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy/v1" : localBaseUrl + "/proxy/v1"}\nOPENAI_API_KEY=${settings.authKey}`,
217
- "openai-env",
218
- )}
250
+ onClick={() => handleCopy(openAiEnv, "openai-env")}
219
251
  >
220
252
  {copied === "openai-env" ? "Copied!" : <Copy className="size-2.5" />}
221
253
  </Button>
@@ -1,3 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{S as t,x as n}from"./vendor-markdown-Ls5AcU91.js";import"./vendor-ui-CaeeFO0B.js";import{t as r}from"./copy-BewHj66E.js";import{t as i}from"./flask-conical-tp8gyZGh.js";import{t as a}from"./loader-circle-DPMrnLQ2.js";import"./api-client-oY1tC3tM.js";import"./portal-container-context-a399Hd9F.js";import{t as o}from"./button-BZtEJcUv.js";import{$t as s,J as c,Jt as l,Kn as u,Qt as d,W as f,Xt as p,Y as m,Yn as h,Yt as g,en as _,tn as v}from"./index-B1bzKRjJ.js";import{getProxySettings as y,updateProxySettings as b}from"./api-settings-CTMdz_VF.js";var x=e(t(),1),S=[{value:`claude-opus-5`,label:`Claude Opus 5`,hint:`most powerful`},{value:`claude-fable-5-1`,label:`Claude Fable 5.1`,hint:`flagship`},{value:`claude-fable-5`,label:`Claude Fable 5`,hint:`flagship`},{value:`claude-opus-4-8`,label:`Claude Opus 4.8`,hint:`powerful`},{value:`claude-opus-4-7`,label:`Claude Opus 4.7`,hint:`powerful`},{value:`claude-opus-4-6`,label:`Claude Opus 4.6`,hint:`powerful`},{value:`claude-sonnet-5`,label:`Claude Sonnet 5`,hint:`fast`},{value:`claude-sonnet-4-6`,label:`Claude Sonnet 4.6`,hint:`fast`},{value:`claude-haiku-4-5`,label:`Claude Haiku 4.5`,hint:`cheap`}];S.map(e=>e.value);var C=n(),w=`Hello! Reply briefly.`,T=`claude-opus-5`;function E(e){let[t,n]=(0,x.useState)(!1);return(0,C.jsxs)(l,{open:t,onOpenChange:n,children:[(0,C.jsx)(_,{asChild:!0,children:(0,C.jsxs)(o,{variant:`outline`,size:`sm`,className:`h-7 text-[11px] gap-1.5 cursor-pointer`,children:[(0,C.jsx)(i,{className:`size-3`}),`Test`]})}),(0,C.jsxs)(g,{className:`sm:max-w-md max-h-[85vh] overflow-y-auto`,children:[(0,C.jsxs)(d,{children:[(0,C.jsx)(s,{className:`text-sm`,children:`Test Proxy`}),(0,C.jsx)(p,{className:`text-[11px]`,children:`Send a test request and inspect the raw response.`})]}),t&&(0,C.jsx)(D,{...e})]})]})}function D({authKey:e,baseUrl:t}){let[n,r]=(0,x.useState)(`anthropic`),[i,s]=(0,x.useState)(w),[l,u]=(0,x.useState)(T),[d,f]=(0,x.useState)(!0),[p,g]=(0,x.useState)(!1),[_,v]=(0,x.useState)(null),[y,b]=(0,x.useState)(null),[E,D]=(0,x.useState)(null),O=(0,x.useRef)(null),k=(0,x.useRef)(null);(0,x.useEffect)(()=>{O.current?.select()},[]),(0,x.useEffect)(()=>{k.current&&(k.current.scrollTop=k.current.scrollHeight)},[_]);let A=e=>{r(e),v(null),b(null),D(null)},j=async()=>{g(!0),v(null),b(null),D(null);let r=Date.now(),a=n===`openai`,o=a?`${t}/proxy/v1/chat/completions`:`${t}/proxy/v1/messages`,s=JSON.stringify({model:l,max_tokens:256,stream:d,messages:[{role:`user`,content:i}]}),c={"Content-Type":`application/json`};a?c.Authorization=`Bearer ${e}`:(c[`x-api-key`]=e,c[`anthropic-version`]=`2023-06-01`);try{let e=await fetch(o,{method:`POST`,headers:c,body:s});if(!e.ok){let t=await e.text();b(`HTTP ${e.status}: ${t}`),g(!1),D(Date.now()-r);return}if(d){let t=e.body?.getReader();if(!t){b(`No response body`),g(!1);return}let n=new TextDecoder,i=``;for(;;){let{done:e,value:r}=await t.read();if(e)break;i+=n.decode(r,{stream:!0}),v(i)}D(Date.now()-r)}else{let t=await e.text();try{v(JSON.stringify(JSON.parse(t),null,2))}catch{v(t)}D(Date.now()-r)}}catch(e){b(e.message)}finally{g(!1),D(e=>e??Date.now()-r)}};return(0,C.jsxs)(`div`,{className:`space-y-3 min-w-0`,children:[(0,C.jsxs)(`div`,{className:`space-y-1.5 min-w-0`,children:[(0,C.jsx)(c,{className:`text-[11px]`,children:`Auth Style`}),(0,C.jsx)(`div`,{className:`flex gap-1`,children:[`anthropic`,`openai`].map(e=>(0,C.jsx)(`button`,{onClick:()=>A(e),className:`flex-1 h-8 rounded-md text-[11px] font-medium border transition-colors cursor-pointer ${n===e?`bg-primary text-primary-foreground border-primary`:`bg-muted text-muted-foreground border-transparent hover:bg-muted/80`}`,children:e===`anthropic`?`Anthropic`:`OpenAI`},e))}),(0,C.jsx)(`p`,{className:`text-[9px] text-muted-foreground`,children:n===`anthropic`?`x-api-key header`:`Authorization: Bearer header`})]}),(0,C.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,C.jsx)(c,{className:`text-[11px]`,children:`Model`}),(0,C.jsx)(`select`,{value:l,onChange:e=>u(e.target.value),className:`h-8 w-full rounded-md border bg-background px-2 text-[11px]`,children:S.map(e=>(0,C.jsx)(`option`,{value:e.value,children:e.value},e.value))})]}),(0,C.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,C.jsx)(c,{className:`text-[11px]`,children:`Streaming`}),(0,C.jsx)(`div`,{className:`flex gap-1`,children:[!0,!1].map(e=>(0,C.jsx)(`button`,{onClick:()=>f(e),className:`h-7 px-3 rounded-md text-[11px] font-medium border transition-colors cursor-pointer ${d===e?`bg-primary text-primary-foreground border-primary`:`bg-muted text-muted-foreground border-transparent hover:bg-muted/80`}`,children:e?`Stream`:`JSON`},String(e)))})]}),(0,C.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,C.jsx)(c,{className:`text-[11px]`,children:`Message`}),(0,C.jsxs)(`div`,{className:`flex gap-1.5`,children:[(0,C.jsx)(m,{ref:O,value:i,onChange:e=>s(e.target.value),placeholder:`Type a test message...`,className:`h-9 text-[11px] flex-1 min-w-0`,onKeyDown:e=>{e.key===`Enter`&&!p&&j()}}),(0,C.jsxs)(o,{size:`sm`,className:`h-9 px-3 cursor-pointer shrink-0`,onClick:j,disabled:p||!i.trim(),children:[p?(0,C.jsx)(a,{className:`size-3.5 animate-spin`}):(0,C.jsx)(h,{className:`size-3.5`}),(0,C.jsx)(`span`,{className:`ml-1 text-[11px]`,children:p?`...`:`Send`})]})]})]}),(_||y)&&(0,C.jsxs)(`div`,{className:`space-y-1 min-w-0`,children:[(0,C.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,C.jsx)(c,{className:`text-[10px] text-muted-foreground`,children:`Raw Response`}),E!=null&&(0,C.jsxs)(`span`,{className:`text-[9px] font-mono text-muted-foreground`,children:[(E/1e3).toFixed(1),`s`]})]}),y?(0,C.jsx)(`pre`,{className:`text-[9px] font-mono bg-error/10 text-error p-2 rounded overflow-auto max-h-52 whitespace-pre-wrap break-all`,children:y}):(0,C.jsx)(`pre`,{ref:k,className:`text-[9px] font-mono bg-muted p-2 rounded overflow-auto max-h-52 whitespace-pre-wrap break-all`,children:_})]})]})}function O(){let[e,t]=(0,x.useState)(null),[n,i]=(0,x.useState)(!0),[a,s]=(0,x.useState)(!1),[l,d]=(0,x.useState)(null),[p,h]=(0,x.useState)(null);(0,x.useEffect)(()=>{i(!0),y().then(t).catch(e=>d(e.message)).finally(()=>i(!1))},[]);let g=async e=>{s(!0),d(null);try{t(await b(e))}catch(e){d(e.message)}finally{s(!1)}},_=(e,t)=>{v(e),h(t),setTimeout(()=>h(null),2e3)};if(n||!e)return(0,C.jsxs)(`div`,{className:`space-y-2`,children:[(0,C.jsx)(`h3`,{className:`text-xs font-medium text-text-secondary`,children:`API Proxy`}),(0,C.jsx)(`p`,{className:`text-[11px] text-text-subtle`,children:l?`Error: ${l}`:`Loading...`})]});let S=!!e.authKey,w=!!e.tunnelUrl,T=e.localEndpoint,D=T.replace(/\/proxy\/v1\/messages$/,``);return(0,C.jsxs)(`div`,{className:`space-y-4`,children:[(0,C.jsx)(`div`,{className:`space-y-1.5`,children:(0,C.jsx)(`p`,{className:`text-[11px] text-muted-foreground`,children:`Expose your Claude accounts as an Anthropic-compatible API endpoint. External tools (OpenCode, Cursor, etc.) can use your accounts via this proxy.`})}),(0,C.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,C.jsxs)(`div`,{className:`space-y-0.5`,children:[(0,C.jsx)(c,{className:`text-xs`,children:`Enable Proxy`}),(0,C.jsx)(`p`,{className:`text-[11px] text-muted-foreground`,children:`Accept API requests on /proxy/v1/messages`})]}),(0,C.jsx)(f,{checked:e.enabled,onCheckedChange:e=>g({enabled:e}),disabled:a})]}),(0,C.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,C.jsx)(c,{className:`text-[11px]`,children:`Auth Key`}),S?(0,C.jsxs)(`div`,{className:`flex gap-1.5`,children:[(0,C.jsx)(m,{readOnly:!0,value:e.authKey,className:`h-7 text-[11px] font-mono flex-1`}),(0,C.jsx)(o,{variant:`outline`,size:`sm`,className:`h-7 px-2 cursor-pointer shrink-0`,onClick:()=>_(e.authKey,`key`),children:p===`key`?`Copied!`:(0,C.jsx)(r,{className:`size-3`})}),(0,C.jsx)(o,{variant:`outline`,size:`sm`,className:`h-7 px-2 cursor-pointer shrink-0`,onClick:()=>g({generateKey:!0}),disabled:a,children:(0,C.jsx)(u,{className:`size-3`})})]}):(0,C.jsx)(o,{variant:`outline`,size:`sm`,className:`h-7 text-xs cursor-pointer`,onClick:()=>g({generateKey:!0}),disabled:a,children:`Generate Auth Key`}),(0,C.jsx)(`p`,{className:`text-[10px] text-muted-foreground`,children:`Use as Bearer token or x-api-key when calling the proxy.`})]}),e.enabled&&S&&(0,C.jsxs)(`div`,{className:`space-y-2 rounded-md border p-3 bg-muted/30`,children:[(0,C.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,C.jsx)(`h4`,{className:`text-[11px] font-medium`,children:`Connection Info`}),(0,C.jsx)(E,{authKey:e.authKey,baseUrl:window.location.origin})]}),(0,C.jsxs)(`div`,{className:`space-y-1`,children:[(0,C.jsx)(c,{className:`text-[10px] text-muted-foreground`,children:`Anthropic Endpoint`}),(0,C.jsxs)(`div`,{className:`flex gap-1.5 items-center`,children:[(0,C.jsx)(`code`,{className:`text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate`,children:w?e.proxyEndpoint:T}),(0,C.jsx)(o,{variant:`ghost`,size:`sm`,className:`h-6 px-1.5 cursor-pointer shrink-0`,onClick:()=>_(w?e.proxyEndpoint:T,`anthropic`),children:p===`anthropic`?`Copied!`:(0,C.jsx)(r,{className:`size-3`})})]})]}),(0,C.jsxs)(`div`,{className:`space-y-1`,children:[(0,C.jsx)(c,{className:`text-[10px] text-muted-foreground`,children:`OpenAI-Compatible Endpoint`}),(0,C.jsxs)(`div`,{className:`flex gap-1.5 items-center`,children:[(0,C.jsx)(`code`,{className:`text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate`,children:w?e.openAiEndpoint:e.localOpenAiEndpoint}),(0,C.jsx)(o,{variant:`ghost`,size:`sm`,className:`h-6 px-1.5 cursor-pointer shrink-0`,onClick:()=>_(w?e.openAiEndpoint:e.localOpenAiEndpoint,`openai`),children:p===`openai`?`Copied!`:(0,C.jsx)(r,{className:`size-3`})})]})]}),!w&&(0,C.jsx)(`p`,{className:`text-[10px] text-muted-foreground`,children:`Start a Cloudflare tunnel (Share) to get a public URL.`}),(0,C.jsxs)(`div`,{className:`space-y-1 pt-1`,children:[(0,C.jsx)(c,{className:`text-[10px] text-muted-foreground`,children:`Anthropic Format`}),(0,C.jsxs)(`div`,{className:`relative`,children:[(0,C.jsx)(`pre`,{className:`text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre`,children:`ANTHROPIC_BASE_URL=${w?e.tunnelUrl+`/proxy`:D+`/proxy`}
2
- ANTHROPIC_API_KEY=${e.authKey}`}),(0,C.jsx)(o,{variant:`ghost`,size:`sm`,className:`absolute top-1 right-1 h-5 px-1 cursor-pointer`,onClick:()=>_(`ANTHROPIC_BASE_URL=${w?e.tunnelUrl+`/proxy`:D+`/proxy`}\nANTHROPIC_API_KEY=${e.authKey}`,`anthropic-env`),children:p===`anthropic-env`?`Copied!`:(0,C.jsx)(r,{className:`size-2.5`})})]})]}),(0,C.jsxs)(`div`,{className:`space-y-1`,children:[(0,C.jsx)(c,{className:`text-[10px] text-muted-foreground`,children:`OpenAI Format`}),(0,C.jsxs)(`div`,{className:`relative`,children:[(0,C.jsx)(`pre`,{className:`text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre`,children:`OPENAI_BASE_URL=${w?e.tunnelUrl+`/proxy/v1`:D+`/proxy/v1`}
3
- OPENAI_API_KEY=${e.authKey}`}),(0,C.jsx)(o,{variant:`ghost`,size:`sm`,className:`absolute top-1 right-1 h-5 px-1 cursor-pointer`,onClick:()=>_(`OPENAI_BASE_URL=${w?e.tunnelUrl+`/proxy/v1`:D+`/proxy/v1`}\nOPENAI_API_KEY=${e.authKey}`,`openai-env`),children:p===`openai-env`?`Copied!`:(0,C.jsx)(r,{className:`size-2.5`})})]})]}),(0,C.jsx)(`div`,{className:`flex items-center gap-3 pt-1`,children:(0,C.jsxs)(`span`,{className:`text-[10px] text-muted-foreground`,children:[`Requests served: `,(0,C.jsx)(`span`,{className:`font-mono`,children:e.requestCount})]})})]}),a&&(0,C.jsx)(`p`,{className:`text-[11px] text-text-subtle`,children:`Saving...`}),l&&(0,C.jsx)(`p`,{className:`text-[11px] text-error`,children:l})]})}export{O as ProxySettingsSection};