opencode-provider-litellm 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -14
- package/package.json +1 -1
- package/src/gcloud-token.test.ts +255 -0
- package/src/gcloud-token.ts +145 -0
- package/src/plugin.test.ts +63 -60
- package/src/plugin.ts +42 -51
- package/src/types.ts +0 -23
- package/src/utils.test.ts +51 -0
- package/src/utils.ts +10 -1
- package/src/skills.test.ts +0 -725
- package/src/skills.ts +0 -375
package/src/plugin.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { vi, describe, it, expect, beforeEach } from 'vitest'
|
|
1
|
+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
2
|
import type { PluginInput } from '@opencode-ai/plugin'
|
|
3
3
|
import type { OpencodeModelConfig } from './types.js'
|
|
4
4
|
|
|
@@ -20,17 +20,16 @@ vi.mock('./mcp-tools.js', () => ({
|
|
|
20
20
|
createMcpToolDefinitions: vi.fn(),
|
|
21
21
|
}))
|
|
22
22
|
|
|
23
|
-
// Mock the
|
|
24
|
-
vi.mock('./
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
// Mock the gcloud token module
|
|
24
|
+
vi.mock('./gcloud-token.js', () => ({
|
|
25
|
+
getGcloudToken: vi.fn(),
|
|
26
|
+
resetTokenCache: vi.fn(),
|
|
27
27
|
}))
|
|
28
28
|
|
|
29
29
|
import { LiteLLMPlugin } from './plugin.js'
|
|
30
30
|
import { discoverModels, injectModelsIntoConfig } from './discovery.js'
|
|
31
31
|
import { resolvePluginConfig } from './utils.js'
|
|
32
32
|
import { createMcpToolDefinitions } from './mcp-tools.js'
|
|
33
|
-
import { createSkillToolDefinitions, createSkillsInjector } from './skills.js'
|
|
34
33
|
|
|
35
34
|
function createMockInput(): PluginInput {
|
|
36
35
|
const logFn = vi.fn().mockResolvedValue(true)
|
|
@@ -79,14 +78,10 @@ describe('LiteLLMPlugin', () => {
|
|
|
79
78
|
vi.mocked(createMcpToolDefinitions).mockResolvedValue({
|
|
80
79
|
mcp_test_server_test_tool: 'mock-mcp-tool',
|
|
81
80
|
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
skill_delete: 'mock-skill-delete',
|
|
87
|
-
})
|
|
88
|
-
// Default Skills injector mock
|
|
89
|
-
vi.mocked(createSkillsInjector).mockReturnValue(vi.fn())
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
delete process.env.LITELLM_GCLOUD_TOKEN_AUTH
|
|
90
85
|
})
|
|
91
86
|
|
|
92
87
|
it('throws on missing config', async () => {
|
|
@@ -99,6 +94,28 @@ describe('LiteLLMPlugin', () => {
|
|
|
99
94
|
)
|
|
100
95
|
})
|
|
101
96
|
|
|
97
|
+
it('throws gcloud-specific error when LITELLM_GCLOUD_TOKEN_AUTH is set but config is missing', async () => {
|
|
98
|
+
vi.mocked(resolvePluginConfig).mockReturnValue(null)
|
|
99
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH = '1'
|
|
100
|
+
|
|
101
|
+
await expect(
|
|
102
|
+
LiteLLMPlugin(mockInput, {})
|
|
103
|
+
).rejects.toThrow(
|
|
104
|
+
'LITELLM_KEY is optional when LITELLM_GCLOUD_TOKEN_AUTH=1',
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('throws generic error when LITELLM_GCLOUD_TOKEN_AUTH is not set and config is missing', async () => {
|
|
109
|
+
vi.mocked(resolvePluginConfig).mockReturnValue(null)
|
|
110
|
+
delete process.env.LITELLM_GCLOUD_TOKEN_AUTH
|
|
111
|
+
|
|
112
|
+
await expect(
|
|
113
|
+
LiteLLMPlugin(mockInput, {})
|
|
114
|
+
).rejects.toThrow(
|
|
115
|
+
"Plugin config error: set 'url' and 'apiKey'",
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
|
|
102
119
|
it('config hook calls discoverModels and injects models into config', async () => {
|
|
103
120
|
const hooks = await LiteLLMPlugin(mockInput, {
|
|
104
121
|
url: 'https://litellm.example.com',
|
|
@@ -212,9 +229,6 @@ describe('LiteLLMPlugin', () => {
|
|
|
212
229
|
|
|
213
230
|
expect(hooks.tool).toBeDefined()
|
|
214
231
|
expect(hooks.tool).toHaveProperty('mcp_test_server_test_tool')
|
|
215
|
-
expect(hooks.tool).toHaveProperty('skill_list')
|
|
216
|
-
expect(hooks.tool).toHaveProperty('skill_create')
|
|
217
|
-
expect(hooks.tool).toHaveProperty('skill_delete')
|
|
218
232
|
})
|
|
219
233
|
|
|
220
234
|
it('tool hook passes correct config and apiKey to createMcpToolDefinitions', async () => {
|
|
@@ -229,41 +243,7 @@ describe('LiteLLMPlugin', () => {
|
|
|
229
243
|
)
|
|
230
244
|
})
|
|
231
245
|
|
|
232
|
-
it('
|
|
233
|
-
await LiteLLMPlugin(mockInput, {
|
|
234
|
-
url: 'https://litellm.example.com',
|
|
235
|
-
apiKey: 'test-api-key',
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
expect(createSkillToolDefinitions).toHaveBeenCalledWith(
|
|
239
|
-
{ url: 'https://litellm.example.com', apiKey: 'test-api-key' },
|
|
240
|
-
'test-api-key',
|
|
241
|
-
)
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
it('chat.message hook is defined', async () => {
|
|
245
|
-
const hooks = await LiteLLMPlugin(mockInput, {
|
|
246
|
-
url: 'https://litellm.example.com',
|
|
247
|
-
apiKey: 'test-api-key',
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
expect(hooks['chat.message']).toBeDefined()
|
|
251
|
-
expect(typeof hooks['chat.message']).toBe('function')
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('chat.message hook is created with correct config and apiKey', async () => {
|
|
255
|
-
await LiteLLMPlugin(mockInput, {
|
|
256
|
-
url: 'https://litellm.example.com',
|
|
257
|
-
apiKey: 'test-api-key',
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
expect(createSkillsInjector).toHaveBeenCalledWith(
|
|
261
|
-
{ url: 'https://litellm.example.com', apiKey: 'test-api-key' },
|
|
262
|
-
'test-api-key',
|
|
263
|
-
)
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
it('MCP discovery failure does not break the plugin — Skills tools still present', async () => {
|
|
246
|
+
it('MCP discovery failure does not break the plugin', async () => {
|
|
267
247
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
268
248
|
vi.mocked(createMcpToolDefinitions).mockRejectedValue(new Error('MCP server unavailable'))
|
|
269
249
|
|
|
@@ -278,10 +258,6 @@ describe('LiteLLMPlugin', () => {
|
|
|
278
258
|
|
|
279
259
|
// Skills tools should still be present
|
|
280
260
|
expect(hooks.tool).toBeDefined()
|
|
281
|
-
expect(hooks.tool).toHaveProperty('skill_list')
|
|
282
|
-
expect(hooks.tool).toHaveProperty('skill_create')
|
|
283
|
-
expect(hooks.tool).toHaveProperty('skill_delete')
|
|
284
|
-
|
|
285
261
|
// MCP tools should not be present (empty object merged)
|
|
286
262
|
expect(hooks.tool).not.toHaveProperty('mcp_test_server_test_tool')
|
|
287
263
|
|
|
@@ -300,10 +276,6 @@ describe('LiteLLMPlugin', () => {
|
|
|
300
276
|
const hooks = await LiteLLMPlugin(mockInput, undefined)
|
|
301
277
|
|
|
302
278
|
expect(hooks.tool).toBeDefined()
|
|
303
|
-
expect(hooks.tool).toHaveProperty('skill_list')
|
|
304
|
-
expect(hooks['chat.message']).toBeDefined()
|
|
305
|
-
expect(typeof hooks['chat.message']).toBe('function')
|
|
306
|
-
|
|
307
279
|
expect(createMcpToolDefinitions).toHaveBeenCalledWith(
|
|
308
280
|
{ url: 'https://env.litellm.example.com', apiKey: 'env-api-key' },
|
|
309
281
|
'env-api-key',
|
|
@@ -312,4 +284,35 @@ describe('LiteLLMPlugin', () => {
|
|
|
312
284
|
delete process.env.LITELLM_URL
|
|
313
285
|
delete process.env.LITELLM_KEY
|
|
314
286
|
})
|
|
287
|
+
|
|
288
|
+
it('registers chat.headers hook when LITELLM_GCLOUD_TOKEN_AUTH is set', async () => {
|
|
289
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH = '1'
|
|
290
|
+
|
|
291
|
+
const { getGcloudToken } = await import('./gcloud-token.js')
|
|
292
|
+
vi.mocked(getGcloudToken).mockResolvedValue('mock-gcloud-token')
|
|
293
|
+
|
|
294
|
+
const hooks = await LiteLLMPlugin(mockInput, {
|
|
295
|
+
url: 'https://litellm.example.com',
|
|
296
|
+
apiKey: 'test-api-key',
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
expect(hooks['chat.headers']).toBeDefined()
|
|
300
|
+
expect(typeof hooks['chat.headers']).toBe('function')
|
|
301
|
+
|
|
302
|
+
// Verify the hook injects the token
|
|
303
|
+
const output = { headers: {} as Record<string, string> }
|
|
304
|
+
await (hooks['chat.headers'] as Function)({}, output)
|
|
305
|
+
expect(output.headers['Authorization']).toBe('Bearer mock-gcloud-token')
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('does not register chat.headers hook when LITELLM_GCLOUD_TOKEN_AUTH is unset', async () => {
|
|
309
|
+
delete process.env.LITELLM_GCLOUD_TOKEN_AUTH
|
|
310
|
+
|
|
311
|
+
const hooks = await LiteLLMPlugin(mockInput, {
|
|
312
|
+
url: 'https://litellm.example.com',
|
|
313
|
+
apiKey: 'test-api-key',
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
expect(hooks['chat.headers']).toBeUndefined()
|
|
317
|
+
})
|
|
315
318
|
})
|
package/src/plugin.ts
CHANGED
|
@@ -2,32 +2,28 @@ import type { Plugin, PluginInput, PluginOptions } from '@opencode-ai/plugin'
|
|
|
2
2
|
import { resolvePluginConfig, getProviderId } from './utils.js'
|
|
3
3
|
import { discoverModels, injectModelsIntoConfig } from './discovery.js'
|
|
4
4
|
import { createMcpToolDefinitions } from './mcp-tools.js'
|
|
5
|
-
import {
|
|
5
|
+
import { getGcloudToken } from './gcloud-token.js'
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Main plugin entry point for the LiteLLM provider.
|
|
9
|
-
*
|
|
10
|
-
* Wires together config (model discovery), auth (/connect API key flow),
|
|
11
|
-
* and chat.headers (per-request Bearer token injection).
|
|
12
|
-
*
|
|
13
|
-
* Auth: LITELLM_URL / LITELLM_KEY env vars take precedence,
|
|
14
|
-
* with fallback to values in opencode.json plugin options.
|
|
15
|
-
*/
|
|
16
7
|
export const LiteLLMPlugin: Plugin = async (
|
|
17
8
|
input: PluginInput,
|
|
18
9
|
options?: PluginOptions,
|
|
19
10
|
) => {
|
|
20
11
|
const pluginConfig = resolvePluginConfig(options)
|
|
21
12
|
if (pluginConfig === null) {
|
|
13
|
+
const isGcloudAuth = process.env.LITELLM_GCLOUD_TOKEN_AUTH &&
|
|
14
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH !== '' &&
|
|
15
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH !== '0'
|
|
16
|
+
|
|
22
17
|
throw new Error(
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
isGcloudAuth
|
|
19
|
+
? "Plugin config error: set LITELLM_URL (LITELLM_KEY is optional when LITELLM_GCLOUD_TOKEN_AUTH=1)."
|
|
20
|
+
: "Plugin config error: set 'url' and 'apiKey' in plugin options, " +
|
|
21
|
+
"or set LITELLM_URL and LITELLM_KEY environment variables.",
|
|
25
22
|
)
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
const providerId = getProviderId()
|
|
29
26
|
|
|
30
|
-
// Discover MCP tools with graceful error handling
|
|
31
27
|
let mcpTools: Record<string, any> = {}
|
|
32
28
|
try {
|
|
33
29
|
mcpTools = await createMcpToolDefinitions(pluginConfig, pluginConfig.apiKey)
|
|
@@ -35,12 +31,8 @@ export const LiteLLMPlugin: Plugin = async (
|
|
|
35
31
|
console.warn(`[opencode-provider-litellm] MCP tool discovery failed: ${e}`)
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* Config hook — discovers models from the LiteLLM proxy and injects
|
|
41
|
-
* them into the OpenCode config under the provider.
|
|
42
|
-
*/
|
|
43
|
-
config: async (config) => {
|
|
34
|
+
const result: Record<string, unknown> = {
|
|
35
|
+
config: async (config: Record<string, any>) => {
|
|
44
36
|
try {
|
|
45
37
|
const models = await discoverModels(
|
|
46
38
|
pluginConfig,
|
|
@@ -55,23 +47,22 @@ export const LiteLLMPlugin: Plugin = async (
|
|
|
55
47
|
message: 'No models discovered',
|
|
56
48
|
},
|
|
57
49
|
})
|
|
58
|
-
|
|
50
|
+
} else {
|
|
51
|
+
injectModelsIntoConfig(
|
|
52
|
+
config as Parameters<typeof injectModelsIntoConfig>[0],
|
|
53
|
+
providerId,
|
|
54
|
+
pluginConfig.url,
|
|
55
|
+
pluginConfig.apiKey,
|
|
56
|
+
models,
|
|
57
|
+
)
|
|
58
|
+
await input.client.app.log({
|
|
59
|
+
body: {
|
|
60
|
+
service: providerId,
|
|
61
|
+
level: 'info',
|
|
62
|
+
message: `Discovered ${Object.keys(models).length} models`,
|
|
63
|
+
},
|
|
64
|
+
})
|
|
59
65
|
}
|
|
60
|
-
|
|
61
|
-
injectModelsIntoConfig(
|
|
62
|
-
config as Parameters<typeof injectModelsIntoConfig>[0],
|
|
63
|
-
providerId,
|
|
64
|
-
pluginConfig.url,
|
|
65
|
-
pluginConfig.apiKey,
|
|
66
|
-
models,
|
|
67
|
-
)
|
|
68
|
-
await input.client.app.log({
|
|
69
|
-
body: {
|
|
70
|
-
service: providerId,
|
|
71
|
-
level: 'info',
|
|
72
|
-
message: `Discovered ${Object.keys(models).length} models`,
|
|
73
|
-
},
|
|
74
|
-
})
|
|
75
66
|
} catch (error) {
|
|
76
67
|
await input.client.app.log({
|
|
77
68
|
body: {
|
|
@@ -83,10 +74,6 @@ export const LiteLLMPlugin: Plugin = async (
|
|
|
83
74
|
}
|
|
84
75
|
},
|
|
85
76
|
|
|
86
|
-
/**
|
|
87
|
-
* Auth hook — lets the user paste an API key via the /connect flow.
|
|
88
|
-
* The key is stored in OpenCode's auth store and used as the Bearer token.
|
|
89
|
-
*/
|
|
90
77
|
auth: {
|
|
91
78
|
provider: providerId,
|
|
92
79
|
methods: [
|
|
@@ -101,28 +88,32 @@ export const LiteLLMPlugin: Plugin = async (
|
|
|
101
88
|
placeholder: 'sk-...',
|
|
102
89
|
},
|
|
103
90
|
],
|
|
104
|
-
async authorize(inputs) {
|
|
105
|
-
|
|
91
|
+
async authorize(inputs: Record<string, unknown> | undefined) {
|
|
92
|
+
const apiKey = inputs?.apiKey
|
|
93
|
+
if (!apiKey || typeof apiKey !== 'string' || apiKey.length === 0) {
|
|
106
94
|
return { type: 'failed' as const }
|
|
107
95
|
}
|
|
108
|
-
return { type: 'success' as const, key:
|
|
96
|
+
return { type: 'success' as const, key: apiKey }
|
|
109
97
|
},
|
|
110
98
|
},
|
|
111
99
|
],
|
|
112
100
|
},
|
|
113
101
|
|
|
114
|
-
/**
|
|
115
|
-
* Tool hook — merges dynamically-discovered MCP tools with static
|
|
116
|
-
* Skills CRUD tools.
|
|
117
|
-
*/
|
|
118
102
|
tool: {
|
|
119
103
|
...mcpTools,
|
|
120
|
-
...createSkillToolDefinitions(pluginConfig, pluginConfig.apiKey),
|
|
121
104
|
},
|
|
105
|
+
}
|
|
122
106
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
107
|
+
if (process.env.LITELLM_GCLOUD_TOKEN_AUTH &&
|
|
108
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH !== '' &&
|
|
109
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH !== '0') {
|
|
110
|
+
result['chat.headers'] = async (_input: Record<string, unknown>, output: { headers: Record<string, string> }) => {
|
|
111
|
+
const token = await getGcloudToken()
|
|
112
|
+
if (token) {
|
|
113
|
+
output.headers['Authorization'] = `Bearer ${token}`
|
|
114
|
+
}
|
|
115
|
+
}
|
|
127
116
|
}
|
|
117
|
+
|
|
118
|
+
return result
|
|
128
119
|
}
|
package/src/types.ts
CHANGED
|
@@ -15,29 +15,6 @@ export interface McpTool {
|
|
|
15
15
|
input_schema: Record<string, unknown>
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export interface SkillSource {
|
|
19
|
-
source: string
|
|
20
|
-
url: string
|
|
21
|
-
path?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface Skill {
|
|
25
|
-
id: string
|
|
26
|
-
name: string
|
|
27
|
-
version: string
|
|
28
|
-
description: string | null
|
|
29
|
-
source: SkillSource
|
|
30
|
-
author: string | null
|
|
31
|
-
homepage: string | null
|
|
32
|
-
keywords: string | null
|
|
33
|
-
category: string | null
|
|
34
|
-
domain: string | null
|
|
35
|
-
namespace: string | null
|
|
36
|
-
enabled: boolean
|
|
37
|
-
created_at: string
|
|
38
|
-
updated_at: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
18
|
export interface OpencodeModelConfig {
|
|
42
19
|
name: string
|
|
43
20
|
tool_call?: boolean
|
package/src/utils.test.ts
CHANGED
|
@@ -137,4 +137,55 @@ describe('resolvePluginConfig', () => {
|
|
|
137
137
|
expect(resolvePluginConfig({})).toBeNull()
|
|
138
138
|
})
|
|
139
139
|
})
|
|
140
|
+
|
|
141
|
+
describe('gcloud token auth', () => {
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
delete process.env.LITELLM_GCLOUD_TOKEN_AUTH
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('allows missing LITELLM_KEY when LITELLM_GCLOUD_TOKEN_AUTH is set', () => {
|
|
147
|
+
process.env.LITELLM_URL = 'https://gcloud.example.com'
|
|
148
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH = '1'
|
|
149
|
+
delete process.env.LITELLM_KEY
|
|
150
|
+
|
|
151
|
+
const config = resolvePluginConfig({})
|
|
152
|
+
expect(config).toEqual({ url: 'https://gcloud.example.com', apiKey: '' })
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('does not allow missing key when gcloud auth is disabled', () => {
|
|
156
|
+
process.env.LITELLM_URL = 'https://gcloud.example.com'
|
|
157
|
+
delete process.env.LITELLM_KEY
|
|
158
|
+
delete process.env.LITELLM_GCLOUD_TOKEN_AUTH
|
|
159
|
+
|
|
160
|
+
const config = resolvePluginConfig({})
|
|
161
|
+
expect(config).toBeNull()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('does not allow missing key when gcloud auth is set to 0', () => {
|
|
165
|
+
process.env.LITELLM_URL = 'https://gcloud.example.com'
|
|
166
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH = '0'
|
|
167
|
+
delete process.env.LITELLM_KEY
|
|
168
|
+
|
|
169
|
+
const config = resolvePluginConfig({})
|
|
170
|
+
expect(config).toBeNull()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('does not allow missing key when gcloud auth is empty string', () => {
|
|
174
|
+
process.env.LITELLM_URL = 'https://gcloud.example.com'
|
|
175
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH = ''
|
|
176
|
+
delete process.env.LITELLM_KEY
|
|
177
|
+
|
|
178
|
+
const config = resolvePluginConfig({})
|
|
179
|
+
expect(config).toBeNull()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('prefers full env vars over gcloud fallback', () => {
|
|
183
|
+
process.env.LITELLM_URL = 'https://gcloud.example.com'
|
|
184
|
+
process.env.LITELLM_KEY = 'normal-key'
|
|
185
|
+
process.env.LITELLM_GCLOUD_TOKEN_AUTH = '1'
|
|
186
|
+
|
|
187
|
+
const config = resolvePluginConfig({})
|
|
188
|
+
expect(config).toEqual({ url: 'https://gcloud.example.com', apiKey: 'normal-key' })
|
|
189
|
+
})
|
|
190
|
+
})
|
|
140
191
|
})
|
package/src/utils.ts
CHANGED
|
@@ -34,14 +34,23 @@ export function mapLiteLLMModel(model: LiteLLMModel): OpencodeModelConfig {
|
|
|
34
34
|
export function resolvePluginConfig(rawConfig: unknown): PluginConfig | null {
|
|
35
35
|
const envUrl = typeof process !== 'undefined' ? process.env.LITELLM_URL : undefined
|
|
36
36
|
const envKey = typeof process !== 'undefined' ? process.env.LITELLM_KEY : undefined
|
|
37
|
+
const envGcloudAuth = typeof process !== 'undefined'
|
|
38
|
+
? process.env.LITELLM_GCLOUD_TOKEN_AUTH
|
|
39
|
+
: undefined
|
|
37
40
|
|
|
38
41
|
const hasEnvVars = envUrl !== undefined && envUrl.length > 0 &&
|
|
39
|
-
|
|
42
|
+
envKey !== undefined && envKey.length > 0
|
|
40
43
|
|
|
41
44
|
if (hasEnvVars) {
|
|
42
45
|
return { url: envUrl, apiKey: envKey }
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
// Allow missing LITELLM_KEY when gcloud token auth is enabled
|
|
49
|
+
if (envUrl !== undefined && envUrl.length > 0 &&
|
|
50
|
+
envGcloudAuth !== undefined && envGcloudAuth !== '' && envGcloudAuth !== '0') {
|
|
51
|
+
return { url: envUrl, apiKey: envKey || '' }
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
// Fall back to config options from opencode.json
|
|
46
55
|
if (rawConfig && typeof rawConfig === 'object' && !Array.isArray(rawConfig)) {
|
|
47
56
|
const obj = rawConfig as Record<string, unknown>
|