playwright-intelligence-engine 0.0.0-stage → 2.0.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/LICENSE +191 -0
- package/README.md +229 -3
- package/bin/pw-intel.js +37 -0
- package/dist/authoring/author.d.ts +83 -0
- package/dist/authoring/author.d.ts.map +1 -0
- package/dist/authoring/generation-contract.d.ts +90 -0
- package/dist/authoring/generation-contract.d.ts.map +1 -0
- package/dist/authoring/hybrid-author.d.ts +6 -0
- package/dist/authoring/hybrid-author.d.ts.map +1 -0
- package/dist/authoring/index.d.ts +7 -0
- package/dist/authoring/index.d.ts.map +1 -0
- package/dist/authoring/llm-author.d.ts +28 -0
- package/dist/authoring/llm-author.d.ts.map +1 -0
- package/dist/authoring/naming/index.d.ts +4 -0
- package/dist/authoring/naming/index.d.ts.map +1 -0
- package/dist/authoring/naming/name-plan.d.ts +45 -0
- package/dist/authoring/naming/name-plan.d.ts.map +1 -0
- package/dist/authoring/naming/name-planner.d.ts +25 -0
- package/dist/authoring/naming/name-planner.d.ts.map +1 -0
- package/dist/authoring/naming/naming-linter.d.ts +20 -0
- package/dist/authoring/naming/naming-linter.d.ts.map +1 -0
- package/dist/authoring/template-author.d.ts +6 -0
- package/dist/authoring/template-author.d.ts.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/collectors/collector-interface.d.ts +7 -0
- package/dist/collectors/collector-interface.d.ts.map +1 -0
- package/dist/collectors/environment-collector.d.ts +12 -0
- package/dist/collectors/environment-collector.d.ts.map +1 -0
- package/dist/collectors/error-collector.d.ts +18 -0
- package/dist/collectors/error-collector.d.ts.map +1 -0
- package/dist/collectors/git-diff-collector.d.ts +20 -0
- package/dist/collectors/git-diff-collector.d.ts.map +1 -0
- package/dist/collectors/live-browser-collector.d.ts +45 -0
- package/dist/collectors/live-browser-collector.d.ts.map +1 -0
- package/dist/collectors/network-collector.d.ts +28 -0
- package/dist/collectors/network-collector.d.ts.map +1 -0
- package/dist/collectors/static-ast-collector.d.ts +32 -0
- package/dist/collectors/static-ast-collector.d.ts.map +1 -0
- package/dist/collectors/trace-collector.d.ts +12 -0
- package/dist/collectors/trace-collector.d.ts.map +1 -0
- package/dist/config/project-config.d.ts +33 -0
- package/dist/config/project-config.d.ts.map +1 -0
- package/dist/core/errors/engine-errors.d.ts +34 -0
- package/dist/core/errors/engine-errors.d.ts.map +1 -0
- package/dist/core/types/diagnostic-context.d.ts +151 -0
- package/dist/core/types/diagnostic-context.d.ts.map +1 -0
- package/dist/core/types/repair-evidence.d.ts +56 -0
- package/dist/core/types/repair-evidence.d.ts.map +1 -0
- package/dist/core/version.d.ts +13 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/diagnostics/diagnostic-contract.d.ts +52 -0
- package/dist/diagnostics/diagnostic-contract.d.ts.map +1 -0
- package/dist/diagnostics/diagnostic-report.d.ts +11 -0
- package/dist/diagnostics/diagnostic-report.d.ts.map +1 -0
- package/dist/diagnostics/evidence-correlator.d.ts +22 -0
- package/dist/diagnostics/evidence-correlator.d.ts.map +1 -0
- package/dist/diagnostics/external-failure-evidence.d.ts +18 -0
- package/dist/diagnostics/external-failure-evidence.d.ts.map +1 -0
- package/dist/diagnostics/failure-category.d.ts +4 -0
- package/dist/diagnostics/failure-category.d.ts.map +1 -0
- package/dist/diagnostics/failure-classifier.d.ts +58 -0
- package/dist/diagnostics/failure-classifier.d.ts.map +1 -0
- package/dist/evaluation/evaluation-scorecard.d.ts +72 -0
- package/dist/evaluation/evaluation-scorecard.d.ts.map +1 -0
- package/dist/evaluation/repository-inspector.d.ts +21 -0
- package/dist/evaluation/repository-inspector.d.ts.map +1 -0
- package/dist/evaluation/repository-runner.d.ts +24 -0
- package/dist/evaluation/repository-runner.d.ts.map +1 -0
- package/dist/evidence/evidence-model.d.ts +88 -0
- package/dist/evidence/evidence-model.d.ts.map +1 -0
- package/dist/evidence/evidence-normalizer.d.ts +19 -0
- package/dist/evidence/evidence-normalizer.d.ts.map +1 -0
- package/dist/evidence/evidence-redactor.d.ts +37 -0
- package/dist/evidence/evidence-redactor.d.ts.map +1 -0
- package/dist/framework/config-adapter.d.ts +28 -0
- package/dist/framework/config-adapter.d.ts.map +1 -0
- package/dist/gate/index.d.ts +21 -0
- package/dist/gate/index.d.ts.map +1 -0
- package/dist/gate/oracle.d.ts +26 -0
- package/dist/gate/oracle.d.ts.map +1 -0
- package/dist/gate/quality.d.ts +27 -0
- package/dist/gate/quality.d.ts.map +1 -0
- package/dist/gate/safety.d.ts +28 -0
- package/dist/gate/safety.d.ts.map +1 -0
- package/dist/generator/architecture-generator.d.ts +55 -0
- package/dist/generator/architecture-generator.d.ts.map +1 -0
- package/dist/generator/assertion-quality-gate.d.ts +18 -0
- package/dist/generator/assertion-quality-gate.d.ts.map +1 -0
- package/dist/generator/candidate-writer.d.ts +34 -0
- package/dist/generator/candidate-writer.d.ts.map +1 -0
- package/dist/generator/test-generator.d.ts +26 -0
- package/dist/generator/test-generator.d.ts.map +1 -0
- package/dist/governance/audit-log.d.ts +23 -0
- package/dist/governance/audit-log.d.ts.map +1 -0
- package/dist/governance/governance-policy.d.ts +24 -0
- package/dist/governance/governance-policy.d.ts.map +1 -0
- package/dist/governance/team-dashboard.d.ts +51 -0
- package/dist/governance/team-dashboard.d.ts.map +1 -0
- package/dist/governance/workflow-audit.d.ts +13 -0
- package/dist/governance/workflow-audit.d.ts.map +1 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20318 -0
- package/dist/integrations/ci-summary.d.ts +14 -0
- package/dist/integrations/ci-summary.d.ts.map +1 -0
- package/dist/integrations/playwright-report-adapter.d.ts +22 -0
- package/dist/integrations/playwright-report-adapter.d.ts.map +1 -0
- package/dist/integrations/quality-gate.d.ts +35 -0
- package/dist/integrations/quality-gate.d.ts.map +1 -0
- package/dist/integrations/sarif-report.d.ts +48 -0
- package/dist/integrations/sarif-report.d.ts.map +1 -0
- package/dist/intelligence/autonomous-repair-workflow.d.ts +31 -0
- package/dist/intelligence/autonomous-repair-workflow.d.ts.map +1 -0
- package/dist/intelligence/business-intent.d.ts +58 -0
- package/dist/intelligence/business-intent.d.ts.map +1 -0
- package/dist/intelligence/evidence-bundle.d.ts +364 -0
- package/dist/intelligence/evidence-bundle.d.ts.map +1 -0
- package/dist/intelligence/failure-clustering.d.ts +38 -0
- package/dist/intelligence/failure-clustering.d.ts.map +1 -0
- package/dist/intelligence/generation-agent.d.ts +120 -0
- package/dist/intelligence/generation-agent.d.ts.map +1 -0
- package/dist/intelligence/generation-verifier.d.ts +151 -0
- package/dist/intelligence/generation-verifier.d.ts.map +1 -0
- package/dist/intelligence/llm-proposal.d.ts +377 -0
- package/dist/intelligence/llm-proposal.d.ts.map +1 -0
- package/dist/intelligence/locator-disambiguation-engine.d.ts +38 -0
- package/dist/intelligence/locator-disambiguation-engine.d.ts.map +1 -0
- package/dist/intelligence/model-contract.d.ts +86 -0
- package/dist/intelligence/model-contract.d.ts.map +1 -0
- package/dist/intelligence/model-provider.d.ts +75 -0
- package/dist/intelligence/model-provider.d.ts.map +1 -0
- package/dist/intelligence/project-intelligence.d.ts +22 -0
- package/dist/intelligence/project-intelligence.d.ts.map +1 -0
- package/dist/intelligence/repair-agent.d.ts +46 -0
- package/dist/intelligence/repair-agent.d.ts.map +1 -0
- package/dist/intelligence/repository-profile.d.ts +131 -0
- package/dist/intelligence/repository-profile.d.ts.map +1 -0
- package/dist/intelligence/suite-intelligence.d.ts +59 -0
- package/dist/intelligence/suite-intelligence.d.ts.map +1 -0
- package/dist/intelligence/test-intelligence.d.ts +44 -0
- package/dist/intelligence/test-intelligence.d.ts.map +1 -0
- package/dist/intelligence/trace-proposal-workflow.d.ts +30 -0
- package/dist/intelligence/trace-proposal-workflow.d.ts.map +1 -0
- package/dist/intelligence/verified-llm-repair-workflow.d.ts +21 -0
- package/dist/intelligence/verified-llm-repair-workflow.d.ts.map +1 -0
- package/dist/intelligence/workflow-planner.d.ts +278 -0
- package/dist/intelligence/workflow-planner.d.ts.map +1 -0
- package/dist/mcp/server.d.ts +32 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/memory/stability-ledger.d.ts +46 -0
- package/dist/memory/stability-ledger.d.ts.map +1 -0
- package/dist/perception/app-model.d.ts +55 -0
- package/dist/perception/app-model.d.ts.map +1 -0
- package/dist/perception/crawler.d.ts +18 -0
- package/dist/perception/crawler.d.ts.map +1 -0
- package/dist/perception/index.d.ts +3 -0
- package/dist/perception/index.d.ts.map +1 -0
- package/dist/repair/approval-service.d.ts +139 -0
- package/dist/repair/approval-service.d.ts.map +1 -0
- package/dist/repair/assertion-hasher.d.ts +62 -0
- package/dist/repair/assertion-hasher.d.ts.map +1 -0
- package/dist/repair/ast-patcher.d.ts +36 -0
- package/dist/repair/ast-patcher.d.ts.map +1 -0
- package/dist/repair/browser-operation-executor.d.ts +27 -0
- package/dist/repair/browser-operation-executor.d.ts.map +1 -0
- package/dist/repair/diff-allowlist.d.ts +74 -0
- package/dist/repair/diff-allowlist.d.ts.map +1 -0
- package/dist/repair/intent-validator.d.ts +157 -0
- package/dist/repair/intent-validator.d.ts.map +1 -0
- package/dist/repair/repair-operations.d.ts +40 -0
- package/dist/repair/repair-operations.d.ts.map +1 -0
- package/dist/repair/repair-planner.d.ts +98 -0
- package/dist/repair/repair-planner.d.ts.map +1 -0
- package/dist/repair/repair-workflow.d.ts +42 -0
- package/dist/repair/repair-workflow.d.ts.map +1 -0
- package/dist/repair/safe-operation-compiler.d.ts +15 -0
- package/dist/repair/safe-operation-compiler.d.ts.map +1 -0
- package/dist/repair/source-mapper.d.ts +19 -0
- package/dist/repair/source-mapper.d.ts.map +1 -0
- package/dist/repair/statement-patcher.d.ts +75 -0
- package/dist/repair/statement-patcher.d.ts.map +1 -0
- package/dist/repair/verification-gates.d.ts +25 -0
- package/dist/repair/verification-gates.d.ts.map +1 -0
- package/dist/repair/workflow-plan.d.ts +35 -0
- package/dist/repair/workflow-plan.d.ts.map +1 -0
- package/dist/repair/workspace-promoter.d.ts +43 -0
- package/dist/repair/workspace-promoter.d.ts.map +1 -0
- package/dist/reproduce/diagnostic-rerunner.d.ts +40 -0
- package/dist/reproduce/diagnostic-rerunner.d.ts.map +1 -0
- package/dist/reproduce/direct-browser-investigator.d.ts +41 -0
- package/dist/reproduce/direct-browser-investigator.d.ts.map +1 -0
- package/dist/reproduce/interactive-workflow-runner.d.ts +43 -0
- package/dist/reproduce/interactive-workflow-runner.d.ts.map +1 -0
- package/dist/sandbox/dependency-strategy.d.ts +45 -0
- package/dist/sandbox/dependency-strategy.d.ts.map +1 -0
- package/dist/sandbox/execution-runner.d.ts +81 -0
- package/dist/sandbox/execution-runner.d.ts.map +1 -0
- package/dist/sandbox/worktree-manager.d.ts +51 -0
- package/dist/sandbox/worktree-manager.d.ts.map +1 -0
- package/dist/selector/abstention-policy.d.ts +34 -0
- package/dist/selector/abstention-policy.d.ts.map +1 -0
- package/dist/selector/candidate-extractor.d.ts +71 -0
- package/dist/selector/candidate-extractor.d.ts.map +1 -0
- package/dist/selector/live-locator-verifier.d.ts +28 -0
- package/dist/selector/live-locator-verifier.d.ts.map +1 -0
- package/dist/selector/repair-candidate-ranker.d.ts +10 -0
- package/dist/selector/repair-candidate-ranker.d.ts.map +1 -0
- package/dist/selector/selector-scorer.d.ts +43 -0
- package/dist/selector/selector-scorer.d.ts.map +1 -0
- package/dist/trace/adapters/adapter-registry.d.ts +18 -0
- package/dist/trace/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/trace/adapters/trace-adapter.d.ts +21 -0
- package/dist/trace/adapters/trace-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/unknown-adapter.d.ts +10 -0
- package/dist/trace/adapters/unknown-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v10-adapter.d.ts +10 -0
- package/dist/trace/adapters/v10-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v4-adapter.d.ts +11 -0
- package/dist/trace/adapters/v4-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v8-adapter.d.ts +10 -0
- package/dist/trace/adapters/v8-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v9-adapter.d.ts +10 -0
- package/dist/trace/adapters/v9-adapter.d.ts.map +1 -0
- package/dist/trace/archive-reader.d.ts +44 -0
- package/dist/trace/archive-reader.d.ts.map +1 -0
- package/dist/trace/snapshot-resolver.d.ts +55 -0
- package/dist/trace/snapshot-resolver.d.ts.map +1 -0
- package/dist/trace/trace-parser.d.ts +50 -0
- package/dist/trace/trace-parser.d.ts.map +1 -0
- package/dist/trace/version-detector.d.ts +20 -0
- package/dist/trace/version-detector.d.ts.map +1 -0
- package/package.json +76 -6
- package/skills/README.md +20 -0
- package/skills/pie-failure-investigation/SKILL.md +16 -0
- package/skills/pie-quality-engineer/SKILL.md +66 -0
- package/skills/pie-release-readiness/SKILL.md +22 -0
- package/skills/pie-repair-review/SKILL.md +21 -0
- package/skills/pie-trace-to-repair/SKILL.md +35 -0
- package/src/authoring/author.ts +81 -0
- package/src/authoring/generation-contract.ts +283 -0
- package/src/authoring/hybrid-author.ts +115 -0
- package/src/authoring/index.ts +7 -0
- package/src/authoring/llm-author.ts +282 -0
- package/src/authoring/naming/index.ts +4 -0
- package/src/authoring/naming/name-plan.ts +28 -0
- package/src/authoring/naming/name-planner.ts +339 -0
- package/src/authoring/naming/naming-linter.ts +292 -0
- package/src/authoring/template-author.ts +39 -0
- package/src/cli/index.ts +1172 -0
- package/src/collectors/collector-interface.ts +8 -0
- package/src/collectors/environment-collector.ts +103 -0
- package/src/collectors/error-collector.ts +177 -0
- package/src/collectors/git-diff-collector.ts +190 -0
- package/src/collectors/live-browser-collector.ts +412 -0
- package/src/collectors/network-collector.ts +90 -0
- package/src/collectors/static-ast-collector.ts +226 -0
- package/src/collectors/trace-collector.ts +66 -0
- package/src/config/project-config.ts +114 -0
- package/src/core/errors/engine-errors.ts +90 -0
- package/src/core/types/diagnostic-context.ts +94 -0
- package/src/core/types/repair-evidence.ts +45 -0
- package/src/core/version.ts +25 -0
- package/src/diagnostics/diagnostic-contract.ts +79 -0
- package/src/diagnostics/diagnostic-report.ts +204 -0
- package/src/diagnostics/evidence-correlator.ts +117 -0
- package/src/diagnostics/external-failure-evidence.ts +72 -0
- package/src/diagnostics/failure-category.ts +40 -0
- package/src/diagnostics/failure-classifier.ts +701 -0
- package/src/evaluation/evaluation-scorecard.ts +198 -0
- package/src/evaluation/repository-inspector.ts +143 -0
- package/src/evaluation/repository-runner.ts +197 -0
- package/src/evidence/evidence-model.ts +76 -0
- package/src/evidence/evidence-normalizer.ts +322 -0
- package/src/evidence/evidence-redactor.ts +218 -0
- package/src/framework/config-adapter.ts +131 -0
- package/src/gate/index.ts +43 -0
- package/src/gate/oracle.ts +69 -0
- package/src/gate/quality.ts +101 -0
- package/src/gate/safety.ts +94 -0
- package/src/generator/architecture-generator.ts +582 -0
- package/src/generator/assertion-quality-gate.ts +88 -0
- package/src/generator/candidate-writer.ts +288 -0
- package/src/generator/test-generator.ts +52 -0
- package/src/governance/audit-log.ts +115 -0
- package/src/governance/governance-policy.ts +72 -0
- package/src/governance/team-dashboard.ts +189 -0
- package/src/governance/workflow-audit.ts +72 -0
- package/src/index.ts +107 -0
- package/src/integrations/ci-summary.ts +37 -0
- package/src/integrations/playwright-report-adapter.ts +173 -0
- package/src/integrations/quality-gate.ts +95 -0
- package/src/integrations/sarif-report.ts +110 -0
- package/src/intelligence/autonomous-repair-workflow.ts +139 -0
- package/src/intelligence/business-intent.ts +163 -0
- package/src/intelligence/evidence-bundle.ts +300 -0
- package/src/intelligence/failure-clustering.ts +134 -0
- package/src/intelligence/generation-agent.ts +561 -0
- package/src/intelligence/generation-verifier.ts +1115 -0
- package/src/intelligence/llm-proposal.ts +519 -0
- package/src/intelligence/locator-disambiguation-engine.ts +331 -0
- package/src/intelligence/model-contract.ts +205 -0
- package/src/intelligence/model-provider.ts +348 -0
- package/src/intelligence/project-intelligence.ts +140 -0
- package/src/intelligence/repair-agent.ts +297 -0
- package/src/intelligence/repository-profile.ts +796 -0
- package/src/intelligence/suite-intelligence.ts +305 -0
- package/src/intelligence/test-intelligence.ts +136 -0
- package/src/intelligence/trace-proposal-workflow.ts +318 -0
- package/src/intelligence/verified-llm-repair-workflow.ts +48 -0
- package/src/intelligence/workflow-planner.ts +966 -0
- package/src/mcp/server.ts +970 -0
- package/src/memory/stability-ledger.ts +243 -0
- package/src/perception/app-model.ts +32 -0
- package/src/perception/crawler.ts +263 -0
- package/src/perception/index.ts +3 -0
- package/src/repair/approval-service.ts +541 -0
- package/src/repair/assertion-hasher.ts +386 -0
- package/src/repair/ast-patcher.ts +223 -0
- package/src/repair/browser-operation-executor.ts +263 -0
- package/src/repair/diff-allowlist.ts +612 -0
- package/src/repair/intent-validator.ts +761 -0
- package/src/repair/repair-operations.ts +65 -0
- package/src/repair/repair-planner.ts +467 -0
- package/src/repair/repair-workflow.ts +241 -0
- package/src/repair/safe-operation-compiler.ts +68 -0
- package/src/repair/source-mapper.ts +156 -0
- package/src/repair/statement-patcher.ts +544 -0
- package/src/repair/verification-gates.ts +46 -0
- package/src/repair/workflow-plan.ts +59 -0
- package/src/repair/workspace-promoter.ts +313 -0
- package/src/reproduce/diagnostic-rerunner.ts +287 -0
- package/src/reproduce/direct-browser-investigator.ts +208 -0
- package/src/reproduce/interactive-workflow-runner.ts +828 -0
- package/src/sandbox/dependency-strategy.ts +215 -0
- package/src/sandbox/execution-runner.ts +408 -0
- package/src/sandbox/worktree-manager.ts +287 -0
- package/src/selector/abstention-policy.ts +175 -0
- package/src/selector/candidate-extractor.ts +343 -0
- package/src/selector/live-locator-verifier.ts +139 -0
- package/src/selector/repair-candidate-ranker.ts +92 -0
- package/src/selector/selector-scorer.ts +344 -0
- package/src/trace/adapters/adapter-registry.ts +49 -0
- package/src/trace/adapters/trace-adapter.ts +28 -0
- package/src/trace/adapters/unknown-adapter.ts +50 -0
- package/src/trace/adapters/v10-adapter.ts +39 -0
- package/src/trace/adapters/v4-adapter.ts +31 -0
- package/src/trace/adapters/v8-adapter.ts +42 -0
- package/src/trace/adapters/v9-adapter.ts +39 -0
- package/src/trace/archive-reader.ts +246 -0
- package/src/trace/snapshot-resolver.ts +308 -0
- package/src/trace/trace-parser.ts +467 -0
- package/src/trace/version-detector.ts +81 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { ResolvedElement } from '../trace/snapshot-resolver.js';
|
|
2
|
+
import { CandidateExtractor } from '../selector/candidate-extractor.js';
|
|
3
|
+
|
|
4
|
+
export type DisambiguationStrategy =
|
|
5
|
+
| 'UNIQUE_DIRECT_MATCH'
|
|
6
|
+
| 'VISIBILITY_FILTER'
|
|
7
|
+
| 'CONTAINER_ANCHOR'
|
|
8
|
+
| 'LANDMARK_SECTION'
|
|
9
|
+
| 'EXPLICIT_POSITIONAL_INTENT'
|
|
10
|
+
| 'ABSTAIN_AMBIGUOUS';
|
|
11
|
+
|
|
12
|
+
export interface DisambiguationContext {
|
|
13
|
+
originalSelector?: string | undefined;
|
|
14
|
+
intendedAction?: string | undefined;
|
|
15
|
+
testIntentText?: string | undefined;
|
|
16
|
+
rootDom?: ResolvedElement | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DisambiguationResult {
|
|
20
|
+
strategy: DisambiguationStrategy;
|
|
21
|
+
resolvedCandidate?: ResolvedElement;
|
|
22
|
+
resolvedExpression?: string;
|
|
23
|
+
scopedContainer?: ResolvedElement;
|
|
24
|
+
containerAnchorText?: string;
|
|
25
|
+
matchCount: number;
|
|
26
|
+
explanation: string;
|
|
27
|
+
confidence: number;
|
|
28
|
+
isUnique: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class LocatorDisambiguationEngine {
|
|
32
|
+
/**
|
|
33
|
+
* Evaluates multiple candidate elements through the evidence-based disambiguation hierarchy:
|
|
34
|
+
* 1. Unique Direct Match (matchCount === 1)
|
|
35
|
+
* 2. Visibility Filtering (filter { visible: true })
|
|
36
|
+
* 3. Container Anchoring (tr, card, list item with distinguishing text)
|
|
37
|
+
* 4. Landmark / Section Scoping (header, nav, main, dialog)
|
|
38
|
+
* 5. Explicit Positional Intent (.first(), .nth() only if original test explicitly declared it)
|
|
39
|
+
* 6. Safe Abstention (never guess positional intent)
|
|
40
|
+
*/
|
|
41
|
+
public static disambiguate(
|
|
42
|
+
candidates: ResolvedElement[],
|
|
43
|
+
context: DisambiguationContext = {},
|
|
44
|
+
pageProperty = 'page'
|
|
45
|
+
): DisambiguationResult {
|
|
46
|
+
if (candidates.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
strategy: 'ABSTAIN_AMBIGUOUS',
|
|
49
|
+
matchCount: 0,
|
|
50
|
+
explanation: 'Zero matching candidate elements found.',
|
|
51
|
+
confidence: 0,
|
|
52
|
+
isUnique: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 1. Single Unique Candidate
|
|
57
|
+
if (candidates.length === 1) {
|
|
58
|
+
const candidate = candidates[0]!;
|
|
59
|
+
const expr = CandidateExtractor.generateLocator(candidate, pageProperty);
|
|
60
|
+
return {
|
|
61
|
+
strategy: 'UNIQUE_DIRECT_MATCH',
|
|
62
|
+
resolvedCandidate: candidate,
|
|
63
|
+
resolvedExpression: expr,
|
|
64
|
+
matchCount: 1,
|
|
65
|
+
explanation: 'Candidate is strictly unique in the DOM snapshot (matchCount === 1).',
|
|
66
|
+
confidence: 1.0,
|
|
67
|
+
isUnique: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Visibility Filtering
|
|
72
|
+
const visibleCandidates = candidates.filter((c) => this.isElementVisible(c));
|
|
73
|
+
if (visibleCandidates.length === 1) {
|
|
74
|
+
const candidate = visibleCandidates[0]!;
|
|
75
|
+
const baseExpr = CandidateExtractor.generateLocator(candidate, pageProperty);
|
|
76
|
+
const resolvedExpression = `${baseExpr}.filter({ visible: true })`;
|
|
77
|
+
return {
|
|
78
|
+
strategy: 'VISIBILITY_FILTER',
|
|
79
|
+
resolvedCandidate: candidate,
|
|
80
|
+
resolvedExpression,
|
|
81
|
+
matchCount: candidates.length,
|
|
82
|
+
explanation: `Disambiguated by visibility: exactly 1 candidate is visible and interactive while ${candidates.length - 1} duplicate(s) are hidden or off-screen.`,
|
|
83
|
+
confidence: 0.95,
|
|
84
|
+
isUnique: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Container Anchoring
|
|
89
|
+
if (context.rootDom) {
|
|
90
|
+
const anchorResult = this.attemptContainerAnchoring(candidates, context, pageProperty);
|
|
91
|
+
if (anchorResult) {
|
|
92
|
+
return anchorResult;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 4. Landmark / Section Scoping
|
|
97
|
+
if (context.rootDom && (context.originalSelector || context.testIntentText)) {
|
|
98
|
+
const landmarkResult = this.attemptLandmarkScoping(candidates, context, pageProperty);
|
|
99
|
+
if (landmarkResult) {
|
|
100
|
+
return landmarkResult;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 5. Explicit Positional Intent Verification
|
|
105
|
+
const hasExplicitPositionalIntent = this.verifyExplicitPositionalIntent(context);
|
|
106
|
+
if (hasExplicitPositionalIntent) {
|
|
107
|
+
const candidate = candidates[0]!;
|
|
108
|
+
const baseExpr = CandidateExtractor.generateLocator(candidate, pageProperty);
|
|
109
|
+
const positionalMethod = hasExplicitPositionalIntent.method; // '.first()', '.last()', or '.nth(N)'
|
|
110
|
+
const resolvedExpression = `${baseExpr}${positionalMethod}`;
|
|
111
|
+
return {
|
|
112
|
+
strategy: 'EXPLICIT_POSITIONAL_INTENT',
|
|
113
|
+
resolvedCandidate: candidate,
|
|
114
|
+
resolvedExpression,
|
|
115
|
+
matchCount: candidates.length,
|
|
116
|
+
explanation: `Verified explicit positional intent: original test or requirement explicitly specified ${positionalMethod}. Positional selection preserved with verified evidence.`,
|
|
117
|
+
confidence: 0.90,
|
|
118
|
+
isUnique: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 6. Safe Abstention (Never invent positional intent!)
|
|
123
|
+
return {
|
|
124
|
+
strategy: 'ABSTAIN_AMBIGUOUS',
|
|
125
|
+
matchCount: candidates.length,
|
|
126
|
+
explanation: `NON_UNIQUE_MATCH: Locator matches ${candidates.length} elements. Disambiguation failed across visibility filtering, container anchoring, and landmark scoping. In accordance with PIE safety invariants, positional intent (.first()/.nth()) is NOT invented. Safe abstention enforced.`,
|
|
127
|
+
confidence: 0,
|
|
128
|
+
isUnique: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Internal Resolution Strategies
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
private static isElementVisible(el: ResolvedElement): boolean {
|
|
137
|
+
if (el.attributes['hidden'] === 'true' || el.attributes['hidden'] !== undefined) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
if (el.attributes['aria-hidden'] === 'true') {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const style = (el.attributes['style'] ?? '').toLowerCase();
|
|
144
|
+
if (style.includes('display: none') || style.includes('visibility: hidden') || style.includes('opacity: 0')) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private static attemptContainerAnchoring(
|
|
151
|
+
candidates: ResolvedElement[],
|
|
152
|
+
context: DisambiguationContext,
|
|
153
|
+
pageProperty: string
|
|
154
|
+
): DisambiguationResult | null {
|
|
155
|
+
if (!context.rootDom) return null;
|
|
156
|
+
|
|
157
|
+
// For each candidate, find its parent container chain
|
|
158
|
+
const candidatePaths = candidates.map((c) => this.findPathToElement(context.rootDom!, c));
|
|
159
|
+
|
|
160
|
+
// Try finding the closest enclosing container: tr, li, article, card
|
|
161
|
+
for (let cIdx = 0; cIdx < candidates.length; cIdx++) {
|
|
162
|
+
const path = candidatePaths[cIdx];
|
|
163
|
+
if (!path || path.length < 2) continue;
|
|
164
|
+
|
|
165
|
+
// Search bottom-up for a container
|
|
166
|
+
for (let i = path.length - 2; i >= 0; i--) {
|
|
167
|
+
const container = path[i]!;
|
|
168
|
+
const isContainer =
|
|
169
|
+
container.tag === 'tr' ||
|
|
170
|
+
container.tag === 'li' ||
|
|
171
|
+
container.tag === 'article' ||
|
|
172
|
+
container.tag === 'section' ||
|
|
173
|
+
(container.attributes['data-testid'] ?? '').includes('card') ||
|
|
174
|
+
(container.attributes['data-testid'] ?? '').includes('item') ||
|
|
175
|
+
(container.attributes['class'] ?? '').includes('card') ||
|
|
176
|
+
(container.attributes['class'] ?? '').includes('item') ||
|
|
177
|
+
container.role === 'row' ||
|
|
178
|
+
container.role === 'listitem';
|
|
179
|
+
|
|
180
|
+
if (isContainer) {
|
|
181
|
+
// Check distinguishing text inside this container
|
|
182
|
+
const containerText = this.getCleanText(container);
|
|
183
|
+
if (containerText.length > 2 && containerText.length < 80) {
|
|
184
|
+
// Check if other candidates' containers have different text
|
|
185
|
+
const otherContainersHaveText = candidatePaths.some((otherPath, oIdx) => {
|
|
186
|
+
if (oIdx === cIdx || !otherPath) return false;
|
|
187
|
+
const otherContainer = otherPath[i];
|
|
188
|
+
if (!otherContainer) return false;
|
|
189
|
+
return this.getCleanText(otherContainer).includes(containerText);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Distinguishing text must NOT appear in other candidates' containers
|
|
193
|
+
if (!otherContainersHaveText) {
|
|
194
|
+
const candidate = candidates[cIdx]!;
|
|
195
|
+
const innerPlan = CandidateExtractor.generateLocatorPlan(candidate);
|
|
196
|
+
const innerExpr = CandidateExtractor.planToExpression(innerPlan, '').replace(/^\./, '');
|
|
197
|
+
const containerTag = container.attributes['data-testid']
|
|
198
|
+
? `[data-testid="${container.attributes['data-testid']}"]`
|
|
199
|
+
: container.tag;
|
|
200
|
+
const escapedText = CandidateExtractor.escapeLocatorString(containerText);
|
|
201
|
+
const resolvedExpression = `${pageProperty}.locator('${containerTag}').filter({ hasText: '${escapedText}' }).${innerExpr}`;
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
strategy: 'CONTAINER_ANCHOR',
|
|
205
|
+
resolvedCandidate: candidate,
|
|
206
|
+
resolvedExpression,
|
|
207
|
+
scopedContainer: container,
|
|
208
|
+
containerAnchorText: containerText,
|
|
209
|
+
matchCount: candidates.length,
|
|
210
|
+
explanation: `Disambiguated via container anchoring: target is uniquely scoped within <${container.tag}> anchored by distinguishing text '${containerText}'.`,
|
|
211
|
+
confidence: 0.92,
|
|
212
|
+
isUnique: true,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private static attemptLandmarkScoping(
|
|
224
|
+
candidates: ResolvedElement[],
|
|
225
|
+
context: DisambiguationContext,
|
|
226
|
+
pageProperty: string
|
|
227
|
+
): DisambiguationResult | null {
|
|
228
|
+
if (!context.rootDom) return null;
|
|
229
|
+
|
|
230
|
+
const landmarks = ['header', 'nav', 'main', 'footer', 'aside', 'dialog'];
|
|
231
|
+
const textToCheck = `${context.originalSelector ?? ''} ${context.testIntentText ?? ''}`.toLowerCase();
|
|
232
|
+
|
|
233
|
+
for (const landmark of landmarks) {
|
|
234
|
+
if (textToCheck.includes(landmark)) {
|
|
235
|
+
// Collect all candidates residing inside this landmark
|
|
236
|
+
const candidatesInLandmark = candidates.filter((candidate) => {
|
|
237
|
+
const path = this.findPathToElement(context.rootDom!, candidate);
|
|
238
|
+
return Boolean(path && path.some((el) => el.tag === landmark || el.role === landmark));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ONLY when exactly one candidate is inside this landmark is it unique
|
|
242
|
+
if (candidatesInLandmark.length === 1) {
|
|
243
|
+
const candidate = candidatesInLandmark[0]!;
|
|
244
|
+
const innerPlan = CandidateExtractor.generateLocatorPlan(candidate);
|
|
245
|
+
const innerExpr = CandidateExtractor.planToExpression(innerPlan, '').replace(/^\./, '');
|
|
246
|
+
const resolvedExpression = `${pageProperty}.locator('${landmark}').${innerExpr}`;
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
strategy: 'LANDMARK_SECTION',
|
|
250
|
+
resolvedCandidate: candidate,
|
|
251
|
+
resolvedExpression,
|
|
252
|
+
matchCount: candidates.length,
|
|
253
|
+
explanation: `Disambiguated via landmark scoping: exactly one target candidate scoped to <${landmark}> based on context.`,
|
|
254
|
+
confidence: 0.88,
|
|
255
|
+
isUnique: true,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private static verifyExplicitPositionalIntent(
|
|
265
|
+
context: DisambiguationContext
|
|
266
|
+
): { method: string; index: number } | null {
|
|
267
|
+
const sel = context.originalSelector ?? '';
|
|
268
|
+
const intent = (context.testIntentText ?? '').toLowerCase();
|
|
269
|
+
|
|
270
|
+
// 1. Check original selector method calls
|
|
271
|
+
if (sel.includes('.first()')) {
|
|
272
|
+
return { method: '.first()', index: 0 };
|
|
273
|
+
}
|
|
274
|
+
if (sel.includes('.last()')) {
|
|
275
|
+
return { method: '.last()', index: -1 };
|
|
276
|
+
}
|
|
277
|
+
const nthMatch = sel.match(/\.nth\((\d+)\)/);
|
|
278
|
+
if (nthMatch && nthMatch[1] !== undefined) {
|
|
279
|
+
const idx = parseInt(nthMatch[1], 10);
|
|
280
|
+
return { method: `.nth(${idx})`, index: idx };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 2. Check explicit test intent text
|
|
284
|
+
if (intent.includes('first ') || intent.includes('1st ') || intent.includes('top item') || intent.includes('top result')) {
|
|
285
|
+
return { method: '.first()', index: 0 };
|
|
286
|
+
}
|
|
287
|
+
if (intent.includes('second ') || intent.includes('2nd ')) {
|
|
288
|
+
return { method: '.nth(1)', index: 1 };
|
|
289
|
+
}
|
|
290
|
+
if (intent.includes('third ') || intent.includes('3rd ')) {
|
|
291
|
+
return { method: '.nth(2)', index: 2 };
|
|
292
|
+
}
|
|
293
|
+
if (intent.includes('last item') || intent.includes('last row')) {
|
|
294
|
+
return { method: '.last()', index: -1 };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private static findPathToElement(
|
|
301
|
+
root: ResolvedElement,
|
|
302
|
+
target: ResolvedElement,
|
|
303
|
+
currentPath: ResolvedElement[] = []
|
|
304
|
+
): ResolvedElement[] | null {
|
|
305
|
+
const nextPath = [...currentPath, root];
|
|
306
|
+
if (root === target) {
|
|
307
|
+
return nextPath;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const child of root.children ?? []) {
|
|
311
|
+
const found = this.findPathToElement(child, target, nextPath);
|
|
312
|
+
if (found) return found;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private static getCleanText(el: ResolvedElement): string {
|
|
319
|
+
if (el.text && el.text.trim().length > 0) {
|
|
320
|
+
return el.text.trim();
|
|
321
|
+
}
|
|
322
|
+
// Collect child text
|
|
323
|
+
const childTexts: string[] = [];
|
|
324
|
+
for (const child of el.children ?? []) {
|
|
325
|
+
if (child.text && child.text.trim().length > 0) {
|
|
326
|
+
childTexts.push(child.text.trim());
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return childTexts.join(' ').trim();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
export type ModelTransport = 'openai-compatible' | 'anthropic-compatible' | 'local' | 'unknown';
|
|
2
|
+
export type StructuredOutputCapability = 'json_schema' | 'json_object' | 'prompt_only' | 'unknown';
|
|
3
|
+
export type ReasoningCapability = 'separate_channel' | 'inline' | 'unsupported' | 'unknown';
|
|
4
|
+
export type ToolCallCapability = 'native' | 'application_protocol' | 'unsupported' | 'unknown';
|
|
5
|
+
|
|
6
|
+
export interface ModelCapabilities {
|
|
7
|
+
transport: ModelTransport;
|
|
8
|
+
structuredOutput: StructuredOutputCapability;
|
|
9
|
+
reasoning: ReasoningCapability;
|
|
10
|
+
toolCalls: ToolCallCapability;
|
|
11
|
+
maxOutputTokens?: number | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ModelUsageEnvelope {
|
|
15
|
+
inputTokens?: number | undefined;
|
|
16
|
+
outputTokens?: number | undefined;
|
|
17
|
+
totalTokens?: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Provider-neutral response shape consumed by PIE's agent protocol. */
|
|
21
|
+
export interface ModelResponseEnvelope {
|
|
22
|
+
finalText?: string | undefined;
|
|
23
|
+
reasoningText?: string | undefined;
|
|
24
|
+
structuredOutput?: unknown;
|
|
25
|
+
toolCalls?: unknown[] | undefined;
|
|
26
|
+
usage?: ModelUsageEnvelope | undefined;
|
|
27
|
+
finishReason?: string | undefined;
|
|
28
|
+
transport: ModelTransport;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ModelRequestKind = 'proposal' | 'agent_step';
|
|
32
|
+
|
|
33
|
+
export interface ModelResponseDiagnostics {
|
|
34
|
+
requestKind: ModelRequestKind;
|
|
35
|
+
transport: ModelTransport;
|
|
36
|
+
topLevelKeys: string[];
|
|
37
|
+
choiceKeys: string[];
|
|
38
|
+
messageKeys: string[];
|
|
39
|
+
contentKind: 'string' | 'array' | 'missing' | 'null';
|
|
40
|
+
hasFinalText: boolean;
|
|
41
|
+
finalTextLength: number;
|
|
42
|
+
reasoningTextLength: number;
|
|
43
|
+
toolCallCount: number;
|
|
44
|
+
finishReason?: string | undefined;
|
|
45
|
+
usagePresent: boolean;
|
|
46
|
+
candidateKeys: string[];
|
|
47
|
+
candidateResponseType?: string | undefined;
|
|
48
|
+
candidateStatus?: string | undefined;
|
|
49
|
+
candidateDecision?: string | undefined;
|
|
50
|
+
candidateActionKind?: string | undefined;
|
|
51
|
+
candidateProposalVersion?: string | undefined;
|
|
52
|
+
/** Transport metadata keys recognized and removed before protocol validation. */
|
|
53
|
+
recognizedResponseMetadataKeys: string[];
|
|
54
|
+
validationErrors: string[];
|
|
55
|
+
normalizationNotes: string[];
|
|
56
|
+
redactedPreview?: string | undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface OpenAiCompatibleResponsePayload {
|
|
60
|
+
choices?: Array<{
|
|
61
|
+
finish_reason?: string;
|
|
62
|
+
message?: {
|
|
63
|
+
content?: string | Array<{ type?: string; text?: string }> | null;
|
|
64
|
+
reasoning_content?: string | null;
|
|
65
|
+
tool_calls?: unknown[];
|
|
66
|
+
};
|
|
67
|
+
}>;
|
|
68
|
+
output_text?: string;
|
|
69
|
+
usage?: ModelUsageEnvelope & {
|
|
70
|
+
prompt_tokens?: number;
|
|
71
|
+
completion_tokens?: number;
|
|
72
|
+
total_tokens?: number;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AnthropicResponsePayload {
|
|
77
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
78
|
+
usage?: { input_tokens?: number; output_tokens?: number };
|
|
79
|
+
stop_reason?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizeOpenAiResponse(payload: OpenAiCompatibleResponsePayload): ModelResponseEnvelope {
|
|
83
|
+
const message = payload.choices?.[0]?.message;
|
|
84
|
+
const content = message?.content;
|
|
85
|
+
const finalText = typeof payload.output_text === 'string'
|
|
86
|
+
? payload.output_text
|
|
87
|
+
: typeof content === 'string'
|
|
88
|
+
? content
|
|
89
|
+
: Array.isArray(content)
|
|
90
|
+
? content.map((part) => part.text ?? '').join('')
|
|
91
|
+
: undefined;
|
|
92
|
+
const inputTokens = payload.usage?.inputTokens ?? payload.usage?.prompt_tokens;
|
|
93
|
+
const outputTokens = payload.usage?.outputTokens ?? payload.usage?.completion_tokens;
|
|
94
|
+
const totalTokens = payload.usage?.totalTokens ?? payload.usage?.total_tokens
|
|
95
|
+
?? (inputTokens === undefined && outputTokens === undefined ? undefined : (inputTokens ?? 0) + (outputTokens ?? 0));
|
|
96
|
+
return {
|
|
97
|
+
finalText: finalText || undefined,
|
|
98
|
+
reasoningText: message?.reasoning_content ?? undefined,
|
|
99
|
+
toolCalls: message?.tool_calls,
|
|
100
|
+
usage: inputTokens === undefined && outputTokens === undefined && totalTokens === undefined
|
|
101
|
+
? undefined
|
|
102
|
+
: { inputTokens, outputTokens, totalTokens },
|
|
103
|
+
finishReason: payload.choices?.[0]?.finish_reason,
|
|
104
|
+
transport: 'openai-compatible',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function normalizeAnthropicResponse(payload: AnthropicResponsePayload): ModelResponseEnvelope {
|
|
109
|
+
const textParts = payload.content?.filter((part) => part.type === 'text').map((part) => part.text ?? '') ?? [];
|
|
110
|
+
const reasoningParts = payload.content?.filter((part) => part.type === 'thinking').map((part) => part.text ?? '') ?? [];
|
|
111
|
+
const inputTokens = payload.usage?.input_tokens;
|
|
112
|
+
const outputTokens = payload.usage?.output_tokens;
|
|
113
|
+
return {
|
|
114
|
+
finalText: textParts.join('') || undefined,
|
|
115
|
+
reasoningText: reasoningParts.join('') || undefined,
|
|
116
|
+
usage: inputTokens === undefined && outputTokens === undefined
|
|
117
|
+
? undefined
|
|
118
|
+
: { inputTokens, outputTokens, totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0) },
|
|
119
|
+
finishReason: payload.stop_reason,
|
|
120
|
+
transport: 'anthropic-compatible',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function redactPreview(value: string): string {
|
|
125
|
+
return value
|
|
126
|
+
.replace(/Bearer\s+[A-Za-z0-9._-]+/gi, 'Bearer [REDACTED]')
|
|
127
|
+
.replace(/(api[_-]?key|token|password|secret)\s*[:=]\s*["']?[^\s,"'}]+/gi, '$1=[REDACTED]')
|
|
128
|
+
.slice(0, 1000);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface CandidateInspection {
|
|
132
|
+
keys: string[];
|
|
133
|
+
responseType?: string | undefined;
|
|
134
|
+
status?: string | undefined;
|
|
135
|
+
decision?: string | undefined;
|
|
136
|
+
actionKind?: string | undefined;
|
|
137
|
+
proposalVersion?: string | undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function inspectCandidate(text: string | undefined): CandidateInspection {
|
|
141
|
+
if (!text) return { keys: [] };
|
|
142
|
+
const fenced = text.trim().match(/```(?:json)?\s*([\s\S]*?)\s*```/i)?.[1];
|
|
143
|
+
const candidateText = (fenced ?? text).trim();
|
|
144
|
+
const start = candidateText.indexOf('{');
|
|
145
|
+
const end = candidateText.lastIndexOf('}');
|
|
146
|
+
if (start < 0 || end <= start) return { keys: [] };
|
|
147
|
+
try {
|
|
148
|
+
const candidate = JSON.parse(candidateText.slice(start, end + 1)) as Record<string, unknown>;
|
|
149
|
+
return {
|
|
150
|
+
keys: Object.keys(candidate),
|
|
151
|
+
responseType: typeof candidate.responseType === 'string' ? candidate.responseType : undefined,
|
|
152
|
+
status: typeof candidate.status === 'string' ? candidate.status : undefined,
|
|
153
|
+
decision: typeof candidate.decision === 'string' ? candidate.decision : undefined,
|
|
154
|
+
actionKind: typeof (candidate.action as Record<string, unknown> | undefined)?.kind === 'string'
|
|
155
|
+
? String((candidate.action as Record<string, unknown>).kind)
|
|
156
|
+
: undefined,
|
|
157
|
+
proposalVersion: typeof candidate.proposalVersion === 'string' ? candidate.proposalVersion : undefined,
|
|
158
|
+
};
|
|
159
|
+
} catch {
|
|
160
|
+
return { keys: [] };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createModelResponseDiagnostics(
|
|
165
|
+
payload: unknown,
|
|
166
|
+
envelope: ModelResponseEnvelope,
|
|
167
|
+
requestKind: ModelRequestKind,
|
|
168
|
+
validationErrors: string[] = [],
|
|
169
|
+
normalizationNotes: string[] = [],
|
|
170
|
+
recognizedResponseMetadataKeys: string[] = [],
|
|
171
|
+
includePreview = false,
|
|
172
|
+
): ModelResponseDiagnostics {
|
|
173
|
+
const record = payload && typeof payload === 'object' ? payload as Record<string, unknown> : {};
|
|
174
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
175
|
+
const firstChoice = choices[0] && typeof choices[0] === 'object' ? choices[0] as Record<string, unknown> : {};
|
|
176
|
+
const message = firstChoice.message && typeof firstChoice.message === 'object' ? firstChoice.message as Record<string, unknown> : {};
|
|
177
|
+
const content = message.content;
|
|
178
|
+
const contentKind = content === null ? 'null' : typeof content === 'string' ? 'string' : Array.isArray(content) ? 'array' : content === undefined ? 'missing' : 'missing';
|
|
179
|
+
const candidate = inspectCandidate(envelope.finalText);
|
|
180
|
+
const diagnostics: ModelResponseDiagnostics = {
|
|
181
|
+
requestKind,
|
|
182
|
+
transport: envelope.transport,
|
|
183
|
+
topLevelKeys: Object.keys(record),
|
|
184
|
+
choiceKeys: Object.keys(firstChoice),
|
|
185
|
+
messageKeys: Object.keys(message),
|
|
186
|
+
contentKind,
|
|
187
|
+
hasFinalText: Boolean(envelope.finalText),
|
|
188
|
+
finalTextLength: envelope.finalText?.length ?? 0,
|
|
189
|
+
reasoningTextLength: envelope.reasoningText?.length ?? 0,
|
|
190
|
+
toolCallCount: envelope.toolCalls?.length ?? 0,
|
|
191
|
+
finishReason: envelope.finishReason,
|
|
192
|
+
usagePresent: envelope.usage !== undefined,
|
|
193
|
+
candidateKeys: candidate.keys,
|
|
194
|
+
candidateResponseType: candidate.responseType,
|
|
195
|
+
candidateStatus: candidate.status,
|
|
196
|
+
candidateDecision: candidate.decision,
|
|
197
|
+
candidateActionKind: candidate.actionKind,
|
|
198
|
+
candidateProposalVersion: candidate.proposalVersion,
|
|
199
|
+
recognizedResponseMetadataKeys,
|
|
200
|
+
validationErrors,
|
|
201
|
+
normalizationNotes,
|
|
202
|
+
};
|
|
203
|
+
if (includePreview && envelope.finalText) diagnostics.redactedPreview = redactPreview(envelope.finalText);
|
|
204
|
+
return diagnostics;
|
|
205
|
+
}
|