codeblog-app 2.3.1 → 2.3.2

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 (83) hide show
  1. package/package.json +8 -73
  2. package/drizzle/0000_init.sql +0 -34
  3. package/drizzle/meta/_journal.json +0 -13
  4. package/drizzle.config.ts +0 -10
  5. package/src/ai/__tests__/chat.test.ts +0 -188
  6. package/src/ai/__tests__/compat.test.ts +0 -46
  7. package/src/ai/__tests__/home.ai-stream.integration.test.ts +0 -77
  8. package/src/ai/__tests__/provider-registry.test.ts +0 -61
  9. package/src/ai/__tests__/provider.test.ts +0 -238
  10. package/src/ai/__tests__/stream-events.test.ts +0 -152
  11. package/src/ai/__tests__/tools.test.ts +0 -93
  12. package/src/ai/chat.ts +0 -336
  13. package/src/ai/configure.ts +0 -143
  14. package/src/ai/models.ts +0 -26
  15. package/src/ai/provider-registry.ts +0 -150
  16. package/src/ai/provider.ts +0 -264
  17. package/src/ai/stream-events.ts +0 -64
  18. package/src/ai/tools.ts +0 -118
  19. package/src/ai/types.ts +0 -105
  20. package/src/auth/index.ts +0 -49
  21. package/src/auth/oauth.ts +0 -123
  22. package/src/cli/__tests__/commands.test.ts +0 -229
  23. package/src/cli/cmd/agent.ts +0 -97
  24. package/src/cli/cmd/ai.ts +0 -10
  25. package/src/cli/cmd/chat.ts +0 -190
  26. package/src/cli/cmd/comment.ts +0 -67
  27. package/src/cli/cmd/config.ts +0 -153
  28. package/src/cli/cmd/feed.ts +0 -53
  29. package/src/cli/cmd/forum.ts +0 -106
  30. package/src/cli/cmd/login.ts +0 -45
  31. package/src/cli/cmd/logout.ts +0 -12
  32. package/src/cli/cmd/me.ts +0 -188
  33. package/src/cli/cmd/post.ts +0 -25
  34. package/src/cli/cmd/publish.ts +0 -64
  35. package/src/cli/cmd/scan.ts +0 -78
  36. package/src/cli/cmd/search.ts +0 -35
  37. package/src/cli/cmd/setup.ts +0 -622
  38. package/src/cli/cmd/tui.ts +0 -20
  39. package/src/cli/cmd/uninstall.ts +0 -281
  40. package/src/cli/cmd/update.ts +0 -123
  41. package/src/cli/cmd/vote.ts +0 -50
  42. package/src/cli/cmd/whoami.ts +0 -18
  43. package/src/cli/mcp-print.ts +0 -6
  44. package/src/cli/ui.ts +0 -357
  45. package/src/config/index.ts +0 -92
  46. package/src/flag/index.ts +0 -23
  47. package/src/global/index.ts +0 -38
  48. package/src/id/index.ts +0 -20
  49. package/src/index.ts +0 -203
  50. package/src/mcp/__tests__/client.test.ts +0 -149
  51. package/src/mcp/__tests__/e2e.ts +0 -331
  52. package/src/mcp/__tests__/integration.ts +0 -148
  53. package/src/mcp/client.ts +0 -118
  54. package/src/server/index.ts +0 -48
  55. package/src/storage/chat.ts +0 -73
  56. package/src/storage/db.ts +0 -85
  57. package/src/storage/schema.sql.ts +0 -39
  58. package/src/storage/schema.ts +0 -1
  59. package/src/tui/__tests__/input-intent.test.ts +0 -27
  60. package/src/tui/__tests__/stream-assembler.test.ts +0 -33
  61. package/src/tui/ai-stream.ts +0 -28
  62. package/src/tui/app.tsx +0 -210
  63. package/src/tui/commands.ts +0 -220
  64. package/src/tui/context/exit.tsx +0 -15
  65. package/src/tui/context/helper.tsx +0 -25
  66. package/src/tui/context/route.tsx +0 -24
  67. package/src/tui/context/theme.tsx +0 -471
  68. package/src/tui/input-intent.ts +0 -26
  69. package/src/tui/routes/home.tsx +0 -1060
  70. package/src/tui/routes/model.tsx +0 -210
  71. package/src/tui/routes/notifications.tsx +0 -87
  72. package/src/tui/routes/post.tsx +0 -102
  73. package/src/tui/routes/search.tsx +0 -105
  74. package/src/tui/routes/setup.tsx +0 -267
  75. package/src/tui/routes/trending.tsx +0 -107
  76. package/src/tui/stream-assembler.ts +0 -49
  77. package/src/util/__tests__/context.test.ts +0 -31
  78. package/src/util/__tests__/lazy.test.ts +0 -37
  79. package/src/util/context.ts +0 -23
  80. package/src/util/error.ts +0 -46
  81. package/src/util/lazy.ts +0 -18
  82. package/src/util/log.ts +0 -144
  83. package/tsconfig.json +0 -11
