@git.zone/tsdoc 2.0.6 → 2.1.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/aidocs_classes/commit.js +2 -2
- package/dist_ts/aidocs_classes/description.js +2 -2
- package/dist_ts/aidocs_classes/readme.js +3 -3
- package/dist_ts/classes.aidoc.d.ts +14 -36
- package/dist_ts/classes.aidoc.js +36 -209
- package/dist_ts/cli.js +3 -15
- package/dist_ts/helpers.agenttools.d.ts +1 -1
- package/dist_ts/helpers.agenttools.js +3 -110
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +6 -5
- package/readme.md +20 -20
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/aidocs_classes/commit.ts +1 -1
- package/ts/aidocs_classes/description.ts +1 -1
- package/ts/aidocs_classes/readme.ts +2 -2
- package/ts/classes.aidoc.ts +48 -253
- package/ts/cli.ts +2 -15
- package/ts/helpers.agenttools.ts +2 -122
- package/ts/plugins.ts +2 -1
package/ts/classes.aidoc.ts
CHANGED
|
@@ -3,70 +3,18 @@ import * as plugins from './plugins.js';
|
|
|
3
3
|
import * as aiDocsClasses from './aidocs_classes/index.js';
|
|
4
4
|
import { logger } from './logging.js';
|
|
5
5
|
|
|
6
|
-
export type TAiDocAuthMode =
|
|
6
|
+
export type TAiDocAuthMode = plugins.tsagent.TTsAgentAuthMode;
|
|
7
7
|
|
|
8
|
-
export interface IAiDocConfig {
|
|
9
|
-
provider?: plugins.smartai.TProvider;
|
|
10
|
-
model?: string;
|
|
11
|
-
authMode?: TAiDocAuthMode;
|
|
12
|
-
chatGptAuthSources?: plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource[];
|
|
13
|
-
chatGptAuthWriteBack?: Partial<Record<plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource, boolean>>;
|
|
14
|
-
providerOptions?: plugins.smartai.TSmartAiProviderOptions;
|
|
15
|
-
cache?: plugins.smartagent.TAgentCacheSetting;
|
|
16
|
-
promptCaching?: boolean | plugins.smartai.ISmartAiCacheOptions;
|
|
17
|
-
apiKey?: string;
|
|
18
|
-
baseUrl?: string;
|
|
19
|
-
ollamaOptions?: plugins.smartai.IOllamaModelOptions;
|
|
20
|
-
}
|
|
8
|
+
export interface IAiDocConfig extends plugins.tsagent.ITsAgentConfig {}
|
|
21
9
|
|
|
22
|
-
export interface IAiDocResolvedConfig extends
|
|
23
|
-
'provider' | 'model' | 'authMode' | 'chatGptAuthSources'
|
|
24
|
-
>> {
|
|
25
|
-
chatGptAuthWriteBack: Partial<Record<plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource, boolean>>;
|
|
26
|
-
providerOptions?: plugins.smartai.TSmartAiProviderOptions;
|
|
27
|
-
cache: plugins.smartagent.TAgentCacheSetting;
|
|
28
|
-
promptCaching?: boolean | plugins.smartai.ISmartAiCacheOptions;
|
|
29
|
-
apiKey?: string;
|
|
30
|
-
baseUrl?: string;
|
|
31
|
-
ollamaOptions?: plugins.smartai.IOllamaModelOptions;
|
|
32
|
-
}
|
|
10
|
+
export interface IAiDocResolvedConfig extends plugins.tsagent.ITsAgentResolvedConfig {}
|
|
33
11
|
|
|
34
12
|
export const defaultAiDocConfig: IAiDocResolvedConfig = {
|
|
35
|
-
|
|
36
|
-
model: 'gpt-5.5',
|
|
37
|
-
authMode: 'auto',
|
|
38
|
-
chatGptAuthSources: ['opencode', 'codex', 'smartai'],
|
|
39
|
-
chatGptAuthWriteBack: {
|
|
40
|
-
smartai: true,
|
|
41
|
-
opencode: false,
|
|
42
|
-
codex: false,
|
|
43
|
-
},
|
|
44
|
-
providerOptions: {
|
|
45
|
-
openai: {
|
|
46
|
-
reasoningEffort: 'xhigh',
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
cache: 'auto',
|
|
13
|
+
...plugins.tsagent.defaultTsAgentConfig,
|
|
50
14
|
};
|
|
51
15
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const mergeProviderOptions = (
|
|
57
|
-
base?: plugins.smartai.TSmartAiProviderOptions,
|
|
58
|
-
override?: plugins.smartai.TSmartAiProviderOptions,
|
|
59
|
-
): plugins.smartai.TSmartAiProviderOptions | undefined => {
|
|
60
|
-
if (!base && !override) return undefined;
|
|
61
|
-
const result: Record<string, any> = { ...(base ?? {}) };
|
|
62
|
-
for (const [provider, options] of Object.entries(override ?? {})) {
|
|
63
|
-
result[provider] = {
|
|
64
|
-
...(isPlainObject(result[provider]) ? result[provider] : {}),
|
|
65
|
-
...(isPlainObject(options) ? options : {}),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
return result as plugins.smartai.TSmartAiProviderOptions;
|
|
69
|
-
};
|
|
16
|
+
type TAiDocRunAgentOptions = Parameters<plugins.tsagent.TsAgent['runAgent']>[0];
|
|
17
|
+
type TAiDocRunAgentResult = Awaited<ReturnType<plugins.tsagent.TsAgent['runAgent']>>;
|
|
70
18
|
|
|
71
19
|
const normalizeAuthMode = (value: unknown): TAiDocAuthMode | undefined => {
|
|
72
20
|
return value === 'auto' || value === 'chatgpt' || value === 'apiKey' ? value : undefined;
|
|
@@ -93,46 +41,29 @@ const normalizeOpenAiReasoningEffort = (value: unknown): plugins.smartai.TOpenAi
|
|
|
93
41
|
: undefined;
|
|
94
42
|
};
|
|
95
43
|
|
|
96
|
-
const getProjectTsdocConfig = async (projectDirArg: string): Promise<IAiDocConfig> => {
|
|
97
|
-
const smartconfigPath = plugins.path.join(projectDirArg, '.smartconfig.json');
|
|
98
|
-
if (!(await plugins.fsInstance.file(smartconfigPath).exists())) {
|
|
99
|
-
return {};
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
const smartconfigJson = JSON.parse(String(await plugins.fsInstance.file(smartconfigPath).encoding('utf8').read()));
|
|
103
|
-
const legacyConfig = isPlainObject(smartconfigJson.tsdoc) ? smartconfigJson.tsdoc : {};
|
|
104
|
-
const namespacedConfig = isPlainObject(smartconfigJson['@git.zone/tsdoc']) ? smartconfigJson['@git.zone/tsdoc'] : {};
|
|
105
|
-
const config = {
|
|
106
|
-
...legacyConfig,
|
|
107
|
-
...namespacedConfig,
|
|
108
|
-
...(isPlainObject(legacyConfig.ai) ? legacyConfig.ai : {}),
|
|
109
|
-
...(isPlainObject(namespacedConfig.ai) ? namespacedConfig.ai : {}),
|
|
110
|
-
};
|
|
111
|
-
return config as IAiDocConfig;
|
|
112
|
-
} catch (error) {
|
|
113
|
-
throw new Error(`Could not parse .smartconfig.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
44
|
export class AiDoc {
|
|
45
|
+
private tsAgent?: plugins.tsagent.TsAgent;
|
|
118
46
|
private openaiToken = '';
|
|
119
47
|
|
|
120
48
|
public smartconfigKV?: plugins.smartconfig.KeyValueStore<{ OPENAI_TOKEN: string }>;
|
|
121
|
-
public qenvInstance
|
|
122
|
-
public aidocInteract
|
|
123
|
-
public model!: plugins.
|
|
124
|
-
public providerOptions?:
|
|
49
|
+
public qenvInstance?: plugins.qenv.Qenv;
|
|
50
|
+
public aidocInteract?: plugins.smartinteract.SmartInteract;
|
|
51
|
+
public model!: plugins.tsagent.TsAgent['model'];
|
|
52
|
+
public providerOptions?: IAiDocResolvedConfig['providerOptions'];
|
|
125
53
|
public config: IAiDocResolvedConfig = defaultAiDocConfig;
|
|
126
54
|
public selectedAuthSource: string = 'none';
|
|
127
55
|
|
|
128
|
-
argvArg: any;
|
|
56
|
+
public argvArg: any;
|
|
129
57
|
|
|
130
58
|
constructor(argvArg?: any) {
|
|
131
|
-
this.argvArg = argvArg;
|
|
59
|
+
this.argvArg = argvArg || {};
|
|
132
60
|
}
|
|
133
61
|
|
|
134
|
-
private get
|
|
135
|
-
|
|
62
|
+
private get activeTsAgent(): plugins.tsagent.TsAgent {
|
|
63
|
+
if (!this.tsAgent) {
|
|
64
|
+
throw new Error('AiDoc.start() must be called before running AI tasks.');
|
|
65
|
+
}
|
|
66
|
+
return this.tsAgent;
|
|
136
67
|
}
|
|
137
68
|
|
|
138
69
|
private async migrateKeyValueStore(): Promise<void> {
|
|
@@ -152,201 +83,65 @@ export class AiDoc {
|
|
|
152
83
|
}
|
|
153
84
|
}
|
|
154
85
|
|
|
155
|
-
private async
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
const token = await this.smartconfigKV.readKey('OPENAI_TOKEN');
|
|
159
|
-
return token || undefined;
|
|
160
|
-
} catch {
|
|
161
|
-
return undefined;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private async resolveApiKey(provider: plugins.smartai.TProvider): Promise<string | undefined> {
|
|
166
|
-
const argvToken = this.argvArg?.OPENAI_TOKEN || this.argvArg?.OPENAI_API_KEY || this.argvArg?.apiKey;
|
|
167
|
-
if (argvToken) return argvToken;
|
|
168
|
-
if (this.config.apiKey) return this.config.apiKey;
|
|
169
|
-
|
|
170
|
-
const envNames = provider === 'openai'
|
|
171
|
-
? ['OPENAI_TOKEN', 'OPENAI_API_KEY']
|
|
172
|
-
: [`${provider.toUpperCase()}_TOKEN`, `${provider.toUpperCase()}_API_KEY`];
|
|
173
|
-
for (const envName of envNames) {
|
|
174
|
-
const value = await this.qenvInstance.getEnvVarOnDemand(envName);
|
|
175
|
-
if (value) return value;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (provider === 'openai') {
|
|
179
|
-
const storedToken = await this.getStoredOpenAiToken();
|
|
180
|
-
if (storedToken) return storedToken;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private async promptForOpenAiToken(): Promise<string> {
|
|
187
|
-
if (!this.isInteractive) {
|
|
188
|
-
throw new Error('OpenAI API key is required. Set OPENAI_TOKEN/OPENAI_API_KEY or configure ChatGPT subscription auth.');
|
|
189
|
-
}
|
|
190
|
-
await plugins.smartdelay.delayFor(1000);
|
|
191
|
-
const answerObject = await this.aidocInteract.askQuestion({
|
|
192
|
-
type: 'input',
|
|
193
|
-
message: 'Please provide your OpenAI token. This will be persisted in your home directory.',
|
|
194
|
-
name: 'OPENAI_TOKEN',
|
|
195
|
-
default: '',
|
|
196
|
-
});
|
|
197
|
-
const token = answerObject.value;
|
|
198
|
-
await this.smartconfigKV?.writeKey('OPENAI_TOKEN', token);
|
|
199
|
-
return token;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private async runChatGptDeviceLogin(): Promise<plugins.smartai.IOpenAiChatGptTokenData> {
|
|
203
|
-
if (!this.isInteractive) {
|
|
204
|
-
throw new Error('ChatGPT subscription auth is required, but no usable auth file was found in non-interactive mode.');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const deviceCode = await plugins.smartai.requestOpenAiChatGptDeviceCode();
|
|
208
|
-
console.log(`Open ${deviceCode.verificationUrl} and enter code ${deviceCode.userCode}`);
|
|
209
|
-
const tokenData = await plugins.smartai.completeOpenAiChatGptDeviceCodeLogin(deviceCode);
|
|
210
|
-
const authFilePath = plugins.smartaiOpenAiChatGptAuth.getDefaultOpenAiChatGptAuthPath('smartai');
|
|
211
|
-
await plugins.smartaiOpenAiChatGptAuth.writeOpenAiChatGptAuthFile(authFilePath, tokenData, 'smartai');
|
|
212
|
-
return tokenData;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private async resolveConfig(): Promise<IAiDocResolvedConfig> {
|
|
216
|
-
const projectConfig = await getProjectTsdocConfig(process.cwd());
|
|
86
|
+
private async resolveTsdocEnvConfig(): Promise<IAiDocConfig> {
|
|
87
|
+
this.qenvInstance = new plugins.qenv.Qenv();
|
|
217
88
|
const envProvider = normalizeProvider(await this.qenvInstance.getEnvVarOnDemand('TSDOC_AI_PROVIDER'));
|
|
218
89
|
const envModel = await this.qenvInstance.getEnvVarOnDemand('TSDOC_AI_MODEL');
|
|
219
90
|
const envAuthMode = normalizeAuthMode(await this.qenvInstance.getEnvVarOnDemand('TSDOC_AUTH_MODE'));
|
|
220
91
|
const envChatGptSources = normalizeChatGptSources(await this.qenvInstance.getEnvVarOnDemand('TSDOC_CHATGPT_AUTH_SOURCES'));
|
|
221
92
|
const envReasoningEffort = normalizeOpenAiReasoningEffort(await this.qenvInstance.getEnvVarOnDemand('TSDOC_OPENAI_REASONING_EFFORT'));
|
|
222
|
-
const envProviderOptions = envReasoningEffort ? {
|
|
223
|
-
openai: {
|
|
224
|
-
reasoningEffort: envReasoningEffort,
|
|
225
|
-
},
|
|
226
|
-
} as plugins.smartai.TSmartAiProviderOptions : undefined;
|
|
227
|
-
const argvConfig: IAiDocConfig = {
|
|
228
|
-
provider: normalizeProvider(this.argvArg?.provider),
|
|
229
|
-
model: typeof this.argvArg?.model === 'string' ? this.argvArg.model : undefined,
|
|
230
|
-
authMode: normalizeAuthMode(this.argvArg?.authMode),
|
|
231
|
-
chatGptAuthSources: normalizeChatGptSources(this.argvArg?.chatGptAuthSources),
|
|
232
|
-
};
|
|
233
|
-
const provider = argvConfig.provider ?? envProvider ?? normalizeProvider(projectConfig.provider) ?? defaultAiDocConfig.provider;
|
|
234
|
-
const model = argvConfig.model ?? envModel ?? projectConfig.model ?? defaultAiDocConfig.model;
|
|
235
|
-
const authMode = argvConfig.authMode ?? envAuthMode ?? normalizeAuthMode(projectConfig.authMode) ?? defaultAiDocConfig.authMode;
|
|
236
|
-
const chatGptAuthSources = argvConfig.chatGptAuthSources
|
|
237
|
-
?? envChatGptSources
|
|
238
|
-
?? normalizeChatGptSources(projectConfig.chatGptAuthSources)
|
|
239
|
-
?? defaultAiDocConfig.chatGptAuthSources;
|
|
240
|
-
const providerOptions = mergeProviderOptions(
|
|
241
|
-
mergeProviderOptions(defaultAiDocConfig.providerOptions, projectConfig.providerOptions),
|
|
242
|
-
envProviderOptions,
|
|
243
|
-
);
|
|
244
|
-
|
|
245
93
|
return {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
},
|
|
256
|
-
providerOptions,
|
|
257
|
-
cache: projectConfig.cache ?? defaultAiDocConfig.cache,
|
|
94
|
+
provider: envProvider,
|
|
95
|
+
model: envModel || undefined,
|
|
96
|
+
authMode: envAuthMode,
|
|
97
|
+
chatGptAuthSources: envChatGptSources,
|
|
98
|
+
providerOptions: envReasoningEffort ? {
|
|
99
|
+
openai: {
|
|
100
|
+
reasoningEffort: envReasoningEffort,
|
|
101
|
+
},
|
|
102
|
+
} : undefined,
|
|
258
103
|
};
|
|
259
104
|
}
|
|
260
105
|
|
|
261
106
|
public async start() {
|
|
262
|
-
this.aidocInteract = new plugins.smartinteract.SmartInteract();
|
|
263
|
-
this.qenvInstance = new plugins.qenv.Qenv();
|
|
264
107
|
await this.migrateKeyValueStore();
|
|
265
108
|
this.smartconfigKV = new plugins.smartconfig.KeyValueStore({
|
|
266
109
|
typeArg: 'userHomeDir',
|
|
267
110
|
identityArg: '@git.zone/tsdoc',
|
|
268
111
|
mandatoryKeys: ['OPENAI_TOKEN'],
|
|
269
112
|
});
|
|
270
|
-
this.config = await this.resolveConfig();
|
|
271
113
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
});
|
|
279
|
-
if (resolvedAuth) {
|
|
280
|
-
openAiChatGptAuth = resolvedAuth.tokenData;
|
|
281
|
-
this.selectedAuthSource = resolvedAuth.source;
|
|
282
|
-
logger.log('info', `Using OpenAI ChatGPT auth from ${resolvedAuth.source}.`);
|
|
283
|
-
} else if (this.config.authMode === 'chatgpt') {
|
|
284
|
-
openAiChatGptAuth = await this.runChatGptDeviceLogin();
|
|
285
|
-
this.selectedAuthSource = 'smartai';
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
let apiKey: string | undefined;
|
|
290
|
-
if (!openAiChatGptAuth) {
|
|
291
|
-
apiKey = await this.resolveApiKey(this.config.provider);
|
|
292
|
-
if (!apiKey && this.config.provider === 'openai' && this.config.authMode === 'auto' && this.isInteractive) {
|
|
293
|
-
openAiChatGptAuth = await this.runChatGptDeviceLogin();
|
|
294
|
-
this.selectedAuthSource = 'smartai';
|
|
295
|
-
}
|
|
296
|
-
if (!apiKey && !openAiChatGptAuth && this.config.provider === 'openai' && this.config.authMode !== 'chatgpt') {
|
|
297
|
-
apiKey = await this.promptForOpenAiToken();
|
|
298
|
-
}
|
|
299
|
-
if (apiKey) {
|
|
300
|
-
this.selectedAuthSource = 'apiKey';
|
|
301
|
-
if (this.config.provider === 'openai') {
|
|
302
|
-
this.openaiToken = apiKey;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const setup = plugins.smartai.getModelSetup({
|
|
308
|
-
provider: this.config.provider,
|
|
309
|
-
model: this.config.model,
|
|
310
|
-
apiKey,
|
|
311
|
-
openAiChatGptAuth,
|
|
312
|
-
providerOptions: this.config.providerOptions,
|
|
313
|
-
promptCaching: this.config.promptCaching,
|
|
314
|
-
baseUrl: this.config.baseUrl,
|
|
315
|
-
ollamaOptions: this.config.ollamaOptions,
|
|
114
|
+
this.tsAgent = new plugins.tsagent.TsAgent({
|
|
115
|
+
argvArg: this.argvArg,
|
|
116
|
+
projectDir: process.cwd(),
|
|
117
|
+
projectConfigKeys: ['@git.zone/tsagent', 'tsdoc', '@git.zone/tsdoc'],
|
|
118
|
+
legacyTokenStoreIdentities: ['@git.zone/tsdoc'],
|
|
119
|
+
config: await this.resolveTsdocEnvConfig(),
|
|
316
120
|
});
|
|
317
|
-
this.
|
|
318
|
-
|
|
121
|
+
await this.tsAgent.start();
|
|
122
|
+
|
|
123
|
+
this.model = this.tsAgent.model;
|
|
124
|
+
this.providerOptions = this.tsAgent.providerOptions;
|
|
125
|
+
this.config = this.tsAgent.config as IAiDocResolvedConfig;
|
|
126
|
+
this.selectedAuthSource = this.tsAgent.selectedAuthSource;
|
|
127
|
+
this.openaiToken = this.tsAgent.getOpenaiToken();
|
|
128
|
+
if (this.selectedAuthSource !== 'none') {
|
|
129
|
+
logger.log('info', `Using AI auth from ${this.selectedAuthSource}.`);
|
|
130
|
+
}
|
|
319
131
|
}
|
|
320
132
|
|
|
321
133
|
public async stop() {
|
|
322
|
-
|
|
134
|
+
await this.tsAgent?.stop();
|
|
323
135
|
}
|
|
324
136
|
|
|
325
137
|
public getOpenaiToken(): string {
|
|
326
138
|
return this.openaiToken;
|
|
327
139
|
}
|
|
328
140
|
|
|
329
|
-
public async runAgent(options:
|
|
330
|
-
|
|
331
|
-
taskName: string;
|
|
332
|
-
useCompaction?: boolean;
|
|
333
|
-
}): Promise<plugins.smartagent.IAgentRunResult> {
|
|
334
|
-
const providerOptions = this.config.provider === 'openai' && this.selectedAuthSource !== 'apiKey' && options.system
|
|
335
|
-
? mergeProviderOptions(this.providerOptions, {
|
|
336
|
-
openai: {
|
|
337
|
-
instructions: options.system,
|
|
338
|
-
},
|
|
339
|
-
})
|
|
340
|
-
: this.providerOptions;
|
|
341
|
-
const result = await plugins.smartagent.runAgent({
|
|
141
|
+
public async runAgent(options: TAiDocRunAgentOptions): Promise<TAiDocRunAgentResult> {
|
|
142
|
+
const result = await this.activeTsAgent.runAgent({
|
|
342
143
|
...options,
|
|
343
|
-
model: this.model,
|
|
344
|
-
providerOptions,
|
|
345
|
-
cache: this.config.cache,
|
|
346
144
|
sessionId: options.sessionId ?? `tsdoc:${options.taskName}:${plugins.path.resolve(options.projectDir)}`,
|
|
347
|
-
onContextOverflow: options.useCompaction
|
|
348
|
-
? async (messages) => plugins.smartagentCompaction.compactMessages(this.model, messages)
|
|
349
|
-
: options.onContextOverflow,
|
|
350
145
|
});
|
|
351
146
|
logger.log(
|
|
352
147
|
'info',
|
package/ts/cli.ts
CHANGED
|
@@ -6,12 +6,6 @@ import { TypeDoc } from './classes.typedoc.js';
|
|
|
6
6
|
import { AiDoc } from './classes.aidoc.js';
|
|
7
7
|
import { NoChangesError } from './aidocs_classes/commit.js';
|
|
8
8
|
|
|
9
|
-
const defaultChatGptAuthSources: plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource[] = [
|
|
10
|
-
'opencode',
|
|
11
|
-
'codex',
|
|
12
|
-
'smartai',
|
|
13
|
-
];
|
|
14
|
-
|
|
15
9
|
const createAiDoc = async (argvArg: any): Promise<AiDoc> => {
|
|
16
10
|
const aidocInstance = new AiDoc(argvArg);
|
|
17
11
|
await aidocInstance.start();
|
|
@@ -27,18 +21,11 @@ const getSmartcliArgv = (): string[] | undefined => {
|
|
|
27
21
|
const handleAuthCommand = async (argvArg: any): Promise<void> => {
|
|
28
22
|
const subcommand = argvArg._?.[1] ?? 'status';
|
|
29
23
|
if (subcommand === 'login') {
|
|
30
|
-
|
|
31
|
-
console.log(`Open ${deviceCode.verificationUrl} and enter code ${deviceCode.userCode}`);
|
|
32
|
-
const tokenData = await plugins.smartai.completeOpenAiChatGptDeviceCodeLogin(deviceCode);
|
|
33
|
-
const authFilePath = plugins.smartaiOpenAiChatGptAuth.getDefaultOpenAiChatGptAuthPath('smartai');
|
|
34
|
-
await plugins.smartaiOpenAiChatGptAuth.writeOpenAiChatGptAuthFile(authFilePath, tokenData, 'smartai');
|
|
35
|
-
logger.log('success', `Stored OpenAI ChatGPT auth at ${authFilePath}`);
|
|
24
|
+
await plugins.tsagent.loginChatGptAuth();
|
|
36
25
|
return;
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
const inspections = await plugins.
|
|
40
|
-
sources: defaultChatGptAuthSources,
|
|
41
|
-
});
|
|
28
|
+
const inspections = await plugins.tsagent.inspectChatGptAuthSources();
|
|
42
29
|
console.log('OpenAI ChatGPT auth sources:');
|
|
43
30
|
for (const inspection of inspections) {
|
|
44
31
|
const status = inspection.usable
|
package/ts/helpers.agenttools.ts
CHANGED
|
@@ -1,125 +1,5 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
'.git',
|
|
6
|
-
'.next',
|
|
7
|
-
'.nogit',
|
|
8
|
-
'.rpt2_cache',
|
|
9
|
-
'build',
|
|
10
|
-
'coverage',
|
|
11
|
-
'dist',
|
|
12
|
-
'node_modules',
|
|
13
|
-
'out',
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
|
-
const defaultDeniedBasenames = new Set([
|
|
17
|
-
'.npmrc',
|
|
18
|
-
'bun.lockb',
|
|
19
|
-
'credentials.json',
|
|
20
|
-
'deno.lock',
|
|
21
|
-
'npm-shrinkwrap.json',
|
|
22
|
-
'package-lock.json',
|
|
23
|
-
'pnpm-lock.yaml',
|
|
24
|
-
'yarn.lock',
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
const normalizeRelativePath = (rootDir: string, inputPath: string): string => {
|
|
28
|
-
const resolvedRoot = plugins.path.resolve(rootDir);
|
|
29
|
-
const resolvedPath = plugins.path.resolve(
|
|
30
|
-
plugins.path.isAbsolute(inputPath) ? inputPath : plugins.path.join(resolvedRoot, inputPath),
|
|
31
|
-
);
|
|
32
|
-
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${plugins.path.sep}`)) {
|
|
33
|
-
throw new Error(`Access denied: "${inputPath}" is outside allowed root "${rootDir}"`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const relativePath = plugins.path.relative(resolvedRoot, resolvedPath) || '.';
|
|
37
|
-
assertAllowedRelativePath(relativePath);
|
|
38
|
-
return resolvedPath;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const assertAllowedRelativePath = (relativePath: string): void => {
|
|
42
|
-
const normalized = relativePath.split(plugins.path.sep).join('/');
|
|
43
|
-
const parts = normalized.split('/').filter(Boolean);
|
|
44
|
-
const basename = parts.at(-1) ?? normalized;
|
|
45
|
-
|
|
46
|
-
if (basename === '.env' || basename.startsWith('.env.')) {
|
|
47
|
-
throw new Error(`Access denied: ${relativePath} may contain environment secrets.`);
|
|
48
|
-
}
|
|
49
|
-
if (basename.endsWith('.pem') || basename.endsWith('.key') || basename.endsWith('.p12')) {
|
|
50
|
-
throw new Error(`Access denied: ${relativePath} may contain private key material.`);
|
|
51
|
-
}
|
|
52
|
-
if (defaultDeniedBasenames.has(basename)) {
|
|
53
|
-
throw new Error(`Access denied: ${relativePath} is excluded from AI file access.`);
|
|
54
|
-
}
|
|
55
|
-
for (const segment of parts) {
|
|
56
|
-
if (defaultDeniedSegments.has(segment) || segment.startsWith('dist_')) {
|
|
57
|
-
throw new Error(`Access denied: ${relativePath} is excluded from AI file access.`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const listDirectory = async (dirPath: string, recursive = false, prefix = ''): Promise<string[]> => {
|
|
63
|
-
const entries = await plugins.fs.readdir(dirPath, { withFileTypes: true });
|
|
64
|
-
const result: string[] = [];
|
|
65
|
-
for (const entry of entries) {
|
|
66
|
-
const relativeEntryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
67
|
-
try {
|
|
68
|
-
assertAllowedRelativePath(relativeEntryPath);
|
|
69
|
-
} catch {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
result.push(`${relativeEntryPath}${entry.isDirectory() ? '/' : ''}`);
|
|
73
|
-
if (recursive && entry.isDirectory()) {
|
|
74
|
-
result.push(...await listDirectory(plugins.path.join(dirPath, entry.name), true, relativeEntryPath));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export const createReadOnlyFileSystemTools = (rootDirArg: string): plugins.smartai.ToolSet => {
|
|
81
|
-
const rootDir = plugins.path.resolve(rootDirArg);
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
read_file: plugins.smartagent.tool({
|
|
85
|
-
description: 'Read file contents within the project. Secret and generated paths are blocked.',
|
|
86
|
-
inputSchema: plugins.smartagent.z.object({
|
|
87
|
-
path: plugins.smartagent.z.string().describe('Absolute or project-relative path to the file'),
|
|
88
|
-
startLine: plugins.smartagent.z.number().optional().describe('First line, 1-indexed and inclusive'),
|
|
89
|
-
endLine: plugins.smartagent.z.number().optional().describe('Last line, 1-indexed and inclusive'),
|
|
90
|
-
}),
|
|
91
|
-
execute: async ({ path: filePath, startLine, endLine }: {
|
|
92
|
-
path: string;
|
|
93
|
-
startLine?: number;
|
|
94
|
-
endLine?: number;
|
|
95
|
-
}) => {
|
|
96
|
-
const resolvedPath = normalizeRelativePath(rootDir, filePath);
|
|
97
|
-
const stat = await plugins.fs.stat(resolvedPath);
|
|
98
|
-
if (!stat.isFile()) {
|
|
99
|
-
throw new Error(`Cannot read non-file path: ${filePath}`);
|
|
100
|
-
}
|
|
101
|
-
const content = await plugins.fs.readFile(resolvedPath, 'utf8');
|
|
102
|
-
const selectedContent = startLine !== undefined || endLine !== undefined
|
|
103
|
-
? content.split('\n').slice((startLine ?? 1) - 1, endLine).join('\n')
|
|
104
|
-
: content;
|
|
105
|
-
return plugins.smartagent.truncateOutput(selectedContent).content;
|
|
106
|
-
},
|
|
107
|
-
}),
|
|
108
|
-
list_directory: plugins.smartagent.tool({
|
|
109
|
-
description: 'List project files and directories. Secret and generated paths are omitted.',
|
|
110
|
-
inputSchema: plugins.smartagent.z.object({
|
|
111
|
-
path: plugins.smartagent.z.string().describe('Absolute or project-relative directory path to list'),
|
|
112
|
-
recursive: plugins.smartagent.z.boolean().optional().describe('List recursively'),
|
|
113
|
-
}),
|
|
114
|
-
execute: async ({ path: dirPath, recursive }: { path: string; recursive?: boolean }) => {
|
|
115
|
-
const resolvedPath = normalizeRelativePath(rootDir, dirPath);
|
|
116
|
-
const stat = await plugins.fs.stat(resolvedPath);
|
|
117
|
-
if (!stat.isDirectory()) {
|
|
118
|
-
throw new Error(`Cannot list non-directory path: ${dirPath}`);
|
|
119
|
-
}
|
|
120
|
-
const entries = await listDirectory(resolvedPath, recursive === true);
|
|
121
|
-
return plugins.smartagent.truncateOutput(entries.join('\n')).content;
|
|
122
|
-
},
|
|
123
|
-
}),
|
|
124
|
-
};
|
|
3
|
+
export const createReadOnlyFileSystemTools = async (rootDirArg: string) => {
|
|
4
|
+
return await plugins.tsagent.createReadOnlyProjectTools(rootDirArg);
|
|
125
5
|
};
|
package/ts/plugins.ts
CHANGED
|
@@ -53,9 +53,10 @@ export const fsInstance = new smartfs.SmartFs(smartFsNodeProvider);
|
|
|
53
53
|
export const smartfileFactory = smartfile.SmartFileFactory.nodeFs();
|
|
54
54
|
|
|
55
55
|
// @git.zone scope
|
|
56
|
+
import * as tsagent from '@git.zone/tsagent';
|
|
56
57
|
import * as tspublish from '@git.zone/tspublish';
|
|
57
58
|
|
|
58
|
-
export { tspublish };
|
|
59
|
+
export { tsagent, tspublish };
|
|
59
60
|
|
|
60
61
|
// third party scope
|
|
61
62
|
import * as typedoc from 'typedoc';
|