-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathstore.ts
More file actions
197 lines (178 loc) · 5.11 KB
/
Copy pathstore.ts
File metadata and controls
197 lines (178 loc) · 5.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**
* Store: persisted user settings + AI provider/model discovery.
*
* Settings are persisted via the Scripting `Storage` API.
* Provider/model options are discovered from the iCloud `.scripting` config:
* - <iCloud>/Documents/.scripting/agent-custom-providers.json (custom providers)
* - <iCloud>/Documents/.scripting/agent-custom-models.json (built-in extra models)
*/
const SETTINGS_KEY = "xcstrings.translation.settings.v2"
import { LANGUAGES } from "./languages"
export type BuiltInProviderId =
| "openai"
| "gemini"
| "anthropic"
| "deepseek"
| "openrouter"
export const BUILT_IN_PROVIDERS: BuiltInProviderId[] = [
"openai",
"gemini",
"anthropic",
"deepseek",
"openrouter",
]
/** A flattened provider option for the UI selector. */
export type ProviderOption = {
/** Stable key: built-in id, or `custom:<name>` for custom providers. */
key: string
/** Display label. */
label: string
/** Whether this is a custom provider. */
isCustom: boolean
/** For custom providers, the `name` used as `{ custom: name }`. */
customName?: string
/** Available model ids for this provider. */
models: string[]
/** Provider's default model id, if any. */
defaultModelId?: string
}
export type TranslationSettings = {
/** Interval between request starts, in milliseconds. */
intervalMs: number
/** Concurrency (1..5). */
concurrency: number
/** Number of items per translation request. */
batchSize: number
/** Selected target locales. */
locales: string[]
/** Selected provider option key. */
providerKey: string
/** Selected model id. */
modelId: string
/** Keep the screen awake while translating. */
keepScreenOn: boolean
/** Keep the app alive in background while translating. */
keepAlive: boolean
}
export const DEFAULT_SETTINGS: TranslationSettings = {
intervalMs: 1000,
concurrency: 1,
batchSize: 30,
locales: LANGUAGES.map(l => l.locale),
providerKey: "openrouter",
modelId: "",
keepScreenOn: true,
keepAlive: true,
}
export function loadSettings(): TranslationSettings {
try {
const saved = Storage.get<Partial<TranslationSettings>>(SETTINGS_KEY)
if (saved && typeof saved === "object") {
return { ...DEFAULT_SETTINGS, ...saved }
}
} catch (e) {
console.error("Failed to load settings:", e)
}
return { ...DEFAULT_SETTINGS }
}
export function saveSettings(settings: TranslationSettings) {
try {
Storage.set(SETTINGS_KEY, settings)
} catch (e) {
console.error("Failed to save settings:", e)
}
}
type CustomProviderEntry = {
name?: string
apiType?: string
currentModelId?: string
modelInfos?: Record<string, unknown>
}
type CustomModelsFile = {
models?: Record<string, Record<string, unknown>>
}
function iCloudConfigPath(file: string): string | null {
try {
if (!FileManager.isiCloudEnabled) {
return null
}
return `${FileManager.iCloudDocumentsDirectory}/.scripting/${file}`
} catch (e) {
console.error("iCloud not available:", e)
return null
}
}
async function readJson<T>(path: string | null): Promise<T | null> {
if (!path) {
return null
}
try {
if (!(await FileManager.exists(path))) {
return null
}
const content = await FileManager.readAsString(path)
return JSON.parse(content) as T
} catch (e) {
console.error("Failed to read config:", path, e)
return null
}
}
/**
* Discover available provider options:
* - Built-in providers, with models discovered from agent-custom-models.json.
* - Custom providers from agent-custom-providers.json.
*/
export async function discoverProviders(): Promise<ProviderOption[]> {
const options: ProviderOption[] = []
// Built-in providers + their configured models.
const modelsFile = await readJson<CustomModelsFile>(
iCloudConfigPath("agent-custom-models.json"),
)
const modelsMap = modelsFile?.models ?? {}
for (const id of BUILT_IN_PROVIDERS) {
const group = modelsMap[`${id}.custom.models`]
const models = group ? Object.keys(group) : []
options.push({
key: id,
label: id,
isCustom: false,
models,
defaultModelId: models[0],
})
}
// Custom providers.
const custom = await readJson<CustomProviderEntry[]>(
iCloudConfigPath("agent-custom-providers.json"),
)
if (Array.isArray(custom)) {
for (const entry of custom) {
const name = entry?.name
if (!name) {
continue
}
const models = entry.modelInfos ? Object.keys(entry.modelInfos) : []
options.push({
key: `custom:${name}`,
label: name,
isCustom: true,
customName: name,
models,
defaultModelId: entry.currentModelId ?? models[0],
})
}
}
return options
}
/** Resolve a provider option + model id into an Assistant provider selection. */
export function toProviderSelection(
option: ProviderOption,
modelId: string,
): { provider: Assistant.Provider; modelId?: string } {
const provider: Assistant.Provider = option.isCustom
? { custom: option.customName! }
: (option.key as BuiltInProviderId)
return {
provider,
modelId: modelId || option.defaultModelId || undefined,
}
}