@@ -1,264 +0,0 @@
1
- import { createAnthropic } from "@ai-sdk/anthropic"
2
- import { createOpenAI } from "@ai-sdk/openai"
3
- import { createGoogleGenerativeAI } from "@ai-sdk/google"
4
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
5
- import { type LanguageModel, type Provider as SDK } from "ai"
6
- import { Config } from "../config"
7
- import { Log } from "../util/log"
8
- import { BUILTIN_MODELS as CORE_MODELS, DEFAULT_MODEL as CORE_DEFAULT_MODEL, type ModelInfo as CoreModelInfo } from "./models"
9
- import { loadProviders, PROVIDER_BASE_URL_ENV, PROVIDER_ENV, routeModel } from "./provider-registry"
10
- import { patchRequestByCompat, resolveCompat, type ModelApi, type ModelCompatConfig } from "./types"
11
-
12
- const log = Log.create({ service: "ai-provider" })
13
-
14
- export namespace AIProvider {
15
- const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = {
16
- "@ai-sdk/anthropic": createAnthropic as any,
17
- "@ai-sdk/openai": createOpenAI as any,
18
- "@ai-sdk/google": createGoogleGenerativeAI as any,
19
- "@ai-sdk/openai-compatible": createOpenAICompatible as any,
20
- }
21
-
22
- const PROVIDER_NPM: Record<ModelApi, string> = {
23
- anthropic: "@ai-sdk/anthropic",
24
- openai: "@ai-sdk/openai",
25
- google: "@ai-sdk/google",
26
- "openai-compatible": "@ai-sdk/openai-compatible",
27
- }
28
-
29
- export const BUILTIN_MODELS = CORE_MODELS
30
- export const DEFAULT_MODEL = CORE_DEFAULT_MODEL
31
- export type ModelInfo = CoreModelInfo
32
-
33
- export async function getApiKey(providerID: string): Promise<string | undefined> {
34
- const envKeys = PROVIDER_ENV[providerID] || []
35
- for (const key of envKeys) {
36
- if (process.env[key]) return process.env[key]
37
- }
38
- const cfg = await Config.load()
39
- return cfg.providers?.[providerID]?.api_key
40
- }
41
-
42
- export async function getBaseUrl(providerID: string): Promise<string | undefined> {
43
- const envKeys = PROVIDER_BASE_URL_ENV[providerID] || []
44
- for (const key of envKeys) {
45
- if (process.env[key]) return process.env[key]
46
- }
47
- const cfg = await Config.load()
48
- return cfg.providers?.[providerID]?.base_url
49
- }
50
-
51
- export async function listProviders(): Promise<Record<string, { name: string; models: string[]; hasKey: boolean }>> {
52
- const result: Record<string, { name: string; models: string[]; hasKey: boolean }> = {}
53
- for (const model of Object.values(BUILTIN_MODELS)) {
54
- if (!result[model.providerID]) {
55
- const key = await getApiKey(model.providerID)
56
- result[model.providerID] = { name: model.providerID, models: [], hasKey: !!key }
57
- }
58
- if (!result[model.providerID]!.models.includes(model.id)) {
59
- result[model.providerID]!.models.push(model.id)
60
- }
61
- }
62
-
63
- const compatKey = await getApiKey("openai-compatible")
64
- const compatBase = await getBaseUrl("openai-compatible")
65
- if (compatKey && compatBase) {
66
- const remoteModels = await fetchRemoteModels(compatBase, compatKey)
67
- result["openai-compatible"] = { name: "OpenAI Compatible", models: remoteModels, hasKey: true }
68
- }
69
-
70
- const loaded = await loadProviders()
71
- for (const provider of Object.values(loaded.providers)) {
72
- if (result[provider.id]) continue
73
- if (!provider.apiKey) continue
74
- result[provider.id] = { name: provider.id, models: [], hasKey: true }
75
- }
76
-
77
- return result
78
- }
79
-
80
- const sdkCache = new Map<string, SDK>()
81
-
82
- export async function getModel(modelID?: string): Promise<LanguageModel> {
83
- const useRegistry = await Config.featureEnabled("ai_provider_registry_v2")
84
- if (useRegistry) {
85
- const route = await routeModel(modelID)
86
- return getLanguageModel(route.providerID, route.modelID, route.apiKey, undefined, route.baseURL, route.compat)
87
- }
88
- return getModelLegacy(modelID)
89
- }
90
-
91
- export async function resolveModelCompat(modelID?: string): Promise<ModelCompatConfig> {
92
- const useRegistry = await Config.featureEnabled("ai_provider_registry_v2")
93
- if (useRegistry) return (await routeModel(modelID)).compat
94
- return (await resolveLegacyRoute(modelID)).compat
95
- }
96
-
97
- async function getModelLegacy(modelID?: string): Promise<LanguageModel> {
98
- const route = await resolveLegacyRoute(modelID)
99
- return getLanguageModel(route.providerID, route.modelID, route.apiKey, undefined, route.baseURL, route.compat)
100
- }
101
-
102
- async function resolveLegacyRoute(modelID?: string): Promise<{
103
- providerID: string
104
- modelID: string
105
- apiKey: string
106
- baseURL?: string
107
- compat: ModelCompatConfig
108
- }> {
109
- const requested = modelID || (await getConfiguredModel()) || DEFAULT_MODEL
110
- const cfg = await Config.load()
111
-
112
- const builtin = BUILTIN_MODELS[requested]
113
- if (builtin) {
114
- const apiKey = await getApiKey(builtin.providerID)
115
- if (!apiKey) throw noKeyError(builtin.providerID)
116
- const baseURL = await getBaseUrl(builtin.providerID)
117
- return {
118
- providerID: builtin.providerID,
119
- modelID: requested,
120
- apiKey,
121
- baseURL,
122
- compat: resolveCompat({ providerID: builtin.providerID, modelID: requested, providerConfig: cfg.providers?.[builtin.providerID] }),
123
- }
124
- }
125
-
126
- if (requested.includes("/")) {
127
- const [providerID, ...rest] = requested.split("/")
128
- const modelID = rest.join("/")
129
- const apiKey = await getApiKey(providerID!)
130
- if (!apiKey) throw noKeyError(providerID!)
131
- const baseURL = await getBaseUrl(providerID!)
132
- return {
133
- providerID: providerID!,
134
- modelID,
135
- apiKey,
136
- baseURL,
137
- compat: resolveCompat({ providerID: providerID!, modelID, providerConfig: cfg.providers?.[providerID!] }),
138
- }
139
- }
140
-
141
- if (cfg.providers) {
142
- for (const [providerID, p] of Object.entries(cfg.providers)) {
143
- if (!p.api_key) continue
144
- const baseURL = p.base_url || (await getBaseUrl(providerID))
145
- if (!baseURL) continue
146
- log.info("legacy fallback: unknown model routed to first provider with base_url", { provider: providerID, model: requested })
147
- return {
148
- providerID,
149
- modelID: requested,
150
- apiKey: p.api_key,
151
- baseURL,
152
- compat: resolveCompat({ providerID, modelID: requested, providerConfig: p }),
153
- }
154
- }
155
- }
156
-
157
- throw new Error(`Unknown model: ${requested}. Run: codeblog config --list`)
158
- }
159
-
160
- function packageForCompat(compat: ModelCompatConfig): string {
161
- let pkg = PROVIDER_NPM[compat.api]
162
- if (compat.modelID.startsWith("claude-") && pkg === "@ai-sdk/openai-compatible") {
163
- pkg = "@ai-sdk/anthropic"
164
- log.info("auto-detected claude model for openai-compatible route, using anthropic sdk", { model: compat.modelID })
165
- }
166
- return pkg
167
- }
168
-
169
- function getLanguageModel(
170
- providerID: string,
171
- modelID: string,
172
- apiKey: string,
173
- npm?: string,
174
- baseURL?: string,
175
- providedCompat?: ModelCompatConfig,
176
- ): LanguageModel {
177
- const compat = providedCompat || resolveCompat({ providerID, modelID })
178
- const pkg = npm || packageForCompat(compat)
179
- const cacheKey = `${providerID}:${pkg}:${compat.cacheKey}:${apiKey.slice(0, 8)}:${baseURL || ""}`
180
-
181
- let sdk = sdkCache.get(cacheKey)
182
- if (!sdk) {
183
- const createFn = BUNDLED_PROVIDERS[pkg]
184
- if (!createFn) throw new Error(`No bundled provider for ${pkg}.`)
185
- const opts: Record<string, unknown> = { apiKey, name: providerID }
186
- if (baseURL) {
187
- const clean = baseURL.replace(/\/+$/, "")
188
- opts.baseURL = clean.endsWith("/v1") ? clean : `${clean}/v1`
189
- }
190
- if (pkg === "@ai-sdk/openai-compatible") {
191
- opts.transformRequestBody = (body: Record<string, any>) => patchRequestByCompat(compat, body)
192
- }
193
- sdk = createFn(opts)
194
- sdkCache.set(cacheKey, sdk)
195
- }
196
-
197
- if (pkg === "@ai-sdk/openai-compatible" && typeof (sdk as any).chatModel === "function") {
198
- return (sdk as any).chatModel(modelID)
199
- }
200
- if (typeof (sdk as any).languageModel === "function") {
201
- return (sdk as any).languageModel(modelID)
202
- }
203
- return (sdk as any)(modelID)
204
- }
205
-
206
- async function fetchRemoteModels(base: string, key: string): Promise<string[]> {
207
- try {
208
- const clean = base.replace(/\/+$/, "")
209
- const url = clean.endsWith("/v1") ? `${clean}/models` : `${clean}/v1/models`
210
- const r = await fetch(url, {
211
- headers: { Authorization: `Bearer ${key}` },
212
- signal: AbortSignal.timeout(8000),
213
- })
214
- if (!r.ok) return []
215
- const data = await r.json() as { data?: Array<{ id: string }> }
216
- return data.data?.map((m) => m.id) ?? []
217
- } catch {
218
- return []
219
- }
220
- }
221
-
222
- function noKeyError(providerID: string): Error {
223
- const envKeys = PROVIDER_ENV[providerID] || []
224
- const envHint = envKeys[0] || `${providerID.toUpperCase().replace(/-/g, "_")}_API_KEY`
225
- return new Error(`No API key for ${providerID}. Set ${envHint} or run: codeblog config --provider ${providerID} --api-key <key>`)
226
- }
227
-
228
- async function getConfiguredModel(): Promise<string | undefined> {
229
- const cfg = await Config.load()
230
- return cfg.model
231
- }
232
-
233
- export async function hasAnyKey(): Promise<boolean> {
234
- const loaded = await loadProviders()
235
- return Object.values(loaded.providers).some((p) => !!p.apiKey)
236
- }
237
-
238
- export async function available(): Promise<Array<{ model: ModelInfo; hasKey: boolean }>> {
239
- const result: Array<{ model: ModelInfo; hasKey: boolean }> = []
240
- for (const model of Object.values(BUILTIN_MODELS)) {
241
- const apiKey = await getApiKey(model.providerID)
242
- result.push({ model, hasKey: !!apiKey })
243
- }
244
-
245
- const compatKey = await getApiKey("openai-compatible")
246
- const compatBase = await getBaseUrl("openai-compatible")
247
- if (compatKey && compatBase) {
248
- const remoteModels = await fetchRemoteModels(compatBase, compatKey)
249
- for (const id of remoteModels) {
250
- if (BUILTIN_MODELS[id]) continue
251
- result.push({
252
- model: { id, providerID: "openai-compatible", name: id, contextWindow: 0, outputTokens: 0 },
253
- hasKey: true,
254
- })
255
- }
256
- }
257
- return result
258
- }
259
-
260
- export function parseModel(model: string) {
261
- const [providerID, ...rest] = model.split("/")
262
- return { providerID, modelID: rest.join("/") }
263
- }
264
- }
@@ -1,64 +0,0 @@
1
- export type StreamEventType =
2
- | "run-start"
3
- | "text-delta"
4
- | "tool-start"
5
- | "tool-result"
6
- | "error"
7
- | "run-finish"
8
-
9
- interface StreamEventBase {
10
- type: StreamEventType
11
- runId: string
12
- seq: number
13
- }
14
-
15
- export interface RunStartEvent extends StreamEventBase {
16
- type: "run-start"
17
- modelID: string
18
- messageCount: number
19
- }
20
-
21
- export interface TextDeltaEvent extends StreamEventBase {
22
- type: "text-delta"
23
- text: string
24
- }
25
-
26
- export interface ToolStartEvent extends StreamEventBase {
27
- type: "tool-start"
28
- callID: string
29
- name: string
30
- args: unknown
31
- }
32
-
33
- export interface ToolResultEvent extends StreamEventBase {
34
- type: "tool-result"
35
- callID: string
36
- name: string
37
- result: unknown
38
- }
39
-
40
- export interface ErrorEvent extends StreamEventBase {
41
- type: "error"
42
- error: Error
43
- }
44
-
45
- export interface RunFinishEvent extends StreamEventBase {
46
- type: "run-finish"
47
- text: string
48
- aborted: boolean
49
- }
50
-
51
- export type StreamEvent =
52
- | RunStartEvent
53
- | TextDeltaEvent
54
- | ToolStartEvent
55
- | ToolResultEvent
56
- | ErrorEvent
57
- | RunFinishEvent
58
-
59
- export function createRunEventFactory(runId: string = crypto.randomUUID()) {
60
- let seq = 0
61
- const next = <T extends StreamEventType, P extends Record<string, unknown>>(type: T, payload: P) =>
62
- ({ type, runId, seq: ++seq, ...payload }) as unknown as Extract<StreamEvent, { type: T }>
63
- return { runId, next }
64
- }
package/src/ai/tools.ts DELETED
@@ -1,118 +0,0 @@
1
- import { tool, jsonSchema } from "ai"
2
- import { McpBridge } from "../mcp/client"
3
- import { Log } from "../util/log"
4
- import type { ModelCompatConfig } from "./types"
5
-
6
- const log = Log.create({ service: "ai-tools" })
7
-
8
- // ---------------------------------------------------------------------------
9
- // Tool display labels for the TUI streaming indicator.
10
- // Kept as a static fallback — new tools added to MCP will show their name
11
- // as-is if not listed here, which is acceptable.
12
- // ---------------------------------------------------------------------------
13
- export const TOOL_LABELS: Record<string, string> = {
14
- scan_sessions: "Scanning IDE sessions...",
15
- read_session: "Reading session...",
16
- analyze_session: "Analyzing session...",
17
- post_to_codeblog: "Publishing post...",
18
- auto_post: "Auto-posting...",
19
- weekly_digest: "Generating weekly digest...",
20
- browse_posts: "Browsing posts...",
21
- search_posts: "Searching posts...",
22
- read_post: "Reading post...",
23
- comment_on_post: "Posting comment...",
24
- vote_on_post: "Voting...",
25
- edit_post: "Editing post...",
26
- delete_post: "Deleting post...",
27
- bookmark_post: "Bookmarking...",
28
- browse_by_tag: "Browsing tags...",
29
- trending_topics: "Loading trending...",
30
- explore_and_engage: "Exploring posts...",
31
- join_debate: "Loading debates...",
32
- my_notifications: "Checking notifications...",
33
- manage_agents: "Managing agents...",
34
- my_posts: "Loading your posts...",
35
- my_dashboard: "Loading dashboard...",
36
- follow_user: "Processing follow...",
37
- codeblog_setup: "Configuring CodeBlog...",
38
- codeblog_status: "Checking status...",
39
- }
40
-
41
- // ---------------------------------------------------------------------------
42
- // Helper: call MCP tool and return result
43
- // ---------------------------------------------------------------------------
44
- async function mcp(name: string, args: Record<string, unknown> = {}): Promise<any> {
45
- return McpBridge.callToolJSON(name, args)
46
- }
47
-
48
- // Strip undefined/null values from args before sending to MCP
49
- function clean(obj: Record<string, unknown>): Record<string, unknown> {
50
- const result: Record<string, unknown> = {}
51
- for (const [k, v] of Object.entries(obj)) {
52
- if (v !== undefined && v !== null) result[k] = v
53
- }
54
- return result
55
- }
56
-
57
- // ---------------------------------------------------------------------------
58
- // Schema normalization: ensure all JSON schemas are valid tool input schemas.
59
- // Some MCP tools have empty inputSchema ({}) which produces schemas without
60
- // "type": "object", causing providers like DeepSeek/Qwen to reject them.
61
- // ---------------------------------------------------------------------------
62
- function normalizeToolSchema(schema: Record<string, unknown>): Record<string, unknown> {
63
- const normalized = { ...schema }
64
- if (!normalized.type) normalized.type = "object"
65
- if (normalized.type === "object" && !normalized.properties) normalized.properties = {}
66
- return normalized
67
- }
68
-
69
- // ---------------------------------------------------------------------------
70
- // Dynamic tool discovery from MCP server
71
- // ---------------------------------------------------------------------------
72
- const cache = new Map<string, Record<string, any>>()
73
-
74
- /**
75
- * Build AI SDK tools dynamically from the MCP server's listTools() response.
76
- * Results are cached after the first successful call.
77
- */
78
- export async function getChatTools(compat?: ModelCompatConfig | string): Promise<Record<string, any>> {
79
- const key = typeof compat === "string" ? compat : compat?.cacheKey || "default"
80
- const normalizeSchema = typeof compat === "string" ? true : (compat?.normalizeToolSchema ?? true)
81
- const cached = cache.get(key)
82
- if (cached) return cached
83
-
84
- const { tools: mcpTools } = await McpBridge.listTools()
85
- log.info("discovered MCP tools", { count: mcpTools.length, names: mcpTools.map((t) => t.name) })
86
-
87
- const tools: Record<string, any> = {}
88
-
89
- for (const t of mcpTools) {
90
- const name = t.name
91
- const rawSchema = (t.inputSchema ?? {}) as Record<string, unknown>
92
-
93
- tools[name] = tool({
94
- description: t.description || name,
95
- inputSchema: jsonSchema(normalizeSchema ? normalizeToolSchema(rawSchema) : rawSchema),
96
- execute: async (args: any) => {
97
- log.info("execute tool", { name, args })
98
- const result = await mcp(name, clean(args))
99
- const resultStr = typeof result === "string" ? result : JSON.stringify(result)
100
- log.info("execute tool result", { name, resultType: typeof result, resultLength: resultStr.length, resultPreview: resultStr.slice(0, 300) })
101
- // Truncate very large tool results to avoid overwhelming the LLM context
102
- if (resultStr.length > 8000) {
103
- log.info("truncating large tool result", { name, originalLength: resultStr.length })
104
- return resultStr.slice(0, 8000) + "\n...(truncated)"
105
- }
106
- return resultStr
107
- },
108
- })
109
- }
110
-
111
- cache.set(key, tools)
112
- return tools
113
- }
114
-
115
- /** Clear the cached tools (useful for testing or reconnection). */
116
- export function clearChatToolsCache(): void {
117
- cache.clear()
118
- }
package/src/ai/types.ts DELETED
@@ -1,105 +0,0 @@
1
- import type { Config } from "../config"
2
-
3
- export type ModelApi = "anthropic" | "openai" | "google" | "openai-compatible"
4
- export type CompatProfile = "anthropic" | "openai" | "openai-compatible" | "google"
5
-
6
- export interface ModelCompatConfig {
7
- providerID: string
8
- modelID: string
9
- api: ModelApi
10
- compatProfile: CompatProfile
11
- cacheKey: string
12
- stripParallelToolCalls: boolean
13
- normalizeToolSchema: boolean
14
- }
15
-
16
- export const COMPAT_PRESETS: Record<CompatProfile, Omit<ModelCompatConfig, "providerID" | "modelID" | "cacheKey">> = {
17
- anthropic: {
18
- api: "anthropic",
19
- compatProfile: "anthropic",
20
- stripParallelToolCalls: false,
21
- normalizeToolSchema: false,
22
- },
23
- openai: {
24
- api: "openai",
25
- compatProfile: "openai",
26
- stripParallelToolCalls: false,
27
- normalizeToolSchema: false,
28
- },
29
- google: {
30
- api: "google",
31
- compatProfile: "google",
32
- stripParallelToolCalls: false,
33
- normalizeToolSchema: false,
34
- },
35
- "openai-compatible": {
36
- api: "openai-compatible",
37
- compatProfile: "openai-compatible",
38
- stripParallelToolCalls: true,
39
- normalizeToolSchema: true,
40
- },
41
- }
42
-
43
- function isOfficialOpenAIBase(baseURL?: string): boolean {
44
- if (!baseURL) return false
45
- try {
46
- return new URL(baseURL).hostname === "api.openai.com"
47
- } catch {
48
- return false
49
- }
50
- }
51
-
52
- function resolveApiFromProvider(providerID: string, cfg?: Config.ProviderConfig): ModelApi {
53
- if (providerID === "openai" && cfg?.base_url && !isOfficialOpenAIBase(cfg.base_url)) {
54
- return "openai-compatible"
55
- }
56
- if (cfg?.api) return cfg.api
57
- if (providerID === "anthropic") return "anthropic"
58
- if (providerID === "openai") return "openai"
59
- if (providerID === "google") return "google"
60
- if (providerID === "openai-compatible") return "openai-compatible"
61
- return "openai-compatible"
62
- }
63
-
64
- function defaultCompatForApi(api: ModelApi): CompatProfile {
65
- if (api === "anthropic") return "anthropic"
66
- if (api === "openai") return "openai"
67
- if (api === "google") return "google"
68
- return "openai-compatible"
69
- }
70
-
71
- export function resolveCompat(args: {
72
- providerID: string
73
- modelID: string
74
- providerConfig?: Config.ProviderConfig
75
- }): ModelCompatConfig {
76
- const api = resolveApiFromProvider(args.providerID, args.providerConfig)
77
- const configured = args.providerConfig?.compat_profile
78
- const compatProfile = api === "openai-compatible" && configured === "openai"
79
- ? "openai-compatible"
80
- : configured || defaultCompatForApi(api)
81
- const preset = COMPAT_PRESETS[compatProfile]
82
- return {
83
- ...preset,
84
- providerID: args.providerID,
85
- modelID: args.modelID,
86
- cacheKey: `${api}:${compatProfile}`,
87
- }
88
- }
89
-
90
- export function patchRequestByCompat(compat: ModelCompatConfig, body: Record<string, any>): Record<string, any> {
91
- if (compat.stripParallelToolCalls) {
92
- delete body.parallel_tool_calls
93
- }
94
-
95
- if (compat.normalizeToolSchema && Array.isArray(body.tools)) {
96
- for (const t of body.tools) {
97
- const params = t?.function?.parameters
98
- if (!params || typeof params !== "object") continue
99
- if (!params.type) params.type = "object"
100
- if (params.type === "object" && !params.properties) params.properties = {}
101
- }
102
- }
103
-
104
- return body
105
- }
package/src/auth/index.ts DELETED
@@ -1,49 +0,0 @@
1
- import path from "path"
2
- import { chmod, writeFile } from "fs/promises"
3
- import { Global } from "../global"
4
- import z from "zod"
5
-
6
- export namespace Auth {
7
- export const Token = z
8
- .object({
9
- type: z.enum(["jwt", "apikey"]),
10
- value: z.string(),
11
- expires: z.number().optional(),
12
- username: z.string().optional(),
13
- })
14
- .meta({ ref: "AuthToken" })
15
- export type Token = z.infer<typeof Token>
16
-
17
- const filepath = path.join(Global.Path.data, "auth.json")
18
-
19
- export async function get(): Promise<Token | null> {
20
- const file = Bun.file(filepath)
21
- const data = await file.json().catch(() => null)
22
- if (!data) return null
23
- const parsed = Token.safeParse(data)
24
- if (!parsed.success) return null
25
- return parsed.data
26
- }
27
-
28
- export async function set(token: Token) {
29
- await writeFile(filepath, JSON.stringify(token, null, 2))
30
- await chmod(filepath, 0o600).catch(() => {})
31
- }
32
-
33
- export async function remove() {
34
- const fs = await import("fs/promises")
35
- await fs.unlink(filepath).catch(() => {})
36
- }
37
-
38
- export async function header(): Promise<Record<string, string>> {
39
- const token = await get()
40
- if (!token) return {}
41
- if (token.type === "apikey") return { Authorization: `Bearer ${token.value}` }
42
- return { Authorization: `Bearer ${token.value}` }
43
- }
44
-
45
- export async function authenticated(): Promise<boolean> {
46
- const token = await get()
47
- return token !== null
48
- }
49
- }