@ulam/halohalo 0.3.2 → 0.3.3
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 +212 -212
- package/agenticAiService.js +76 -76
- package/aiService.js +31 -31
- package/angular.js +102 -102
- package/connectivity.js +17 -17
- package/createCompletion.js +113 -113
- package/createProviderConfig.js +85 -85
- package/fetch.js +134 -134
- package/index.js +20 -20
- package/init.js +33 -33
- package/models.js +40 -48
- package/package.json +1 -1
- package/react.js +2 -2
- package/useCompletion.js +17 -17
- package/useProviderConfig.js +27 -27
- package/vue.js +73 -73
package/fetch.js
CHANGED
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
import { AiApiError, httpStatusToErrorType, PROVIDER_CONFIGS } from './providers.js'
|
|
2
|
-
|
|
3
|
-
// ─── Provider URL Whitelist ───────────────────────────────────────────────────
|
|
4
|
-
// Prevents SSRF attacks from user-configured provider URLs.
|
|
5
|
-
const ALLOWED_PROVIDER_HOSTS = new Set([
|
|
6
|
-
'api.anthropic.com',
|
|
7
|
-
'api.openai.com',
|
|
8
|
-
'generativelanguage.googleapis.com',
|
|
9
|
-
])
|
|
10
|
-
|
|
11
|
-
function validateProviderUrl(url) {
|
|
12
|
-
try {
|
|
13
|
-
const hostname = new URL(url).hostname
|
|
14
|
-
return ALLOWED_PROVIDER_HOSTS.has(hostname)
|
|
15
|
-
} catch {
|
|
16
|
-
return false
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// ─── callProvider ─────────────────────────────────────────────────────────────
|
|
21
|
-
// Single-turn completion against any configured provider.
|
|
22
|
-
// Returns the response text string.
|
|
23
|
-
|
|
24
|
-
export async function callProvider({ provider, model, key, prompt, maxTokens = 1024 }) {
|
|
25
|
-
const config = PROVIDER_CONFIGS[provider]
|
|
26
|
-
if (!config) throw new AiApiError('api_error', { provider })
|
|
27
|
-
|
|
28
|
-
let url
|
|
29
|
-
if (config.buildUrl) {
|
|
30
|
-
url = config.buildUrl(key, model)
|
|
31
|
-
if (!url) throw new AiApiError('api_error', { provider })
|
|
32
|
-
} else {
|
|
33
|
-
url = config.url.replace('{model}', model)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!validateProviderUrl(url)) {
|
|
37
|
-
throw new AiApiError('api_error', { provider })
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let res
|
|
41
|
-
try {
|
|
42
|
-
res = await fetch(url, {
|
|
43
|
-
method: 'POST',
|
|
44
|
-
headers: config.buildHeaders(key),
|
|
45
|
-
body: config.buildBody(prompt, model, maxTokens),
|
|
46
|
-
})
|
|
47
|
-
} catch {
|
|
48
|
-
throw new AiApiError('network_error', { provider })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!res.ok) {
|
|
52
|
-
throw new AiApiError(httpStatusToErrorType(res.status), { status: res.status, provider })
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return config.parseResponse(res)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ─── callAnthropicWithTools ───────────────────────────────────────────────────
|
|
59
|
-
// Agentic tool-use loop for Anthropic. Calls the messages API repeatedly until
|
|
60
|
-
// stop_reason is 'end_turn' or maxTurns is exhausted.
|
|
61
|
-
//
|
|
62
|
-
// onToolCall(toolName, toolInput) => toolResultContent (string or object)
|
|
63
|
-
// Returns the final text block content.
|
|
64
|
-
|
|
65
|
-
export async function callAnthropicWithTools({
|
|
66
|
-
key,
|
|
67
|
-
model,
|
|
68
|
-
system,
|
|
69
|
-
tools,
|
|
70
|
-
messages,
|
|
71
|
-
maxTokens = 2048,
|
|
72
|
-
maxTurns = 5,
|
|
73
|
-
onToolCall,
|
|
74
|
-
}) {
|
|
75
|
-
const config = PROVIDER_CONFIGS.anthropic
|
|
76
|
-
let turns = 0
|
|
77
|
-
|
|
78
|
-
while (turns <= maxTurns) {
|
|
79
|
-
let res
|
|
80
|
-
try {
|
|
81
|
-
res = await fetch(config.url, {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
headers: config.buildHeaders(key),
|
|
84
|
-
body: JSON.stringify({ model, max_tokens: maxTokens, system, tools, messages }),
|
|
85
|
-
})
|
|
86
|
-
} catch {
|
|
87
|
-
throw new AiApiError('network_error', { provider: 'anthropic' })
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!res.ok) {
|
|
91
|
-
throw new AiApiError(httpStatusToErrorType(res.status), { status: res.status, provider: 'anthropic' })
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const data = await res.json()
|
|
95
|
-
|
|
96
|
-
if (import.meta.env.DEV) console.log(`[halohalo] turn ${turns + 1}, stop_reason: ${data.stop_reason}`)
|
|
97
|
-
|
|
98
|
-
messages.push({ role: 'assistant', content: data.content })
|
|
99
|
-
|
|
100
|
-
if (data.stop_reason === 'end_turn') {
|
|
101
|
-
return data.content.find(b => b.type === 'text')?.text || ''
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (data.stop_reason === 'tool_use') {
|
|
105
|
-
if (turns >= maxTurns) {
|
|
106
|
-
if (import.meta.env.DEV) console.warn('[halohalo] maxTurns reached')
|
|
107
|
-
throw new AiApiError('api_error', { provider: 'anthropic' })
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const toolBlocks = data.content.filter(b => b.type === 'tool_use')
|
|
111
|
-
const toolResults = await Promise.all(
|
|
112
|
-
toolBlocks.map(async (block) => {
|
|
113
|
-
const result = await onToolCall?.(block.name, block.input) ?? ''
|
|
114
|
-
if (import.meta.env.DEV) {
|
|
115
|
-
console.log(`[halohalo] tool_use: ${block.name}`, block.input, '→', result)
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
type: 'tool_result',
|
|
119
|
-
tool_use_id: block.id,
|
|
120
|
-
content: typeof result === 'string' ? result : JSON.stringify(result),
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
messages.push({ role: 'user', content: toolResults })
|
|
126
|
-
turns++
|
|
127
|
-
continue
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
break
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
throw new AiApiError('api_error', { provider: 'anthropic' })
|
|
134
|
-
}
|
|
1
|
+
import { AiApiError, httpStatusToErrorType, PROVIDER_CONFIGS } from './providers.js'
|
|
2
|
+
|
|
3
|
+
// ─── Provider URL Whitelist ───────────────────────────────────────────────────
|
|
4
|
+
// Prevents SSRF attacks from user-configured provider URLs.
|
|
5
|
+
const ALLOWED_PROVIDER_HOSTS = new Set([
|
|
6
|
+
'api.anthropic.com',
|
|
7
|
+
'api.openai.com',
|
|
8
|
+
'generativelanguage.googleapis.com',
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
function validateProviderUrl(url) {
|
|
12
|
+
try {
|
|
13
|
+
const hostname = new URL(url).hostname
|
|
14
|
+
return ALLOWED_PROVIDER_HOSTS.has(hostname)
|
|
15
|
+
} catch {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── callProvider ─────────────────────────────────────────────────────────────
|
|
21
|
+
// Single-turn completion against any configured provider.
|
|
22
|
+
// Returns the response text string.
|
|
23
|
+
|
|
24
|
+
export async function callProvider({ provider, model, key, prompt, maxTokens = 1024 }) {
|
|
25
|
+
const config = PROVIDER_CONFIGS[provider]
|
|
26
|
+
if (!config) throw new AiApiError('api_error', { provider })
|
|
27
|
+
|
|
28
|
+
let url
|
|
29
|
+
if (config.buildUrl) {
|
|
30
|
+
url = config.buildUrl(key, model)
|
|
31
|
+
if (!url) throw new AiApiError('api_error', { provider })
|
|
32
|
+
} else {
|
|
33
|
+
url = config.url.replace('{model}', model)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!validateProviderUrl(url)) {
|
|
37
|
+
throw new AiApiError('api_error', { provider })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let res
|
|
41
|
+
try {
|
|
42
|
+
res = await fetch(url, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: config.buildHeaders(key),
|
|
45
|
+
body: config.buildBody(prompt, model, maxTokens),
|
|
46
|
+
})
|
|
47
|
+
} catch {
|
|
48
|
+
throw new AiApiError('network_error', { provider })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
throw new AiApiError(httpStatusToErrorType(res.status), { status: res.status, provider })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return config.parseResponse(res)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── callAnthropicWithTools ───────────────────────────────────────────────────
|
|
59
|
+
// Agentic tool-use loop for Anthropic. Calls the messages API repeatedly until
|
|
60
|
+
// stop_reason is 'end_turn' or maxTurns is exhausted.
|
|
61
|
+
//
|
|
62
|
+
// onToolCall(toolName, toolInput) => toolResultContent (string or object)
|
|
63
|
+
// Returns the final text block content.
|
|
64
|
+
|
|
65
|
+
export async function callAnthropicWithTools({
|
|
66
|
+
key,
|
|
67
|
+
model,
|
|
68
|
+
system,
|
|
69
|
+
tools,
|
|
70
|
+
messages,
|
|
71
|
+
maxTokens = 2048,
|
|
72
|
+
maxTurns = 5,
|
|
73
|
+
onToolCall,
|
|
74
|
+
}) {
|
|
75
|
+
const config = PROVIDER_CONFIGS.anthropic
|
|
76
|
+
let turns = 0
|
|
77
|
+
|
|
78
|
+
while (turns <= maxTurns) {
|
|
79
|
+
let res
|
|
80
|
+
try {
|
|
81
|
+
res = await fetch(config.url, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: config.buildHeaders(key),
|
|
84
|
+
body: JSON.stringify({ model, max_tokens: maxTokens, system, tools, messages }),
|
|
85
|
+
})
|
|
86
|
+
} catch {
|
|
87
|
+
throw new AiApiError('network_error', { provider: 'anthropic' })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
throw new AiApiError(httpStatusToErrorType(res.status), { status: res.status, provider: 'anthropic' })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const data = await res.json()
|
|
95
|
+
|
|
96
|
+
if (import.meta.env.DEV) console.log(`[halohalo] turn ${turns + 1}, stop_reason: ${data.stop_reason}`)
|
|
97
|
+
|
|
98
|
+
messages.push({ role: 'assistant', content: data.content })
|
|
99
|
+
|
|
100
|
+
if (data.stop_reason === 'end_turn') {
|
|
101
|
+
return data.content.find(b => b.type === 'text')?.text || ''
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (data.stop_reason === 'tool_use') {
|
|
105
|
+
if (turns >= maxTurns) {
|
|
106
|
+
if (import.meta.env.DEV) console.warn('[halohalo] maxTurns reached')
|
|
107
|
+
throw new AiApiError('api_error', { provider: 'anthropic' })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const toolBlocks = data.content.filter(b => b.type === 'tool_use')
|
|
111
|
+
const toolResults = await Promise.all(
|
|
112
|
+
toolBlocks.map(async (block) => {
|
|
113
|
+
const result = await onToolCall?.(block.name, block.input) ?? ''
|
|
114
|
+
if (import.meta.env.DEV) {
|
|
115
|
+
console.log(`[halohalo] tool_use: ${block.name}`, block.input, '→', result)
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
type: 'tool_result',
|
|
119
|
+
tool_use_id: block.id,
|
|
120
|
+
content: typeof result === 'string' ? result : JSON.stringify(result),
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
messages.push({ role: 'user', content: toolResults })
|
|
126
|
+
turns++
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
throw new AiApiError('api_error', { provider: 'anthropic' })
|
|
134
|
+
}
|
package/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
// vanilla core
|
|
2
|
-
export { initHalohalo } from './init.js'
|
|
3
|
-
export { AiApiError, httpStatusToErrorType, PROVIDER_CONFIGS } from './providers.js'
|
|
4
|
-
export { callProvider, callAnthropicWithTools } from './fetch.js'
|
|
5
|
-
export { searchItems, makeSearchTool } from './search.js'
|
|
6
|
-
export { createProviderConfig } from './createProviderConfig.js'
|
|
7
|
-
export { createCompletion } from './createCompletion.js'
|
|
8
|
-
export { getAiRefinement, parseAiResponse } from './aiService.js'
|
|
9
|
-
export { getAgenticRefinement } from './agenticAiService.js'
|
|
10
|
-
export { PROVIDERS, PROVIDER_MODELS, initModels, initApiKeys } from './models.js'
|
|
11
|
-
export { getAiProvider, isAgenticModeEnabled, getAiModel, getProviderLabel } from './prefs.js'
|
|
12
|
-
export { checkConnectivity } from './connectivity.js'
|
|
13
|
-
export {
|
|
14
|
-
LS_AI_PROVIDER, LS_AGENTIC_MODE, LS_APIKEY_PREFIX, LS_AI_MODEL_PREFIX,
|
|
15
|
-
AI_MAX_TOKENS, AI_AGENTIC_MAX_TOKENS, AGENTIC_MAX_TOOL_TURNS,
|
|
16
|
-
AI_DESC_REGEX, AI_FIX_REGEX,
|
|
17
|
-
DEBUG_AI_DELAY_MS, DEFAULT_AI_MODELS, PROVIDER_LABELS,
|
|
18
|
-
DEBUG_COMMANDS, DEBUG_COMMAND_VALUES,
|
|
19
|
-
ANTHROPIC_API_VERSION, ANTHROPIC_API_URL,
|
|
20
|
-
} from './constants.js'
|
|
1
|
+
// vanilla core
|
|
2
|
+
export { initHalohalo } from './init.js'
|
|
3
|
+
export { AiApiError, httpStatusToErrorType, PROVIDER_CONFIGS } from './providers.js'
|
|
4
|
+
export { callProvider, callAnthropicWithTools } from './fetch.js'
|
|
5
|
+
export { searchItems, makeSearchTool } from './search.js'
|
|
6
|
+
export { createProviderConfig } from './createProviderConfig.js'
|
|
7
|
+
export { createCompletion } from './createCompletion.js'
|
|
8
|
+
export { getAiRefinement, parseAiResponse } from './aiService.js'
|
|
9
|
+
export { getAgenticRefinement } from './agenticAiService.js'
|
|
10
|
+
export { PROVIDERS, PROVIDER_MODELS, initModels, initApiKeys } from './models.js'
|
|
11
|
+
export { getAiProvider, isAgenticModeEnabled, getAiModel, getProviderLabel } from './prefs.js'
|
|
12
|
+
export { checkConnectivity } from './connectivity.js'
|
|
13
|
+
export {
|
|
14
|
+
LS_AI_PROVIDER, LS_AGENTIC_MODE, LS_APIKEY_PREFIX, LS_AI_MODEL_PREFIX,
|
|
15
|
+
AI_MAX_TOKENS, AI_AGENTIC_MAX_TOKENS, AGENTIC_MAX_TOOL_TURNS,
|
|
16
|
+
AI_DESC_REGEX, AI_FIX_REGEX,
|
|
17
|
+
DEBUG_AI_DELAY_MS, DEFAULT_AI_MODELS, PROVIDER_LABELS,
|
|
18
|
+
DEBUG_COMMANDS, DEBUG_COMMAND_VALUES,
|
|
19
|
+
ANTHROPIC_API_VERSION, ANTHROPIC_API_URL,
|
|
20
|
+
} from './constants.js'
|
package/init.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
// ─── halohalo module singleton ────────────────────────────────────────────────
|
|
2
|
-
// Holds app-injected config. Call initHalohalo() before using aiService or
|
|
3
|
-
// agenticAiService.
|
|
4
|
-
|
|
5
|
-
let _buildPrompt = null
|
|
6
|
-
let _systemPrompt = null
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Register app-specific AI config. Call once at app init.
|
|
10
|
-
*
|
|
11
|
-
* @param {{
|
|
12
|
-
* buildPrompt: (params: object) => string,
|
|
13
|
-
* systemPrompt?: string,
|
|
14
|
-
* }} config
|
|
15
|
-
*
|
|
16
|
-
* buildPrompt receives { finding, descText, fixText, note } and must return
|
|
17
|
-
* the user-turn prompt string for single-turn refinement.
|
|
18
|
-
*
|
|
19
|
-
* systemPrompt is used for agentic (tool-use) mode. If omitted, agentic mode
|
|
20
|
-
* falls back to a generic instruction.
|
|
21
|
-
*/
|
|
22
|
-
export function initHalohalo({ buildPrompt, systemPrompt = null }) {
|
|
23
|
-
_buildPrompt = buildPrompt
|
|
24
|
-
_systemPrompt = systemPrompt
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function getBuildPrompt() {
|
|
28
|
-
return _buildPrompt
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function getSystemPrompt() {
|
|
32
|
-
return _systemPrompt
|
|
33
|
-
}
|
|
1
|
+
// ─── halohalo module singleton ────────────────────────────────────────────────
|
|
2
|
+
// Holds app-injected config. Call initHalohalo() before using aiService or
|
|
3
|
+
// agenticAiService.
|
|
4
|
+
|
|
5
|
+
let _buildPrompt = null
|
|
6
|
+
let _systemPrompt = null
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register app-specific AI config. Call once at app init.
|
|
10
|
+
*
|
|
11
|
+
* @param {{
|
|
12
|
+
* buildPrompt: (params: object) => string,
|
|
13
|
+
* systemPrompt?: string,
|
|
14
|
+
* }} config
|
|
15
|
+
*
|
|
16
|
+
* buildPrompt receives { finding, descText, fixText, note } and must return
|
|
17
|
+
* the user-turn prompt string for single-turn refinement.
|
|
18
|
+
*
|
|
19
|
+
* systemPrompt is used for agentic (tool-use) mode. If omitted, agentic mode
|
|
20
|
+
* falls back to a generic instruction.
|
|
21
|
+
*/
|
|
22
|
+
export function initHalohalo({ buildPrompt, systemPrompt = null }) {
|
|
23
|
+
_buildPrompt = buildPrompt
|
|
24
|
+
_systemPrompt = systemPrompt
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getBuildPrompt() {
|
|
28
|
+
return _buildPrompt
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getSystemPrompt() {
|
|
32
|
+
return _systemPrompt
|
|
33
|
+
}
|
package/models.js
CHANGED
|
@@ -1,48 +1,40 @@
|
|
|
1
|
-
import { LS_AI_MODEL_PREFIX, LS_APIKEY_PREFIX, DEFAULT_AI_MODELS } from './constants.js'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
{ id: '
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function initApiKeys() {
|
|
44
|
-
const adapter = getAdapterInstance()
|
|
45
|
-
return Object.fromEntries(
|
|
46
|
-
PROVIDERS.map(p => [p.id, window.electronAPI ? '' : adapter.readPref(`${LS_APIKEY_PREFIX}${p.id}`) || ''])
|
|
47
|
-
)
|
|
48
|
-
}
|
|
1
|
+
import { LS_AI_MODEL_PREFIX, LS_APIKEY_PREFIX, DEFAULT_AI_MODELS } from './constants.js'
|
|
2
|
+
import { getAdapter } from '@ulam/sawsawan'
|
|
3
|
+
|
|
4
|
+
const _adapter = getAdapter()
|
|
5
|
+
|
|
6
|
+
export const PROVIDERS = [
|
|
7
|
+
{ id: 'anthropic', label: 'Anthropic (Claude)', placeholderKey: 'settings.api_placeholder_anthropic' },
|
|
8
|
+
{ id: 'openai', label: 'OpenAI (GPT)', placeholderKey: 'settings.api_placeholder_openai' },
|
|
9
|
+
{ id: 'google', label: 'Google (Gemini)', placeholderKey: 'settings.api_placeholder_google' },
|
|
10
|
+
{ id: 'microsoft', label: 'Microsoft (Copilot)', placeholderKey: 'settings.api_placeholder_default' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export const PROVIDER_MODELS = {
|
|
14
|
+
anthropic: [
|
|
15
|
+
{ id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5, fast, low cost' },
|
|
16
|
+
{ id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6, balanced (default)' },
|
|
17
|
+
{ id: 'claude-opus-4-7', label: 'Claude Opus 4.7, most capable' },
|
|
18
|
+
],
|
|
19
|
+
openai: [
|
|
20
|
+
{ id: 'gpt-4o-mini', label: 'GPT-4o Mini, fast, low cost' },
|
|
21
|
+
{ id: 'gpt-4o', label: 'GPT-4o, balanced (default)' },
|
|
22
|
+
],
|
|
23
|
+
google: [
|
|
24
|
+
{ id: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash, fast (default)' },
|
|
25
|
+
{ id: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro, most capable' },
|
|
26
|
+
],
|
|
27
|
+
microsoft: [],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function initModels() {
|
|
31
|
+
return Object.fromEntries(
|
|
32
|
+
PROVIDERS.map(p => [p.id, _adapter.readPref(`${LS_AI_MODEL_PREFIX}${p.id}`) || DEFAULT_AI_MODELS[p.id] || ''])
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function initApiKeys() {
|
|
37
|
+
return Object.fromEntries(
|
|
38
|
+
PROVIDERS.map(p => [p.id, window.electronAPI ? '' : _adapter.readPref(`${LS_APIKEY_PREFIX}${p.id}`) || ''])
|
|
39
|
+
)
|
|
40
|
+
}
|
package/package.json
CHANGED
package/react.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { useProviderConfig } from './useProviderConfig.js'
|
|
2
|
-
export { useCompletion } from './useCompletion.js'
|
|
1
|
+
export { useProviderConfig } from './useProviderConfig.js'
|
|
2
|
+
export { useCompletion } from './useCompletion.js'
|
package/useCompletion.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
|
-
import { createCompletion } from './createCompletion.js'
|
|
3
|
-
|
|
4
|
-
export function useCompletion(options = {}) {
|
|
5
|
-
const [instance] = useState(createCompletion)
|
|
6
|
-
const [state, setState] = useState({ loading: false, animating: false })
|
|
7
|
-
|
|
8
|
-
useEffect(() => instance.subscribe(setState), [instance])
|
|
9
|
-
useEffect(() => () => instance.cancel(), [instance])
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
loading: state.loading,
|
|
13
|
-
animating: state.animating,
|
|
14
|
-
complete: (callOptions) => instance.complete({ ...options, ...callOptions }),
|
|
15
|
-
cancel: () => instance.cancel(),
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import { createCompletion } from './createCompletion.js'
|
|
3
|
+
|
|
4
|
+
export function useCompletion(options = {}) {
|
|
5
|
+
const [instance] = useState(createCompletion)
|
|
6
|
+
const [state, setState] = useState({ loading: false, animating: false })
|
|
7
|
+
|
|
8
|
+
useEffect(() => instance.subscribe(setState), [instance])
|
|
9
|
+
useEffect(() => () => instance.cancel(), [instance])
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
loading: state.loading,
|
|
13
|
+
animating: state.animating,
|
|
14
|
+
complete: (callOptions) => instance.complete({ ...options, ...callOptions }),
|
|
15
|
+
cancel: () => instance.cancel(),
|
|
16
|
+
}
|
|
17
|
+
}
|
package/useProviderConfig.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { useState, useCallback, useSyncExternalStore } from 'react'
|
|
2
|
-
import { createProviderConfig } from './createProviderConfig.js'
|
|
3
|
-
|
|
4
|
-
export function useProviderConfig(storageKeys, providers) {
|
|
5
|
-
const [config] = useState(() => createProviderConfig(storageKeys, providers))
|
|
6
|
-
|
|
7
|
-
const snapshot = useSyncExternalStore(
|
|
8
|
-
(listen) => config.subscribe(listen),
|
|
9
|
-
() => ({
|
|
10
|
-
provider: config.provider,
|
|
11
|
-
models: config.models,
|
|
12
|
-
mode: config.mode,
|
|
13
|
-
providers: config.providers,
|
|
14
|
-
})
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
...snapshot,
|
|
19
|
-
setProvider: useCallback((id) => config.setProvider(id), [config]),
|
|
20
|
-
setModel: useCallback((pid, mid) => config.setModel(pid, mid), [config]),
|
|
21
|
-
setMode: useCallback((v) => config.setMode(v), [config]),
|
|
22
|
-
setKey: useCallback((pid, v) => config.setKey(pid, v), [config]),
|
|
23
|
-
getKey: useCallback((pid) => config.getKey(pid), [config]),
|
|
24
|
-
getModel: useCallback((pid) => config.getModel(pid), [config]),
|
|
25
|
-
getLabel: useCallback((pid) => config.getLabel(pid), [config]),
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
import { useState, useCallback, useSyncExternalStore } from 'react'
|
|
2
|
+
import { createProviderConfig } from './createProviderConfig.js'
|
|
3
|
+
|
|
4
|
+
export function useProviderConfig(storageKeys, providers) {
|
|
5
|
+
const [config] = useState(() => createProviderConfig(storageKeys, providers))
|
|
6
|
+
|
|
7
|
+
const snapshot = useSyncExternalStore(
|
|
8
|
+
(listen) => config.subscribe(listen),
|
|
9
|
+
() => ({
|
|
10
|
+
provider: config.provider,
|
|
11
|
+
models: config.models,
|
|
12
|
+
mode: config.mode,
|
|
13
|
+
providers: config.providers,
|
|
14
|
+
})
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
...snapshot,
|
|
19
|
+
setProvider: useCallback((id) => config.setProvider(id), [config]),
|
|
20
|
+
setModel: useCallback((pid, mid) => config.setModel(pid, mid), [config]),
|
|
21
|
+
setMode: useCallback((v) => config.setMode(v), [config]),
|
|
22
|
+
setKey: useCallback((pid, v) => config.setKey(pid, v), [config]),
|
|
23
|
+
getKey: useCallback((pid) => config.getKey(pid), [config]),
|
|
24
|
+
getModel: useCallback((pid) => config.getModel(pid), [config]),
|
|
25
|
+
getLabel: useCallback((pid) => config.getLabel(pid), [config]),
|
|
26
|
+
}
|
|
27
|
+
}
|