@jonsoc/console-app 1.1.34

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 (217) hide show
  1. package/.opencode/agent/css.md +149 -0
  2. package/README.md +32 -0
  3. package/package.json +49 -0
  4. package/public/apple-touch-icon-v3.png +1 -0
  5. package/public/apple-touch-icon.png +1 -0
  6. package/public/email +1 -0
  7. package/public/favicon-96x96-v3.png +1 -0
  8. package/public/favicon-96x96.png +1 -0
  9. package/public/favicon-v3.ico +1 -0
  10. package/public/favicon-v3.svg +1 -0
  11. package/public/favicon.ico +1 -0
  12. package/public/favicon.svg +1 -0
  13. package/public/opencode-brand-assets.zip +0 -0
  14. package/public/robots.txt +6 -0
  15. package/public/site.webmanifest +1 -0
  16. package/public/social-share-black.png +1 -0
  17. package/public/social-share-zen.png +1 -0
  18. package/public/social-share.png +1 -0
  19. package/public/theme.json +182 -0
  20. package/public/web-app-manifest-192x192.png +1 -0
  21. package/public/web-app-manifest-512x512.png +1 -0
  22. package/script/generate-sitemap.ts +103 -0
  23. package/src/app.css +1 -0
  24. package/src/app.tsx +27 -0
  25. package/src/asset/black/hero.png +0 -0
  26. package/src/asset/brand/opencode-brand-assets.zip +0 -0
  27. package/src/asset/brand/opencode-logo-dark.png +0 -0
  28. package/src/asset/brand/opencode-logo-dark.svg +16 -0
  29. package/src/asset/brand/opencode-logo-light.png +0 -0
  30. package/src/asset/brand/opencode-logo-light.svg +16 -0
  31. package/src/asset/brand/opencode-wordmark-dark.png +0 -0
  32. package/src/asset/brand/opencode-wordmark-dark.svg +30 -0
  33. package/src/asset/brand/opencode-wordmark-light.png +0 -0
  34. package/src/asset/brand/opencode-wordmark-light.svg +30 -0
  35. package/src/asset/brand/opencode-wordmark-simple-dark.png +0 -0
  36. package/src/asset/brand/opencode-wordmark-simple-dark.svg +22 -0
  37. package/src/asset/brand/opencode-wordmark-simple-light.png +0 -0
  38. package/src/asset/brand/opencode-wordmark-simple-light.svg +22 -0
  39. package/src/asset/brand/preview-opencode-dark.png +0 -0
  40. package/src/asset/brand/preview-opencode-logo-dark.png +0 -0
  41. package/src/asset/brand/preview-opencode-logo-light.png +0 -0
  42. package/src/asset/brand/preview-opencode-wordmark-dark.png +0 -0
  43. package/src/asset/brand/preview-opencode-wordmark-light.png +0 -0
  44. package/src/asset/brand/preview-opencode-wordmark-simple-dark.png +0 -0
  45. package/src/asset/brand/preview-opencode-wordmark-simple-light.png +0 -0
  46. package/src/asset/lander/avatar-adam.png +0 -0
  47. package/src/asset/lander/avatar-david.png +0 -0
  48. package/src/asset/lander/avatar-dax.png +0 -0
  49. package/src/asset/lander/avatar-frank.png +0 -0
  50. package/src/asset/lander/avatar-jay.png +0 -0
  51. package/src/asset/lander/brand-assets-dark.svg +10 -0
  52. package/src/asset/lander/brand-assets-light.svg +10 -0
  53. package/src/asset/lander/brand.png +0 -0
  54. package/src/asset/lander/check.svg +3 -0
  55. package/src/asset/lander/copy.svg +3 -0
  56. package/src/asset/lander/desktop-app-icon.png +0 -0
  57. package/src/asset/lander/dock.png +0 -0
  58. package/src/asset/lander/logo-dark.svg +11 -0
  59. package/src/asset/lander/logo-light.svg +11 -0
  60. package/src/asset/lander/opencode-comparison-min.mp4 +0 -0
  61. package/src/asset/lander/opencode-comparison-poster.png +0 -0
  62. package/src/asset/lander/opencode-desktop-icon.png +0 -0
  63. package/src/asset/lander/opencode-logo-dark.svg +11 -0
  64. package/src/asset/lander/opencode-logo-light.svg +11 -0
  65. package/src/asset/lander/opencode-min.mp4 +0 -0
  66. package/src/asset/lander/opencode-poster.png +0 -0
  67. package/src/asset/lander/opencode-wordmark-dark.svg +25 -0
  68. package/src/asset/lander/opencode-wordmark-light.svg +25 -0
  69. package/src/asset/lander/screenshot-github.png +0 -0
  70. package/src/asset/lander/screenshot-splash.png +0 -0
  71. package/src/asset/lander/screenshot-vscode.png +0 -0
  72. package/src/asset/lander/screenshot.png +0 -0
  73. package/src/asset/lander/wordmark-dark.svg +3 -0
  74. package/src/asset/lander/wordmark-light.svg +3 -0
  75. package/src/asset/logo-ornate-dark.svg +18 -0
  76. package/src/asset/logo-ornate-light.svg +18 -0
  77. package/src/asset/logo.svg +18 -0
  78. package/src/asset/zen-ornate-dark.svg +8 -0
  79. package/src/asset/zen-ornate-light.svg +8 -0
  80. package/src/component/dropdown.css +80 -0
  81. package/src/component/dropdown.tsx +79 -0
  82. package/src/component/email-signup.tsx +48 -0
  83. package/src/component/faq.tsx +33 -0
  84. package/src/component/footer.tsx +38 -0
  85. package/src/component/header-context-menu.css +63 -0
  86. package/src/component/header.tsx +279 -0
  87. package/src/component/icon.tsx +257 -0
  88. package/src/component/legal.tsx +20 -0
  89. package/src/component/modal.css +66 -0
  90. package/src/component/modal.tsx +24 -0
  91. package/src/component/spotlight.css +15 -0
  92. package/src/component/spotlight.tsx +820 -0
  93. package/src/config.ts +29 -0
  94. package/src/context/auth.session.ts +0 -0
  95. package/src/context/auth.ts +116 -0
  96. package/src/context/auth.withActor.ts +7 -0
  97. package/src/entry-client.tsx +4 -0
  98. package/src/entry-server.tsx +30 -0
  99. package/src/global.d.ts +5 -0
  100. package/src/lib/github.ts +38 -0
  101. package/src/middleware.ts +5 -0
  102. package/src/routes/[...404].css +130 -0
  103. package/src/routes/[...404].tsx +38 -0
  104. package/src/routes/api/enterprise.ts +47 -0
  105. package/src/routes/auth/[...callback].ts +41 -0
  106. package/src/routes/auth/authorize.ts +10 -0
  107. package/src/routes/auth/index.ts +12 -0
  108. package/src/routes/auth/logout.ts +17 -0
  109. package/src/routes/auth/status.ts +7 -0
  110. package/src/routes/bench/[id].tsx +365 -0
  111. package/src/routes/bench/index.tsx +86 -0
  112. package/src/routes/bench/submission.ts +29 -0
  113. package/src/routes/black/common.tsx +62 -0
  114. package/src/routes/black/index.tsx +108 -0
  115. package/src/routes/black/subscribe/[plan].tsx +449 -0
  116. package/src/routes/black/workspace.css +214 -0
  117. package/src/routes/black/workspace.tsx +229 -0
  118. package/src/routes/black.css +828 -0
  119. package/src/routes/black.tsx +285 -0
  120. package/src/routes/brand/index.css +555 -0
  121. package/src/routes/brand/index.tsx +252 -0
  122. package/src/routes/changelog/index.css +477 -0
  123. package/src/routes/changelog/index.tsx +147 -0
  124. package/src/routes/debug/index.ts +13 -0
  125. package/src/routes/desktop-feedback.ts +5 -0
  126. package/src/routes/discord.ts +5 -0
  127. package/src/routes/docs/[...path].ts +20 -0
  128. package/src/routes/docs/index.ts +20 -0
  129. package/src/routes/download/[platform].ts +38 -0
  130. package/src/routes/download/index.css +750 -0
  131. package/src/routes/download/index.tsx +482 -0
  132. package/src/routes/download/types.ts +4 -0
  133. package/src/routes/enterprise/index.css +578 -0
  134. package/src/routes/enterprise/index.tsx +251 -0
  135. package/src/routes/index.css +1251 -0
  136. package/src/routes/index.tsx +840 -0
  137. package/src/routes/legal/privacy-policy/index.css +343 -0
  138. package/src/routes/legal/privacy-policy/index.tsx +1512 -0
  139. package/src/routes/legal/terms-of-service/index.css +254 -0
  140. package/src/routes/legal/terms-of-service/index.tsx +512 -0
  141. package/src/routes/openapi.json.ts +7 -0
  142. package/src/routes/s/[id].ts +20 -0
  143. package/src/routes/stripe/webhook.ts +532 -0
  144. package/src/routes/t/[...path].tsx +20 -0
  145. package/src/routes/temp.tsx +172 -0
  146. package/src/routes/user-menu.css +18 -0
  147. package/src/routes/user-menu.tsx +32 -0
  148. package/src/routes/workspace/[id]/billing/billing-section.module.css +185 -0
  149. package/src/routes/workspace/[id]/billing/billing-section.tsx +240 -0
  150. package/src/routes/workspace/[id]/billing/black-section.module.css +142 -0
  151. package/src/routes/workspace/[id]/billing/black-section.tsx +269 -0
  152. package/src/routes/workspace/[id]/billing/black-waitlist-section.module.css +23 -0
  153. package/src/routes/workspace/[id]/billing/index.tsx +32 -0
  154. package/src/routes/workspace/[id]/billing/monthly-limit-section.module.css +96 -0
  155. package/src/routes/workspace/[id]/billing/monthly-limit-section.tsx +133 -0
  156. package/src/routes/workspace/[id]/billing/payment-section.module.css +93 -0
  157. package/src/routes/workspace/[id]/billing/payment-section.tsx +122 -0
  158. package/src/routes/workspace/[id]/billing/reload-section.module.css +261 -0
  159. package/src/routes/workspace/[id]/billing/reload-section.tsx +213 -0
  160. package/src/routes/workspace/[id]/graph-section.module.css +145 -0
  161. package/src/routes/workspace/[id]/graph-section.tsx +475 -0
  162. package/src/routes/workspace/[id]/index.tsx +81 -0
  163. package/src/routes/workspace/[id]/keys/index.tsx +11 -0
  164. package/src/routes/workspace/[id]/keys/key-section.module.css +197 -0
  165. package/src/routes/workspace/[id]/keys/key-section.tsx +176 -0
  166. package/src/routes/workspace/[id]/members/index.tsx +11 -0
  167. package/src/routes/workspace/[id]/members/member-section.module.css +249 -0
  168. package/src/routes/workspace/[id]/members/member-section.tsx +343 -0
  169. package/src/routes/workspace/[id]/members/role-dropdown.css +72 -0
  170. package/src/routes/workspace/[id]/members/role-dropdown.tsx +43 -0
  171. package/src/routes/workspace/[id]/model-section.module.css +173 -0
  172. package/src/routes/workspace/[id]/model-section.tsx +174 -0
  173. package/src/routes/workspace/[id]/new-user-section.module.css +143 -0
  174. package/src/routes/workspace/[id]/new-user-section.tsx +104 -0
  175. package/src/routes/workspace/[id]/provider-section.module.css +138 -0
  176. package/src/routes/workspace/[id]/provider-section.tsx +188 -0
  177. package/src/routes/workspace/[id]/settings/index.tsx +11 -0
  178. package/src/routes/workspace/[id]/settings/settings-section.module.css +94 -0
  179. package/src/routes/workspace/[id]/settings/settings-section.tsx +122 -0
  180. package/src/routes/workspace/[id]/usage-section.module.css +185 -0
  181. package/src/routes/workspace/[id]/usage-section.tsx +200 -0
  182. package/src/routes/workspace/[id].css +308 -0
  183. package/src/routes/workspace/[id].tsx +62 -0
  184. package/src/routes/workspace/common.tsx +120 -0
  185. package/src/routes/workspace-picker.css +74 -0
  186. package/src/routes/workspace-picker.tsx +122 -0
  187. package/src/routes/workspace.css +107 -0
  188. package/src/routes/workspace.tsx +38 -0
  189. package/src/routes/zen/index.css +866 -0
  190. package/src/routes/zen/index.tsx +343 -0
  191. package/src/routes/zen/util/dataDumper.ts +44 -0
  192. package/src/routes/zen/util/error.ts +13 -0
  193. package/src/routes/zen/util/handler.ts +784 -0
  194. package/src/routes/zen/util/logger.ts +12 -0
  195. package/src/routes/zen/util/provider/anthropic.ts +752 -0
  196. package/src/routes/zen/util/provider/google.ts +75 -0
  197. package/src/routes/zen/util/provider/openai-compatible.ts +546 -0
  198. package/src/routes/zen/util/provider/openai.ts +630 -0
  199. package/src/routes/zen/util/provider/provider.ts +210 -0
  200. package/src/routes/zen/util/rateLimiter.ts +41 -0
  201. package/src/routes/zen/util/stickyProviderTracker.ts +16 -0
  202. package/src/routes/zen/util/trialLimiter.ts +49 -0
  203. package/src/routes/zen/v1/chat/completions.ts +11 -0
  204. package/src/routes/zen/v1/messages.ts +11 -0
  205. package/src/routes/zen/v1/models/[model].ts +13 -0
  206. package/src/routes/zen/v1/models.ts +60 -0
  207. package/src/routes/zen/v1/responses.ts +11 -0
  208. package/src/style/base.css +21 -0
  209. package/src/style/component/button.css +102 -0
  210. package/src/style/index.css +8 -0
  211. package/src/style/reset.css +76 -0
  212. package/src/style/token/color.css +91 -0
  213. package/src/style/token/font.css +21 -0
  214. package/src/style/token/space.css +46 -0
  215. package/sst-env.d.ts +9 -0
  216. package/tsconfig.json +21 -0
  217. package/vite.config.ts +25 -0
