autosnippet 3.2.7 → 3.2.9
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/bin/cli.js +13 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +26 -29
- package/lib/cli/SetupService.js +1 -1
- package/lib/core/AstAnalyzer.js +27 -5
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +9 -5
- package/lib/http/routes/remote.js +149 -270
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +70 -28
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +490 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1016 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +408 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
- package/lib/service/agent/domain/insight-producer.js +267 -0
- package/lib/service/agent/domain/scan-prompts.js +105 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +303 -0
- package/lib/service/agent/strategies.js +717 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +33 -22
- package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +49 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
- package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -357
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /package/lib/service/{chat → agent}/tools/query.js +0 -0
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 提取: class, interface, type alias, enum, function, method, property, import, export
|
|
6
6
|
* 模式检测: Singleton, Factory, Observer, React Hook/Component, Middleware, Decorator
|
|
7
|
+
*
|
|
8
|
+
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
|
|
7
9
|
*/
|
|
8
10
|
|
|
11
|
+
import { ImportRecord } from '../analysis/ImportRecord.js';
|
|
12
|
+
import { extractCallSitesTS } from '../analysis/CallSiteExtractor.js';
|
|
13
|
+
|
|
9
14
|
function walkTypeScript(root, ctx) {
|
|
10
15
|
_walkTSNode(root, ctx, null);
|
|
11
16
|
}
|
|
@@ -20,7 +25,9 @@ function _walkTSNode(node, ctx, parentClassName) {
|
|
|
20
25
|
(c) => c.type === 'string' || c.type === 'string_fragment'
|
|
21
26
|
);
|
|
22
27
|
if (source) {
|
|
23
|
-
|
|
28
|
+
const importPath = source.text.replace(/^['"]|['"]$/g, '');
|
|
29
|
+
const importMeta = _parseImportClause(child);
|
|
30
|
+
ctx.imports.push(new ImportRecord(importPath, importMeta));
|
|
24
31
|
}
|
|
25
32
|
break;
|
|
26
33
|
}
|
|
@@ -123,6 +130,12 @@ function _walkTSClassBody(body, ctx, className) {
|
|
|
123
130
|
case 'method_definition': {
|
|
124
131
|
const m = _parseTSMethod(child, className);
|
|
125
132
|
ctx.methods.push(m);
|
|
133
|
+
|
|
134
|
+
// Phase 5.3: Extract constructor parameter properties (DI pattern)
|
|
135
|
+
if (m.name === 'constructor') {
|
|
136
|
+
const constructorProps = _extractTSConstructorProperties(child, className);
|
|
137
|
+
ctx.properties.push(...constructorProps);
|
|
138
|
+
}
|
|
126
139
|
break;
|
|
127
140
|
}
|
|
128
141
|
|
|
@@ -287,15 +300,111 @@ function _parseTSProperty(node, className) {
|
|
|
287
300
|
const isStatic = node.text.trimStart().startsWith('static');
|
|
288
301
|
const isReadonly = node.text.includes('readonly');
|
|
289
302
|
|
|
303
|
+
// Phase 5.3: Extract type annotation for DI/RTA resolution
|
|
304
|
+
const typeAnnotation = _extractTypeAnnotation(node);
|
|
305
|
+
|
|
306
|
+
// Phase 5.3: Extract decorators on properties (e.g. @Inject)
|
|
307
|
+
const decorators = [];
|
|
308
|
+
for (const child of node.namedChildren) {
|
|
309
|
+
if (child.type === 'decorator') {
|
|
310
|
+
decorators.push(child.text);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
290
314
|
return {
|
|
291
315
|
name,
|
|
292
316
|
className,
|
|
293
317
|
isStatic,
|
|
294
318
|
isReadonly,
|
|
319
|
+
typeAnnotation,
|
|
320
|
+
decorators: decorators.length > 0 ? decorators : undefined,
|
|
295
321
|
line: node.startPosition.row + 1,
|
|
296
322
|
};
|
|
297
323
|
}
|
|
298
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Phase 5.3: Extract type name from a type_annotation node
|
|
327
|
+
* Handles: type_identifier, generic_type, nested_type_identifier, union_type
|
|
328
|
+
* Strips generics: UserRepo<T> → UserRepo
|
|
329
|
+
*
|
|
330
|
+
* @param {object} parentNode — AST node that may contain a type_annotation child
|
|
331
|
+
* @returns {string|null}
|
|
332
|
+
*/
|
|
333
|
+
function _extractTypeAnnotation(parentNode) {
|
|
334
|
+
const typeAnnotNode = parentNode.namedChildren.find((c) => c.type === 'type_annotation');
|
|
335
|
+
if (!typeAnnotNode) return null;
|
|
336
|
+
|
|
337
|
+
const typeNode = typeAnnotNode.namedChildren.find(
|
|
338
|
+
(c) =>
|
|
339
|
+
c.type === 'type_identifier' ||
|
|
340
|
+
c.type === 'generic_type' ||
|
|
341
|
+
c.type === 'nested_type_identifier'
|
|
342
|
+
);
|
|
343
|
+
if (!typeNode) return null;
|
|
344
|
+
|
|
345
|
+
// Strip generics: Repository<User> → Repository
|
|
346
|
+
const text = typeNode.text;
|
|
347
|
+
const bracketIdx = text.indexOf('<');
|
|
348
|
+
return bracketIdx > 0 ? text.slice(0, bracketIdx) : text;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Phase 5.3: Extract constructor parameter properties (TypeScript DI pattern)
|
|
353
|
+
*
|
|
354
|
+
* TypeScript shorthand: constructor(private userRepo: UserRepo)
|
|
355
|
+
* creates a class property `userRepo` with type `UserRepo`
|
|
356
|
+
*
|
|
357
|
+
* @param {object} constructorNode — method_definition node with name "constructor"
|
|
358
|
+
* @param {string} className
|
|
359
|
+
* @returns {Array} — property objects
|
|
360
|
+
*/
|
|
361
|
+
function _extractTSConstructorProperties(constructorNode, className) {
|
|
362
|
+
const properties = [];
|
|
363
|
+
const params = constructorNode.namedChildren.find((c) => c.type === 'formal_parameters');
|
|
364
|
+
if (!params) return properties;
|
|
365
|
+
|
|
366
|
+
for (const param of params.namedChildren) {
|
|
367
|
+
if (param.type !== 'required_parameter' && param.type !== 'optional_parameter') continue;
|
|
368
|
+
|
|
369
|
+
// Check for accessibility modifier (public, private, protected) or readonly
|
|
370
|
+
// These turn constructor params into class properties
|
|
371
|
+
const hasAccessibility = param.namedChildren.some(
|
|
372
|
+
(c) => c.type === 'accessibility_modifier' || c.type === 'override_modifier'
|
|
373
|
+
);
|
|
374
|
+
const hasReadonly = param.text.includes('readonly');
|
|
375
|
+
|
|
376
|
+
if (!hasAccessibility && !hasReadonly) continue; // Not a property declaration
|
|
377
|
+
|
|
378
|
+
const nameNode = param.namedChildren.find((c) => c.type === 'identifier');
|
|
379
|
+
const name = nameNode?.text;
|
|
380
|
+
if (!name) continue;
|
|
381
|
+
|
|
382
|
+
// Extract type annotation
|
|
383
|
+
const typeAnnotation = _extractTypeAnnotation(param);
|
|
384
|
+
|
|
385
|
+
// Extract decorators on parameter (e.g. @Inject)
|
|
386
|
+
const decorators = [];
|
|
387
|
+
for (const child of param.namedChildren) {
|
|
388
|
+
if (child.type === 'decorator') {
|
|
389
|
+
decorators.push(child.text);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
properties.push({
|
|
394
|
+
name,
|
|
395
|
+
className,
|
|
396
|
+
isStatic: false,
|
|
397
|
+
isReadonly: hasReadonly,
|
|
398
|
+
typeAnnotation,
|
|
399
|
+
isConstructorParam: true,
|
|
400
|
+
decorators: decorators.length > 0 ? decorators : undefined,
|
|
401
|
+
line: param.startPosition.row + 1,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return properties;
|
|
406
|
+
}
|
|
407
|
+
|
|
299
408
|
function _parseTSVariableDecl(node, ctx, parentClassName) {
|
|
300
409
|
for (const child of node.namedChildren) {
|
|
301
410
|
if (child.type === 'variable_declarator') {
|
|
@@ -315,9 +424,187 @@ function _parseTSVariableDecl(node, ctx, parentClassName) {
|
|
|
315
424
|
line: child.startPosition.row + 1,
|
|
316
425
|
kind: 'definition',
|
|
317
426
|
});
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// CJS require(): const x = require('path') / const { a, b } = require('path')
|
|
431
|
+
// Dynamic import(): const mod = await import('./module')
|
|
432
|
+
const callNode = child.namedChildren.find((c) => c.type === 'call_expression');
|
|
433
|
+
if (callNode) {
|
|
434
|
+
const requireImport = _parseCJSRequire(callNode, child);
|
|
435
|
+
if (requireImport) {
|
|
436
|
+
ctx.imports.push(requireImport);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const dynamicImport = _parseDynamicImport(callNode, child);
|
|
440
|
+
if (dynamicImport) {
|
|
441
|
+
ctx.imports.push(dynamicImport);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// await import() — await wraps the call_expression
|
|
446
|
+
const awaitNode = child.namedChildren.find((c) => c.type === 'await_expression');
|
|
447
|
+
if (awaitNode) {
|
|
448
|
+
const awaitedCall = awaitNode.namedChildren.find((c) => c.type === 'call_expression');
|
|
449
|
+
if (awaitedCall) {
|
|
450
|
+
const dynamicImport = _parseDynamicImport(awaitedCall, child);
|
|
451
|
+
if (dynamicImport) {
|
|
452
|
+
ctx.imports.push(dynamicImport);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ── TS/JS Import 解析 ──
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* 从 import_statement 节点解析导入子句的结构化信息
|
|
464
|
+
*
|
|
465
|
+
* @param {TreeSitterNode} importNode — import_statement 节点
|
|
466
|
+
* @returns {{ symbols: string[], kind: string, alias: string|null, isTypeOnly: boolean }}
|
|
467
|
+
*/
|
|
468
|
+
function _parseImportClause(importNode) {
|
|
469
|
+
const symbols = [];
|
|
470
|
+
let kind = 'side-effect';
|
|
471
|
+
let alias = null;
|
|
472
|
+
let isTypeOnly = false;
|
|
473
|
+
|
|
474
|
+
// 检查 type-only import: import type { ... }
|
|
475
|
+
if (importNode.text.trimStart().startsWith('import type')) {
|
|
476
|
+
isTypeOnly = true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
for (const child of importNode.namedChildren) {
|
|
480
|
+
if (child.type === 'import_clause') {
|
|
481
|
+
for (const clauseChild of child.namedChildren) {
|
|
482
|
+
if (clauseChild.type === 'identifier') {
|
|
483
|
+
// default import: import Foo from '...'
|
|
484
|
+
symbols.push(clauseChild.text);
|
|
485
|
+
kind = 'default';
|
|
486
|
+
} else if (clauseChild.type === 'named_imports') {
|
|
487
|
+
// named imports: import { A, B as C } from '...'
|
|
488
|
+
kind = 'named';
|
|
489
|
+
for (const specifier of clauseChild.namedChildren) {
|
|
490
|
+
if (specifier.type === 'import_specifier') {
|
|
491
|
+
// 收集 specifier 中的所有 identifier / type_identifier
|
|
492
|
+
const identifiers = specifier.namedChildren.filter(
|
|
493
|
+
(c) => c.type === 'identifier' || c.type === 'type_identifier'
|
|
494
|
+
);
|
|
495
|
+
// import { A as B } → identifiers = [A, B] → push B (本地名)
|
|
496
|
+
// import { A } → identifiers = [A] → push A
|
|
497
|
+
if (identifiers.length > 0) {
|
|
498
|
+
symbols.push(identifiers[identifiers.length - 1].text);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} else if (clauseChild.type === 'namespace_import') {
|
|
503
|
+
// namespace import: import * as M from '...'
|
|
504
|
+
kind = 'namespace';
|
|
505
|
+
symbols.push('*');
|
|
506
|
+
const aliasNode = clauseChild.namedChildren.find((c) => c.type === 'identifier');
|
|
507
|
+
if (aliasNode) {
|
|
508
|
+
alias = aliasNode.text;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return { symbols, kind, alias, isTypeOnly };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 解析 CJS require() 调用: const x = require('path') / const { a, b } = require('path')
|
|
520
|
+
*
|
|
521
|
+
* @param {TreeSitterNode} callNode — call_expression 节点
|
|
522
|
+
* @param {TreeSitterNode} declaratorNode — variable_declarator 节点 (包含 lhs 绑定)
|
|
523
|
+
* @returns {ImportRecord|null}
|
|
524
|
+
*/
|
|
525
|
+
function _parseCJSRequire(callNode, declaratorNode) {
|
|
526
|
+
// 检查 callee 是否为 'require'
|
|
527
|
+
const callee = callNode.namedChildren[0];
|
|
528
|
+
if (!callee || callee.type !== 'identifier' || callee.text !== 'require') return null;
|
|
529
|
+
|
|
530
|
+
// 提取 require 参数中的路径字符串
|
|
531
|
+
const args = callNode.namedChildren.find((c) => c.type === 'arguments');
|
|
532
|
+
if (!args || args.namedChildCount === 0) return null;
|
|
533
|
+
|
|
534
|
+
const firstArg = args.namedChildren[0];
|
|
535
|
+
if (!firstArg || (firstArg.type !== 'string' && firstArg.type !== 'template_string')) return null;
|
|
536
|
+
|
|
537
|
+
const importPath = firstArg.text.replace(/^['"`]|['"`]$/g, '');
|
|
538
|
+
if (!importPath) return null;
|
|
539
|
+
|
|
540
|
+
// 解析 lhs 绑定模式
|
|
541
|
+
const lhs = declaratorNode.namedChildren[0]; // identifier or object_pattern
|
|
542
|
+
if (!lhs) return new ImportRecord(importPath, { symbols: [], kind: 'side-effect' });
|
|
543
|
+
|
|
544
|
+
if (lhs.type === 'identifier') {
|
|
545
|
+
// const express = require('express') → namespace import
|
|
546
|
+
return new ImportRecord(importPath, {
|
|
547
|
+
symbols: ['*'],
|
|
548
|
+
kind: 'namespace',
|
|
549
|
+
alias: lhs.text,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (lhs.type === 'object_pattern') {
|
|
554
|
+
// const { readFile, writeFile } = require('fs')
|
|
555
|
+
const symbols = [];
|
|
556
|
+
for (const prop of lhs.namedChildren) {
|
|
557
|
+
if (prop.type === 'shorthand_property_identifier_pattern' || prop.type === 'shorthand_property_identifier') {
|
|
558
|
+
symbols.push(prop.text);
|
|
559
|
+
} else if (prop.type === 'pair_pattern' || prop.type === 'pair') {
|
|
560
|
+
// { readFile: rf } → 使用本地名 rf
|
|
561
|
+
const identifiers = prop.namedChildren.filter((c) => c.type === 'identifier');
|
|
562
|
+
if (identifiers.length > 0) {
|
|
563
|
+
symbols.push(identifiers[identifiers.length - 1].text);
|
|
564
|
+
}
|
|
318
565
|
}
|
|
319
566
|
}
|
|
567
|
+
return new ImportRecord(importPath, {
|
|
568
|
+
symbols: symbols.length > 0 ? symbols : ['*'],
|
|
569
|
+
kind: symbols.length > 0 ? 'named' : 'namespace',
|
|
570
|
+
});
|
|
320
571
|
}
|
|
572
|
+
|
|
573
|
+
// 其他 lhs 模式 (如数组解构), 作为 side-effect 处理
|
|
574
|
+
return new ImportRecord(importPath, { symbols: [], kind: 'side-effect' });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* 解析动态 import() 表达式: const mod = await import('./module')
|
|
579
|
+
*
|
|
580
|
+
* @param {TreeSitterNode} callNode — call_expression 节点
|
|
581
|
+
* @param {TreeSitterNode} declaratorNode — variable_declarator 节点
|
|
582
|
+
* @returns {ImportRecord|null}
|
|
583
|
+
*/
|
|
584
|
+
function _parseDynamicImport(callNode, declaratorNode) {
|
|
585
|
+
// 动态 import() 在 tree-sitter 中解析为 call_expression, callee 是 'import'
|
|
586
|
+
const callee = callNode.namedChildren[0];
|
|
587
|
+
if (!callee) return null;
|
|
588
|
+
// tree-sitter 可能将 import() 解析为 identifier('import') 或 special node
|
|
589
|
+
if (callee.text !== 'import') return null;
|
|
590
|
+
|
|
591
|
+
const args = callNode.namedChildren.find((c) => c.type === 'arguments');
|
|
592
|
+
if (!args || args.namedChildCount === 0) return null;
|
|
593
|
+
|
|
594
|
+
const firstArg = args.namedChildren[0];
|
|
595
|
+
if (!firstArg || (firstArg.type !== 'string' && firstArg.type !== 'template_string')) return null;
|
|
596
|
+
|
|
597
|
+
const importPath = firstArg.text.replace(/^['"`]|['"`]$/g, '');
|
|
598
|
+
if (!importPath) return null;
|
|
599
|
+
|
|
600
|
+
const lhs = declaratorNode?.namedChildren?.[0];
|
|
601
|
+
const alias = lhs?.type === 'identifier' ? lhs.text : null;
|
|
602
|
+
|
|
603
|
+
return new ImportRecord(importPath, {
|
|
604
|
+
symbols: ['*'],
|
|
605
|
+
kind: 'dynamic',
|
|
606
|
+
alias,
|
|
607
|
+
});
|
|
321
608
|
}
|
|
322
609
|
|
|
323
610
|
// ── TS/JS 模式检测 ──
|
|
@@ -477,6 +764,7 @@ export const plugin = {
|
|
|
477
764
|
getGrammar,
|
|
478
765
|
walk: walkTypeScript,
|
|
479
766
|
detectPatterns: detectTSPatterns,
|
|
767
|
+
extractCallSites: extractCallSitesTS,
|
|
480
768
|
extensions: ['.ts'],
|
|
481
769
|
};
|
|
482
770
|
|
|
@@ -493,5 +781,6 @@ export const tsxPlugin = {
|
|
|
493
781
|
getGrammar: getTsxGrammar,
|
|
494
782
|
walk: walkTypeScript,
|
|
495
783
|
detectPatterns: detectTSPatterns,
|
|
784
|
+
extractCallSites: extractCallSitesTS,
|
|
496
785
|
extensions: ['.tsx'],
|
|
497
786
|
};
|
|
@@ -11,7 +11,7 @@ import { JvmDiscoverer } from './JvmDiscoverer.js';
|
|
|
11
11
|
import { NodeDiscoverer } from './NodeDiscoverer.js';
|
|
12
12
|
import { PythonDiscoverer } from './PythonDiscoverer.js';
|
|
13
13
|
import { RustDiscoverer } from './RustDiscoverer.js';
|
|
14
|
-
import { SpmDiscoverer } from '
|
|
14
|
+
import { SpmDiscoverer } from '../../platform/ios/spm/SpmDiscoverer.js';
|
|
15
15
|
|
|
16
16
|
/** @type {DiscovererRegistry|null} */
|
|
17
17
|
let _registry = null;
|
|
@@ -53,4 +53,4 @@ export { NodeDiscoverer } from './NodeDiscoverer.js';
|
|
|
53
53
|
export { ProjectDiscoverer } from './ProjectDiscoverer.js';
|
|
54
54
|
export { PythonDiscoverer } from './PythonDiscoverer.js';
|
|
55
55
|
export { RustDiscoverer } from './RustDiscoverer.js';
|
|
56
|
-
export { SpmDiscoverer } from '
|
|
56
|
+
export { SpmDiscoverer } from '../../platform/ios/spm/SpmDiscoverer.js';
|
|
@@ -20,6 +20,51 @@ export class AiProvider {
|
|
|
20
20
|
this._circuitThreshold = config.circuitThreshold || 5; // 触发熔断的连续失败次数
|
|
21
21
|
this._circuitOpenedAt = 0; // 熔断打开时间
|
|
22
22
|
this._circuitCooldownMs = 30_000; // 初始冷却 30 秒
|
|
23
|
+
|
|
24
|
+
// ── Provider 级全局并发闸门 + 429 冷却窗 ──
|
|
25
|
+
this._maxConcurrency = Math.max(1, Number(config.maxConcurrency || process.env.ASD_AI_MAX_CONCURRENCY || 4));
|
|
26
|
+
this._activeRequests = 0;
|
|
27
|
+
this._requestQueue = [];
|
|
28
|
+
this._rateLimitedUntil = 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async _acquireRequestSlot() {
|
|
32
|
+
if (this._activeRequests < this._maxConcurrency) {
|
|
33
|
+
this._activeRequests += 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await new Promise((resolve) => this._requestQueue.push(resolve));
|
|
37
|
+
this._activeRequests += 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_releaseRequestSlot() {
|
|
41
|
+
this._activeRequests = Math.max(0, this._activeRequests - 1);
|
|
42
|
+
const next = this._requestQueue.shift();
|
|
43
|
+
if (next) {
|
|
44
|
+
next();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async _waitForRateLimitWindow() {
|
|
49
|
+
const waitMs = (this._rateLimitedUntil || 0) - Date.now();
|
|
50
|
+
if (waitMs > 0) {
|
|
51
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_setRateLimitWindow(waitMs) {
|
|
56
|
+
const safeWait = Math.max(0, Number(waitMs) || 0);
|
|
57
|
+
if (safeWait <= 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const until = Date.now() + safeWait;
|
|
61
|
+
if (until > (this._rateLimitedUntil || 0)) {
|
|
62
|
+
this._rateLimitedUntil = until;
|
|
63
|
+
this._log?.(
|
|
64
|
+
'warn',
|
|
65
|
+
`[RateLimit] ${this.name} enters cooldown ${Math.round(safeWait / 1000)}s (global)`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
23
68
|
}
|
|
24
69
|
|
|
25
70
|
/**
|
|
@@ -81,9 +126,9 @@ export class AiProvider {
|
|
|
81
126
|
* 带工具声明的结构化对话 — 原生函数调用 API
|
|
82
127
|
*
|
|
83
128
|
* 支持原生函数调用的 Provider(Gemini / OpenAI / Claude)覆盖此方法,
|
|
84
|
-
* 返回结构化 functionCall 而非文本,
|
|
129
|
+
* 返回结构化 functionCall 而非文本,AgentRuntime 据此跳过正则解析。
|
|
85
130
|
*
|
|
86
|
-
* 默认实现降级为 chat(),由
|
|
131
|
+
* 默认实现降级为 chat(),由 AgentRuntime 进行文本解析。
|
|
87
132
|
*
|
|
88
133
|
* 统一消息格式 (Provider-Agnostic):
|
|
89
134
|
* - { role: 'user', content: 'text' }
|
|
@@ -150,35 +195,6 @@ export class AiProvider {
|
|
|
150
195
|
return this.extractJSON(response, openChar, closeChar);
|
|
151
196
|
}
|
|
152
197
|
|
|
153
|
-
/**
|
|
154
|
-
* 从源码文件批量提取 Recipe 结构(AI 驱动)
|
|
155
|
-
* 默认实现使用 chat() + 标准提示词;子类可覆盖以使用专用 API
|
|
156
|
-
* @param {string} targetName - SPM Target 名称
|
|
157
|
-
* @param {Array<{name:string,content:string}>} filesContent
|
|
158
|
-
* @param {object} [options] - 可选参数
|
|
159
|
-
* @returns {Promise<Array<object>>}
|
|
160
|
-
*/
|
|
161
|
-
async extractRecipes(targetName, filesContent, options = {}) {
|
|
162
|
-
const prompt = this._buildExtractPrompt(targetName, filesContent, options);
|
|
163
|
-
const parsed = await this.chatWithStructuredOutput(prompt, {
|
|
164
|
-
openChar: '[',
|
|
165
|
-
closeChar: ']',
|
|
166
|
-
temperature: 0.3,
|
|
167
|
-
maxTokens: 32768,
|
|
168
|
-
});
|
|
169
|
-
if (!Array.isArray(parsed)) {
|
|
170
|
-
this._log(
|
|
171
|
-
'warn',
|
|
172
|
-
`[extractRecipes] structured output parse failed for target: ${targetName}`
|
|
173
|
-
);
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
if (parsed.length === 0) {
|
|
177
|
-
this._log('info', `[extractRecipes] AI returned empty array for target: ${targetName}`);
|
|
178
|
-
}
|
|
179
|
-
return parsed;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
198
|
/**
|
|
183
199
|
* 内部日志辅助(子类可通过 this.logger 覆盖)
|
|
184
200
|
*/
|
|
@@ -193,147 +209,6 @@ export class AiProvider {
|
|
|
193
209
|
}
|
|
194
210
|
}
|
|
195
211
|
|
|
196
|
-
/**
|
|
197
|
-
* 构建 extractRecipes 标准提示词(语言自适应 + Skill 增强)
|
|
198
|
-
*/
|
|
199
|
-
_buildExtractPrompt(targetName, filesContent, options = {}) {
|
|
200
|
-
const files = filesContent.map((f) => `--- FILE: ${f.name} ---\n${f.content}`).join('\n\n');
|
|
201
|
-
|
|
202
|
-
// 检测文件主要语言
|
|
203
|
-
const langProfile = this._detectLanguageProfile(filesContent);
|
|
204
|
-
|
|
205
|
-
// AST 代码结构分析注入 — 帮助 AI 理解继承体系、设计模式、代码规模
|
|
206
|
-
const astSection = options.astContext
|
|
207
|
-
? `\n# Code Structure Analysis (AST)\nThe following is a Tree-sitter AST analysis of the project. Use this structural context to better understand class hierarchies, design patterns, and code quality when extracting recipes:\n\n${options.astContext.substring(0, 3000)}\n`
|
|
208
|
-
: '';
|
|
209
|
-
|
|
210
|
-
// 用户语言偏好 — 控制 AI 输出人类可读字段的语言
|
|
211
|
-
const langInstruction = this._buildLangInstruction(options.lang);
|
|
212
|
-
|
|
213
|
-
// comprehensive 模式:全量分析整个文件,不跳过任何有意义的方法
|
|
214
|
-
if (options.comprehensive) {
|
|
215
|
-
return `# Role
|
|
216
|
-
You are a ${langProfile.role} performing a **comprehensive full-file analysis**.
|
|
217
|
-
|
|
218
|
-
# Goal
|
|
219
|
-
Thoroughly analyze ALL code in "${targetName}" and create Recipe entries for **every significant method, function, or code block**.
|
|
220
|
-
This is a full-file analysis — do NOT skip methods just because they seem "simple" or "standard".
|
|
221
|
-
${astSection}
|
|
222
|
-
|
|
223
|
-
# What to extract
|
|
224
|
-
- **Every** complete method/function with 5+ lines of implementation
|
|
225
|
-
- Initialization and configuration methods (init, viewDidLoad, setup, configure)
|
|
226
|
-
- Event handlers and action methods
|
|
227
|
-
- Data processing and business logic
|
|
228
|
-
- Protocol/delegate implementations
|
|
229
|
-
- ANY code block that a developer might reference, learn from, or reuse
|
|
230
|
-
|
|
231
|
-
# Extraction Rules
|
|
232
|
-
- Extract **BROADLY** — include all meaningful code units, not just "clever" or "novel" patterns
|
|
233
|
-
- Each recipe must be a **complete, standalone** code unit with full signature and body
|
|
234
|
-
- Put the complete code in \`content.pattern\`, and a meaningful project writeup in \`content.markdown\`
|
|
235
|
-
- Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
|
|
236
|
-
- Every recipe must be traceable to real code in the file. Do NOT invent code
|
|
237
|
-
- Include relevant \`headers\` (import/require lines) that the code depends on
|
|
238
|
-
- You **MUST** extract at least ONE recipe — every source file has something worth capturing
|
|
239
|
-
- For each recipe, provide a concise \`doClause\` (imperative sentence) and a \`topicHint\` group label
|
|
240
|
-
|
|
241
|
-
${langProfile.extractionExamples}
|
|
242
|
-
|
|
243
|
-
# Output (JSON Array)
|
|
244
|
-
Each item MUST use the following V3 KnowledgeEntry structure:
|
|
245
|
-
- title (string): Descriptive English name
|
|
246
|
-
- description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
|
|
247
|
-
- trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
|
|
248
|
-
- kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
|
|
249
|
-
- knowledgeType (string): "code-pattern" | "architecture" | "best-practice" | "code-standard" | "code-relation" | "data-flow" | "event-and-data-flow" | "module-dependency" | "boundary-constraint" | "code-style" | "solution" | "anti-pattern"
|
|
250
|
-
- complexity (string): "basic" | "intermediate" | "advanced"
|
|
251
|
-
- scope (string): "universal" | "project-specific" | "team-convention"
|
|
252
|
-
- category: ${langProfile.categories}
|
|
253
|
-
- language: "${langProfile.primaryLanguage}"
|
|
254
|
-
- content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
|
|
255
|
-
- reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
|
|
256
|
-
- headers (string[]): Required import/require lines
|
|
257
|
-
- tags (string[]): Search keywords
|
|
258
|
-
- doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
|
|
259
|
-
- dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
|
|
260
|
-
- whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
|
|
261
|
-
- topicHint (string): Group label for related patterns (e.g. "Networking", "UI-Layout", "Error-Handling")
|
|
262
|
-
- coreCode (string): Minimal 3-10 line code skeleton that captures the essence. Must be syntactically complete — balanced brackets/parentheses, never start with } or ) and never end with { or (
|
|
263
|
-
- usageGuide (string): ### 章节格式使用指南 — 描述何时使用此模式、步骤和注意事项
|
|
264
|
-
- constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
|
|
265
|
-
- aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
|
|
266
|
-
|
|
267
|
-
IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
|
|
268
|
-
${langInstruction}
|
|
269
|
-
Return ONLY a JSON array. Do NOT return an empty array.
|
|
270
|
-
|
|
271
|
-
Files Content:
|
|
272
|
-
${files}`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return `# Role
|
|
276
|
-
You are a ${langProfile.role} extracting production-quality reusable code patterns.
|
|
277
|
-
|
|
278
|
-
# Goal
|
|
279
|
-
Extract meaningful, complete code patterns from "${targetName}". Each recipe must provide real value to a developer.
|
|
280
|
-
${astSection}
|
|
281
|
-
|
|
282
|
-
# What makes a GOOD recipe
|
|
283
|
-
- A **complete function/method** or **logical code block** (10-40 lines typically), NOT individual statements
|
|
284
|
-
- Code that demonstrates a **real design pattern**: ${langProfile.patternExamples}
|
|
285
|
-
- Code that a developer would actually **copy-paste and adapt** for a new feature
|
|
286
|
-
|
|
287
|
-
# What makes a BAD recipe (AVOID these)
|
|
288
|
-
- Trivial 2-3 line snippets like just a single assignment or import
|
|
289
|
-
- Overly generic code that doesn't reflect the file's actual logic
|
|
290
|
-
- Breaking a single function into multiple tiny recipes
|
|
291
|
-
|
|
292
|
-
# Extraction Strategy
|
|
293
|
-
For each function/method/class in the file, ask: "Would a developer benefit from having this as a reusable template?" If yes, extract the **complete unit** with its full body.
|
|
294
|
-
|
|
295
|
-
${langProfile.extractionExamples}
|
|
296
|
-
|
|
297
|
-
# Rules
|
|
298
|
-
1. \`content.pattern\` must contain a **complete function/method or logical unit** — include the signature and full body
|
|
299
|
-
2. \`content.markdown\` must be a meaningful project-specific writeup explaining what this code does and when to use it
|
|
300
|
-
3. Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
|
|
301
|
-
4. Every recipe must be traceable to real code in the file. Do NOT invent code
|
|
302
|
-
5. Include relevant \`headers\` (import/require lines) that the code depends on
|
|
303
|
-
6. Every recipe must have a concise \`doClause\` and a \`topicHint\` group label
|
|
304
|
-
|
|
305
|
-
# Output (JSON Array)
|
|
306
|
-
Each item MUST use the following V3 KnowledgeEntry structure:
|
|
307
|
-
- title (string): Descriptive English name
|
|
308
|
-
- description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
|
|
309
|
-
- trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
|
|
310
|
-
- kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
|
|
311
|
-
- knowledgeType (string): "code-pattern" | "architecture" | "best-practice" | "code-standard" | "code-relation" | "data-flow" | "event-and-data-flow" | "module-dependency" | "boundary-constraint" | "code-style" | "solution" | "anti-pattern"
|
|
312
|
-
- complexity (string): "basic" | "intermediate" | "advanced"
|
|
313
|
-
- scope (string): "universal" | "project-specific" | "team-convention"
|
|
314
|
-
- category: ${langProfile.categories}
|
|
315
|
-
- language: "${langProfile.primaryLanguage}"
|
|
316
|
-
- content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
|
|
317
|
-
- reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
|
|
318
|
-
- headers (string[]): Required import/require lines
|
|
319
|
-
- tags (string[]): Search keywords
|
|
320
|
-
- doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
|
|
321
|
-
- dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
|
|
322
|
-
- whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
|
|
323
|
-
- topicHint (string): Group label (e.g. "Networking", "UI-Layout")
|
|
324
|
-
- coreCode (string): Minimal 3-10 line code skeleton that captures the essence. Must be syntactically complete — balanced brackets/parentheses, never start with } or ) and never end with { or (
|
|
325
|
-
- usageGuide (string): ### 章节格式使用指南 — 描述何时使用此模式、步骤和注意事项
|
|
326
|
-
- constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
|
|
327
|
-
- aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
|
|
328
|
-
|
|
329
|
-
IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
|
|
330
|
-
${langInstruction}
|
|
331
|
-
Return ONLY a JSON array. If no meaningful patterns found, return [].
|
|
332
|
-
|
|
333
|
-
Files Content:
|
|
334
|
-
${files}`;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
212
|
/**
|
|
338
213
|
* 根据用户语言偏好生成输出语言指令
|
|
339
214
|
* @param {string} [lang] - 语言代码,如 'zh', 'en'
|
|
@@ -856,7 +731,12 @@ ${items}`;
|
|
|
856
731
|
}
|
|
857
732
|
|
|
858
733
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
734
|
+
let slotAcquired = false;
|
|
859
735
|
try {
|
|
736
|
+
await this._waitForRateLimitWindow();
|
|
737
|
+
await this._acquireRequestSlot();
|
|
738
|
+
slotAcquired = true;
|
|
739
|
+
|
|
860
740
|
const result = await fn();
|
|
861
741
|
// 成功 → 完全重置熔断器(包括冷却时间)
|
|
862
742
|
this._circuitFailures = 0;
|
|
@@ -886,6 +766,16 @@ ${items}`;
|
|
|
886
766
|
causeCode === 'UND_ERR_SOCKET');
|
|
887
767
|
const isRetryable = err.status === 429 || err.status >= 500 || isNetworkError;
|
|
888
768
|
|
|
769
|
+
// 429:触发 provider 级冷却窗,抑制并发重试风暴
|
|
770
|
+
if (err.status === 429) {
|
|
771
|
+
const retryAfterMs = Number(err.retryAfterMs || 0);
|
|
772
|
+
const adaptiveCooldown = Math.max(
|
|
773
|
+
retryAfterMs,
|
|
774
|
+
Math.round(baseDelay * 2 ** attempt * 1.5 + Math.random() * 1000)
|
|
775
|
+
);
|
|
776
|
+
this._setRateLimitWindow(adaptiveCooldown);
|
|
777
|
+
}
|
|
778
|
+
|
|
889
779
|
// 首次失败记录详细诊断(含 cause)
|
|
890
780
|
if (attempt === 0 && (isNetworkError || err.cause)) {
|
|
891
781
|
this._log?.(
|
|
@@ -921,6 +811,10 @@ ${items}`;
|
|
|
921
811
|
`[_withRetry] attempt ${attempt + 1} failed (${err.message}), retrying in ${Math.round(delay / 1000)}s…`
|
|
922
812
|
);
|
|
923
813
|
await new Promise((r) => setTimeout(r, delay));
|
|
814
|
+
} finally {
|
|
815
|
+
if (slotAcquired) {
|
|
816
|
+
this._releaseRequestSlot();
|
|
817
|
+
}
|
|
924
818
|
}
|
|
925
819
|
}
|
|
926
820
|
}
|