@@ -0,0 +1,752 @@
1
+ import { EventStreamCodec } from "@smithy/eventstream-codec"
2
+ import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
3
+ import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
4
+
5
+ type Usage = {
6
+ cache_creation?: {
7
+ ephemeral_5m_input_tokens?: number
8
+ ephemeral_1h_input_tokens?: number
9
+ }
10
+ cache_creation_input_tokens?: number
11
+ cache_read_input_tokens?: number
12
+ input_tokens?: number
13
+ output_tokens?: number
14
+ server_tool_use?: {
15
+ web_search_requests?: number
16
+ }
17
+ }
18
+
19
+ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => {
20
+ const isBedrockModelArn = providerModel.startsWith("arn:aws:bedrock:")
21
+ const isBedrockModelID = providerModel.startsWith("global.anthropic.")
22
+ const isBedrock = isBedrockModelArn || isBedrockModelID
23
+ const isSonnet = reqModel.includes("sonnet")
24
+ return {
25
+ format: "anthropic",
26
+ modifyUrl: (providerApi: string, isStream?: boolean) =>
27
+ isBedrock
28
+ ? `${providerApi}/model/${isBedrockModelArn ? encodeURIComponent(providerModel) : providerModel}/${isStream ? "invoke-with-response-stream" : "invoke"}`
29
+ : providerApi + "/messages",
30
+ modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
31
+ if (isBedrock) {
32
+ headers.set("Authorization", `Bearer ${apiKey}`)
33
+ } else {
34
+ headers.set("x-api-key", apiKey)
35
+ headers.set("anthropic-version", headers.get("anthropic-version") ?? "2023-06-01")
36
+ if (body.model.startsWith("claude-sonnet-")) {
37
+ headers.set("anthropic-beta", "context-1m-2025-08-07")
38
+ }
39
+ }
40
+ },
41
+ modifyBody: (body: Record<string, any>) => ({
42
+ ...body,
43
+ ...(isBedrock
44
+ ? {
45
+ anthropic_version: "bedrock-2023-05-31",
46
+ anthropic_beta: isSonnet ? "context-1m-2025-08-07" : undefined,
47
+ model: undefined,
48
+ stream: undefined,
49
+ }
50
+ : {
51
+ service_tier: "standard_only",
52
+ }),
53
+ }),
54
+ createBinaryStreamDecoder: () => {
55
+ if (!isBedrock) return undefined
56
+
57
+ const decoder = new TextDecoder()
58
+ const encoder = new TextEncoder()
59
+ const codec = new EventStreamCodec(toUtf8, fromUtf8)
60
+ let buffer = new Uint8Array(0)
61
+ return (value: Uint8Array) => {
62
+ const newBuffer = new Uint8Array(buffer.length + value.length)
63
+ newBuffer.set(buffer)
64
+ newBuffer.set(value, buffer.length)
65
+ buffer = newBuffer
66
+
67
+ const messages = []
68
+ while (buffer.length >= 4) {
69
+ // first 4 bytes are the total length (big-endian)
70
+ const totalLength = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength).getUint32(0, false)
71
+
72
+ // wait for more chunks
73
+ if (buffer.length < totalLength) break
74
+
75
+ try {
76
+ const subView = buffer.subarray(0, totalLength)
77
+ const decoded = codec.decode(subView)
78
+ buffer = buffer.slice(totalLength)
79
+
80
+ /* Example of Bedrock data
81
+ ```
82
+ {
83
+ bytes: 'eyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtb3B1cy00LTUtMjAyNTExMDEiLCJpZCI6Im1zZ19iZHJrXzAxMjVGdHRGb2lkNGlwWmZ4SzZMbktxeCIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjo0LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjEsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMTk2MywiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6MSwiZXBoZW1lcmFsXzFoX2lucHV0X3Rva2VucyI6MH0sIm91dHB1dF90b2tlbnMiOjF9fX0=',
84
+ p: '...'
85
+ }
86
+ ```
87
+
88
+ Decoded bytes
89
+ ```
90
+ {
91
+ type: 'message_start',
92
+ message: {
93
+ model: 'claude-opus-4-5-20251101',
94
+ id: 'msg_bdrk_0125FttFoid4ipZfxK6LnKqx',
95
+ type: 'message',
96
+ role: 'assistant',
97
+ content: [],
98
+ stop_reason: null,
99
+ stop_sequence: null,
100
+ usage: {
101
+ input_tokens: 4,
102
+ cache_creation_input_tokens: 1,
103
+ cache_read_input_tokens: 11963,
104
+ cache_creation: [Object],
105
+ output_tokens: 1
106
+ }
107
+ }
108
+ }
109
+ ```
110
+ */
111
+
112
+ /* Example of Anthropic data
113
+ ```
114
+ event: message_delta
115
+ data: {"type":"message_start","message":{"model":"claude-opus-4-5-20251101","id":"msg_01ETvwVWSKULxzPdkQ1xAnk2","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":11543,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":11543,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}}
116
+ ```
117
+ */
118
+ if (decoded.headers[":message-type"]?.value === "event") {
119
+ const data = decoder.decode(decoded.body, { stream: true })
120
+
121
+ const parsedDataResult = JSON.parse(data)
122
+ delete parsedDataResult.p
123
+ const binary = atob(parsedDataResult.bytes)
124
+ const uint8 = Uint8Array.from(binary, (c) => c.charCodeAt(0))
125
+ const bytes = decoder.decode(uint8)
126
+ const eventName = JSON.parse(bytes).type
127
+ messages.push([`event: ${eventName}`, "\n", `data: ${bytes}`, "\n\n"].join(""))
128
+ }
129
+ } catch (e) {
130
+ console.log("@@@EE@@@")
131
+ console.log(e)
132
+ break
133
+ }
134
+ }
135
+ return encoder.encode(messages.join(""))
136
+ }
137
+ },
138
+ streamSeparator: "\n\n",
139
+ createUsageParser: () => {
140
+ let usage: Usage
141
+
142
+ return {
143
+ parse: (chunk: string) => {
144
+ const data = chunk.split("\n")[1]
145
+ if (!data.startsWith("data: ")) return
146
+
147
+ let json
148
+ try {
149
+ json = JSON.parse(data.slice(6))
150
+ } catch (e) {
151
+ return
152
+ }
153
+
154
+ const usageUpdate = json.usage ?? json.message?.usage
155
+ if (!usageUpdate) return
156
+ usage = {
157
+ ...usage,
158
+ ...usageUpdate,
159
+ cache_creation: {
160
+ ...usage?.cache_creation,
161
+ ...usageUpdate.cache_creation,
162
+ },
163
+ server_tool_use: {
164
+ ...usage?.server_tool_use,
165
+ ...usageUpdate.server_tool_use,
166
+ },
167
+ }
168
+ },
169
+ retrieve: () => usage,
170
+ }
171
+ },
172
+ normalizeUsage: (usage: Usage) => ({
173
+ inputTokens: usage.input_tokens ?? 0,
174
+ outputTokens: usage.output_tokens ?? 0,
175
+ reasoningTokens: undefined,
176
+ cacheReadTokens: usage.cache_read_input_tokens ?? undefined,
177
+ cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined,
178
+ cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined,
179
+ }),
180
+ }
181
+ }
182
+
183
+ export function fromAnthropicRequest(body: any): CommonRequest {
184
+ if (!body || typeof body !== "object") return body
185
+
186
+ const msgs: any[] = []
187
+
188
+ const sys = Array.isArray(body.system) ? body.system : undefined
189
+ if (sys && sys.length > 0) {
190
+ for (const s of sys) {
191
+ if (!s) continue
192
+ if ((s as any).type !== "text") continue
193
+ if (typeof (s as any).text !== "string") continue
194
+ if ((s as any).text.length === 0) continue
195
+ msgs.push({ role: "system", content: (s as any).text })
196
+ }
197
+ }
198
+
199
+ const toImg = (src: any) => {
200
+ if (!src || typeof src !== "object") return undefined
201
+ if ((src as any).type === "url" && typeof (src as any).url === "string")
202
+ return { type: "image_url", image_url: { url: (src as any).url } }
203
+ if (
204
+ (src as any).type === "base64" &&
205
+ typeof (src as any).media_type === "string" &&
206
+ typeof (src as any).data === "string"
207
+ )
208
+ return {
209
+ type: "image_url",
210
+ image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` },
211
+ }
212
+ return undefined
213
+ }
214
+
215
+ const inMsgs = Array.isArray(body.messages) ? body.messages : []
216
+ for (const m of inMsgs) {
217
+ if (!m || !(m as any).role) continue
218
+
219
+ if ((m as any).role === "user") {
220
+ const partsIn = Array.isArray((m as any).content) ? (m as any).content : []
221
+ const partsOut: any[] = []
222
+ for (const p of partsIn) {
223
+ if (!p || !(p as any).type) continue
224
+ if ((p as any).type === "text" && typeof (p as any).text === "string")
225
+ partsOut.push({ type: "text", text: (p as any).text })
226
+ if ((p as any).type === "image") {
227
+ const ip = toImg((p as any).source)
228
+ if (ip) partsOut.push(ip)
229
+ }
230
+ if ((p as any).type === "tool_result") {
231
+ const id = (p as any).tool_use_id
232
+ const content =
233
+ typeof (p as any).content === "string" ? (p as any).content : JSON.stringify((p as any).content)
234
+ msgs.push({ role: "tool", tool_call_id: id, content })
235
+ }
236
+ }
237
+ if (partsOut.length > 0) {
238
+ if (partsOut.length === 1 && partsOut[0].type === "text") msgs.push({ role: "user", content: partsOut[0].text })
239
+ else msgs.push({ role: "user", content: partsOut })
240
+ }
241
+ continue
242
+ }
243
+
244
+ if ((m as any).role === "assistant") {
245
+ const partsIn = Array.isArray((m as any).content) ? (m as any).content : []
246
+ const texts: string[] = []
247
+ const tcs: any[] = []
248
+ for (const p of partsIn) {
249
+ if (!p || !(p as any).type) continue
250
+ if ((p as any).type === "text" && typeof (p as any).text === "string") texts.push((p as any).text)
251
+ if ((p as any).type === "tool_use") {
252
+ const name = (p as any).name
253
+ const id = (p as any).id
254
+ const inp = (p as any).input
255
+ const input = (() => {
256
+ if (typeof inp === "string") return inp
257
+ try {
258
+ return JSON.stringify(inp ?? {})
259
+ } catch {
260
+ return String(inp ?? "")
261
+ }
262
+ })()
263
+ tcs.push({ id, type: "function", function: { name, arguments: input } })
264
+ }
265
+ }
266
+ const out: any = { role: "assistant", content: texts.join("") }
267
+ if (tcs.length > 0) out.tool_calls = tcs
268
+ msgs.push(out)
269
+ continue
270
+ }
271
+ }
272
+
273
+ const tools = Array.isArray(body.tools)
274
+ ? body.tools
275
+ .filter((t: any) => t && typeof t === "object" && "input_schema" in t)
276
+ .map((t: any) => ({
277
+ type: "function",
278
+ function: {
279
+ name: (t as any).name,
280
+ description: (t as any).description,
281
+ parameters: (t as any).input_schema,
282
+ },
283
+ }))
284
+ : undefined
285
+
286
+ const tcin = body.tool_choice
287
+ const tc = (() => {
288
+ if (!tcin) return undefined
289
+ if ((tcin as any).type === "auto") return "auto"
290
+ if ((tcin as any).type === "any") return "required"
291
+ if ((tcin as any).type === "tool" && typeof (tcin as any).name === "string")
292
+ return { type: "function" as const, function: { name: (tcin as any).name } }
293
+ return undefined
294
+ })()
295
+
296
+ const stop = (() => {
297
+ const v = body.stop_sequences
298
+ if (!v) return undefined
299
+ if (Array.isArray(v)) return v.length === 1 ? v[0] : v
300
+ if (typeof v === "string") return v
301
+ return undefined
302
+ })()
303
+
304
+ return {
305
+ model: body.model,
306
+ max_tokens: body.max_tokens,
307
+ temperature: body.temperature,
308
+ top_p: body.top_p,
309
+ stop,
310
+ messages: msgs,
311
+ stream: !!body.stream,
312
+ tools,
313
+ tool_choice: tc,
314
+ }
315
+ }
316
+
317
+ export function toAnthropicRequest(body: CommonRequest) {
318
+ if (!body || typeof body !== "object") return body
319
+
320
+ const sysIn = Array.isArray(body.messages) ? body.messages.filter((m: any) => m && m.role === "system") : []
321
+ let ccCount = 0
322
+ const cc = () => {
323
+ ccCount++
324
+ return ccCount <= 4 ? { cache_control: { type: "ephemeral" } } : {}
325
+ }
326
+ const system = sysIn
327
+ .filter((m: any) => typeof m.content === "string" && m.content.length > 0)
328
+ .map((m: any) => ({ type: "text", text: m.content, ...cc() }))
329
+
330
+ const msgsIn = Array.isArray(body.messages) ? body.messages : []
331
+ const msgsOut: any[] = []
332
+
333
+ const toSrc = (p: any) => {
334
+ if (!p || typeof p !== "object") return undefined
335
+ if ((p as any).type === "image_url" && (p as any).image_url) {
336
+ const u = (p as any).image_url.url ?? (p as any).image_url
337
+ if (typeof u === "string" && u.startsWith("data:")) {
338
+ const m = u.match(/^data:([^;]+);base64,(.*)$/)
339
+ if (m) return { type: "base64", media_type: m[1], data: m[2] }
340
+ }
341
+ if (typeof u === "string") return { type: "url", url: u }
342
+ }
343
+ return undefined
344
+ }
345
+
346
+ for (const m of msgsIn) {
347
+ if (!m || !(m as any).role) continue
348
+
349
+ if ((m as any).role === "user") {
350
+ if (typeof (m as any).content === "string") {
351
+ msgsOut.push({
352
+ role: "user",
353
+ content: [{ type: "text", text: (m as any).content, ...cc() }],
354
+ })
355
+ } else if (Array.isArray((m as any).content)) {
356
+ const parts: any[] = []
357
+ for (const p of (m as any).content) {
358
+ if (!p || !(p as any).type) continue
359
+ if ((p as any).type === "text" && typeof (p as any).text === "string")
360
+ parts.push({ type: "text", text: (p as any).text, ...cc() })
361
+ if ((p as any).type === "image_url") {
362
+ const s = toSrc(p)
363
+ if (s) parts.push({ type: "image", source: s, ...cc() })
364
+ }
365
+ }
366
+ if (parts.length > 0) msgsOut.push({ role: "user", content: parts })
367
+ }
368
+ continue
369
+ }
370
+
371
+ if ((m as any).role === "assistant") {
372
+ const out: any = { role: "assistant", content: [] as any[] }
373
+ if (typeof (m as any).content === "string" && (m as any).content.length > 0) {
374
+ ;(out.content as any[]).push({ type: "text", text: (m as any).content, ...cc() })
375
+ }
376
+ if (Array.isArray((m as any).tool_calls)) {
377
+ for (const tc of (m as any).tool_calls) {
378
+ if ((tc as any).type === "function" && (tc as any).function) {
379
+ let input: any
380
+ const a = (tc as any).function.arguments
381
+ if (typeof a === "string") {
382
+ try {
383
+ input = JSON.parse(a)
384
+ } catch {
385
+ input = a
386
+ }
387
+ } else input = a
388
+ const id = (tc as any).id || `toolu_${Math.random().toString(36).slice(2)}`
389
+ ;(out.content as any[]).push({
390
+ type: "tool_use",
391
+ id,
392
+ name: (tc as any).function.name,
393
+ input,
394
+ ...cc(),
395
+ })
396
+ }
397
+ }
398
+ }
399
+ if ((out.content as any[]).length > 0) msgsOut.push(out)
400
+ continue
401
+ }
402
+
403
+ if ((m as any).role === "tool") {
404
+ msgsOut.push({
405
+ role: "user",
406
+ content: [
407
+ {
408
+ type: "tool_result",
409
+ tool_use_id: (m as any).tool_call_id,
410
+ content: (m as any).content,
411
+ ...cc(),
412
+ },
413
+ ],
414
+ })
415
+ continue
416
+ }
417
+ }
418
+
419
+ const tools = Array.isArray(body.tools)
420
+ ? body.tools
421
+ .filter((t: any) => t && typeof t === "object" && (t as any).type === "function")
422
+ .map((t: any) => ({
423
+ name: (t as any).function.name,
424
+ description: (t as any).function.description,
425
+ input_schema: (t as any).function.parameters,
426
+ ...cc(),
427
+ }))
428
+ : undefined
429
+
430
+ const tcIn = body.tool_choice
431
+ const tool_choice = (() => {
432
+ if (!tcIn) return undefined
433
+ if (tcIn === "auto") return { type: "auto" }
434
+ if (tcIn === "required") return { type: "any" }
435
+ if ((tcIn as any).type === "function" && (tcIn as any).function?.name)
436
+ return { type: "tool", name: (tcIn as any).function.name }
437
+ return undefined
438
+ })()
439
+
440
+ const stop_sequences = (() => {
441
+ const v = body.stop
442
+ if (!v) return undefined
443
+ if (Array.isArray(v)) return v
444
+ if (typeof v === "string") return [v]
445
+ return undefined
446
+ })()
447
+
448
+ return {
449
+ max_tokens: body.max_tokens ?? 32_000,
450
+ temperature: body.temperature,
451
+ top_p: body.top_p,
452
+ system: system.length > 0 ? system : undefined,
453
+ messages: msgsOut,
454
+ stream: !!body.stream,
455
+ tools,
456
+ tool_choice,
457
+ stop_sequences,
458
+ }
459
+ }
460
+
461
+ export function fromAnthropicResponse(resp: any): CommonResponse {
462
+ if (!resp || typeof resp !== "object") return resp
463
+
464
+ if (Array.isArray((resp as any).choices)) return resp
465
+
466
+ const isAnthropic = typeof (resp as any).type === "string" && (resp as any).type === "message"
467
+ if (!isAnthropic) return resp
468
+
469
+ const idIn = (resp as any).id
470
+ const id =
471
+ typeof idIn === "string" ? idIn.replace(/^msg_/, "chatcmpl_") : `chatcmpl_${Math.random().toString(36).slice(2)}`
472
+ const model = (resp as any).model
473
+
474
+ const blocks: any[] = Array.isArray((resp as any).content) ? (resp as any).content : []
475
+ const text = blocks
476
+ .filter((b) => b && b.type === "text" && typeof (b as any).text === "string")
477
+ .map((b: any) => b.text)
478
+ .join("")
479
+ const tcs = blocks
480
+ .filter((b) => b && b.type === "tool_use")
481
+ .map((b: any) => {
482
+ const name = (b as any).name
483
+ const args = (() => {
484
+ const inp = (b as any).input
485
+ if (typeof inp === "string") return inp
486
+ try {
487
+ return JSON.stringify(inp ?? {})
488
+ } catch {
489
+ return String(inp ?? "")
490
+ }
491
+ })()
492
+ const tid =
493
+ typeof (b as any).id === "string" && (b as any).id.length > 0
494
+ ? (b as any).id
495
+ : `toolu_${Math.random().toString(36).slice(2)}`
496
+ return { id: tid, type: "function" as const, function: { name, arguments: args } }
497
+ })
498
+
499
+ const finish = (r: string | null) => {
500
+ if (r === "end_turn") return "stop"
501
+ if (r === "tool_use") return "tool_calls"
502
+ if (r === "max_tokens") return "length"
503
+ if (r === "content_filter") return "content_filter"
504
+ return null
505
+ }
506
+
507
+ const u = (resp as any).usage
508
+ const usage = (() => {
509
+ if (!u) return undefined as any
510
+ const pt = typeof (u as any).input_tokens === "number" ? (u as any).input_tokens : undefined
511
+ const ct = typeof (u as any).output_tokens === "number" ? (u as any).output_tokens : undefined
512
+ const total = pt != null && ct != null ? pt + ct : undefined
513
+ const cached =
514
+ typeof (u as any).cache_read_input_tokens === "number" ? (u as any).cache_read_input_tokens : undefined
515
+ const details = cached != null ? { cached_tokens: cached } : undefined
516
+ return {
517
+ prompt_tokens: pt,
518
+ completion_tokens: ct,
519
+ total_tokens: total,
520
+ ...(details ? { prompt_tokens_details: details } : {}),
521
+ }
522
+ })()
523
+
524
+ return {
525
+ id,
526
+ object: "chat.completion",
527
+ created: Math.floor(Date.now() / 1000),
528
+ model,
529
+ choices: [
530
+ {
531
+ index: 0,
532
+ message: {
533
+ role: "assistant",
534
+ ...(text && text.length > 0 ? { content: text } : {}),
535
+ ...(tcs.length > 0 ? { tool_calls: tcs } : {}),
536
+ },
537
+ finish_reason: finish((resp as any).stop_reason ?? null),
538
+ },
539
+ ],
540
+ ...(usage ? { usage } : {}),
541
+ }
542
+ }
543
+
544
+ export function toAnthropicResponse(resp: CommonResponse) {
545
+ if (!resp || typeof resp !== "object") return resp
546
+
547
+ if (!Array.isArray((resp as any).choices)) return resp
548
+
549
+ const choice = (resp as any).choices[0]
550
+ if (!choice) return resp
551
+
552
+ const message = choice.message
553
+ if (!message) return resp
554
+
555
+ const content: any[] = []
556
+
557
+ if (typeof message.content === "string" && message.content.length > 0)
558
+ content.push({ type: "text", text: message.content })
559
+
560
+ if (Array.isArray(message.tool_calls)) {
561
+ for (const tc of message.tool_calls) {
562
+ if ((tc as any).type === "function" && (tc as any).function) {
563
+ let input: any
564
+ try {
565
+ input = JSON.parse((tc as any).function.arguments)
566
+ } catch {
567
+ input = (tc as any).function.arguments
568
+ }
569
+ content.push({
570
+ type: "tool_use",
571
+ id: (tc as any).id,
572
+ name: (tc as any).function.name,
573
+ input,
574
+ })
575
+ }
576
+ }
577
+ }
578
+
579
+ const stop_reason = (() => {
580
+ const r = choice.finish_reason
581
+ if (r === "stop") return "end_turn"
582
+ if (r === "tool_calls") return "tool_use"
583
+ if (r === "length") return "max_tokens"
584
+ if (r === "content_filter") return "content_filter"
585
+ return null
586
+ })()
587
+
588
+ const usage = (() => {
589
+ const u = (resp as any).usage
590
+ if (!u) return undefined
591
+ return {
592
+ input_tokens: u.prompt_tokens,
593
+ output_tokens: u.completion_tokens,
594
+ cache_read_input_tokens: u.prompt_tokens_details?.cached_tokens,
595
+ }
596
+ })()
597
+
598
+ return {
599
+ id: (resp as any).id,
600
+ type: "message",
601
+ role: "assistant",
602
+ content: content.length > 0 ? content : [{ type: "text", text: "" }],
603
+ model: (resp as any).model,
604
+ stop_reason,
605
+ usage,
606
+ }
607
+ }
608
+
609
+ export function fromAnthropicChunk(chunk: string): CommonChunk | string {
610
+ // Anthropic sends two lines per part: "event: <type>\n" + "data: <json>"
611
+ const lines = chunk.split("\n")
612
+ const dataLine = lines.find((l) => l.startsWith("data: "))
613
+ if (!dataLine) return chunk
614
+
615
+ let json
616
+ try {
617
+ json = JSON.parse(dataLine.slice(6))
618
+ } catch {
619
+ return chunk
620
+ }
621
+
622
+ const out: CommonChunk = {
623
+ id: json.id ?? json.message?.id ?? "",
624
+ object: "chat.completion.chunk",
625
+ created: Math.floor(Date.now() / 1000),
626
+ model: json.model ?? json.message?.model ?? "",
627
+ choices: [],
628
+ }
629
+
630
+ if (json.type === "content_block_start") {
631
+ const cb = json.content_block
632
+ if (cb?.type === "text") {
633
+ out.choices.push({
634
+ index: json.index ?? 0,
635
+ delta: { role: "assistant", content: "" },
636
+ finish_reason: null,
637
+ })
638
+ } else if (cb?.type === "tool_use") {
639
+ out.choices.push({
640
+ index: json.index ?? 0,
641
+ delta: {
642
+ tool_calls: [
643
+ {
644
+ index: json.index ?? 0,
645
+ id: cb.id,
646
+ type: "function",
647
+ function: { name: cb.name, arguments: "" },
648
+ },
649
+ ],
650
+ },
651
+ finish_reason: null,
652
+ })
653
+ }
654
+ }
655
+
656
+ if (json.type === "content_block_delta") {
657
+ const d = json.delta
658
+ if (d?.type === "text_delta") {
659
+ out.choices.push({ index: json.index ?? 0, delta: { content: d.text }, finish_reason: null })
660
+ } else if (d?.type === "input_json_delta") {
661
+ out.choices.push({
662
+ index: json.index ?? 0,
663
+ delta: {
664
+ tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }],
665
+ },
666
+ finish_reason: null,
667
+ })
668
+ }
669
+ }
670
+
671
+ if (json.type === "message_delta") {
672
+ const d = json.delta
673
+ const finish_reason = (() => {
674
+ const r = d?.stop_reason
675
+ if (r === "end_turn") return "stop"
676
+ if (r === "tool_use") return "tool_calls"
677
+ if (r === "max_tokens") return "length"
678
+ if (r === "content_filter") return "content_filter"
679
+ return null
680
+ })()
681
+
682
+ out.choices.push({ index: 0, delta: {}, finish_reason })
683
+ }
684
+
685
+ if (json.usage) {
686
+ const u = json.usage
687
+ out.usage = {
688
+ prompt_tokens: u.input_tokens,
689
+ completion_tokens: u.output_tokens,
690
+ total_tokens: (u.input_tokens || 0) + (u.output_tokens || 0),
691
+ ...(u.cache_read_input_tokens ? { prompt_tokens_details: { cached_tokens: u.cache_read_input_tokens } } : {}),
692
+ }
693
+ }
694
+
695
+ return out
696
+ }
697
+
698
+ export function toAnthropicChunk(chunk: CommonChunk): string {
699
+ if (!chunk.choices || !Array.isArray(chunk.choices) || chunk.choices.length === 0) {
700
+ return JSON.stringify({})
701
+ }
702
+
703
+ const choice = chunk.choices[0]
704
+ const delta = choice.delta
705
+ if (!delta) return JSON.stringify({})
706
+
707
+ const result: any = {}
708
+
709
+ if (delta.content) {
710
+ result.type = "content_block_delta"
711
+ result.index = 0
712
+ result.delta = { type: "text_delta", text: delta.content }
713
+ }
714
+
715
+ if (delta.tool_calls) {
716
+ for (const tc of delta.tool_calls) {
717
+ if (tc.function?.name) {
718
+ result.type = "content_block_start"
719
+ result.index = tc.index ?? 0
720
+ result.content_block = { type: "tool_use", id: tc.id, name: tc.function.name, input: {} }
721
+ } else if (tc.function?.arguments) {
722
+ result.type = "content_block_delta"
723
+ result.index = tc.index ?? 0
724
+ result.delta = { type: "input_json_delta", partial_json: tc.function.arguments }
725
+ }
726
+ }
727
+ }
728
+
729
+ if (choice.finish_reason) {
730
+ const stop_reason = (() => {
731
+ const r = choice.finish_reason
732
+ if (r === "stop") return "end_turn"
733
+ if (r === "tool_calls") return "tool_use"
734
+ if (r === "length") return "max_tokens"
735
+ if (r === "content_filter") return "content_filter"
736
+ return null
737
+ })()
738
+ result.type = "message_delta"
739
+ result.delta = { stop_reason, stop_sequence: null }
740
+ }
741
+
742
+ if (chunk.usage) {
743
+ const u = chunk.usage
744
+ result.usage = {
745
+ input_tokens: u.prompt_tokens,
746
+ output_tokens: u.completion_tokens,
747
+ cache_read_input_tokens: u.prompt_tokens_details?.cached_tokens,
748
+ }
749
+ }
750
+
751
+ return JSON.stringify(result)
752
+ }