@duckcodeailabs/dql-agent 1.13.5 → 1.14.1
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/agent-run-engine.d.ts +79 -4
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +457 -39
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-gates.d.ts.map +1 -1
- package/dist/agent-run-gates.js +13 -0
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agent-run-store.d.ts.map +1 -1
- package/dist/agent-run-store.js +42 -25
- package/dist/agent-run-store.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts +91 -0
- package/dist/agentic/analyst-loop.d.ts.map +1 -0
- package/dist/agentic/analyst-loop.js +311 -0
- package/dist/agentic/analyst-loop.js.map +1 -0
- package/dist/agentic/assumptions.d.ts +62 -0
- package/dist/agentic/assumptions.d.ts.map +1 -0
- package/dist/agentic/assumptions.js +73 -0
- package/dist/agentic/assumptions.js.map +1 -0
- package/dist/agentic/business-explanation.d.ts +56 -0
- package/dist/agentic/business-explanation.d.ts.map +1 -0
- package/dist/agentic/business-explanation.js +130 -0
- package/dist/agentic/business-explanation.js.map +1 -0
- package/dist/agentic/identifier-ledger.d.ts +74 -0
- package/dist/agentic/identifier-ledger.d.ts.map +1 -0
- package/dist/agentic/identifier-ledger.js +151 -0
- package/dist/agentic/identifier-ledger.js.map +1 -0
- package/dist/agentic/ledger-tools.d.ts +68 -0
- package/dist/agentic/ledger-tools.d.ts.map +1 -0
- package/dist/agentic/ledger-tools.js +198 -0
- package/dist/agentic/ledger-tools.js.map +1 -0
- package/dist/agentic/orchestrator-policy.d.ts +60 -0
- package/dist/agentic/orchestrator-policy.d.ts.map +1 -0
- package/dist/agentic/orchestrator-policy.js +73 -0
- package/dist/agentic/orchestrator-policy.js.map +1 -0
- package/dist/agentic/orchestrator.d.ts +54 -0
- package/dist/agentic/orchestrator.d.ts.map +1 -0
- package/dist/agentic/orchestrator.js +67 -0
- package/dist/agentic/orchestrator.js.map +1 -0
- package/dist/agentic/preview-tool.d.ts +36 -0
- package/dist/agentic/preview-tool.d.ts.map +1 -0
- package/dist/agentic/preview-tool.js +71 -0
- package/dist/agentic/preview-tool.js.map +1 -0
- package/dist/agentic/rerank.d.ts +50 -0
- package/dist/agentic/rerank.d.ts.map +1 -0
- package/dist/agentic/rerank.js +140 -0
- package/dist/agentic/rerank.js.map +1 -0
- package/dist/agentic/research/hypothesis.d.ts +110 -0
- package/dist/agentic/research/hypothesis.d.ts.map +1 -0
- package/dist/agentic/research/hypothesis.js +125 -0
- package/dist/agentic/research/hypothesis.js.map +1 -0
- package/dist/agentic/research/synthesis.d.ts +32 -0
- package/dist/agentic/research/synthesis.d.ts.map +1 -0
- package/dist/agentic/research/synthesis.js +94 -0
- package/dist/agentic/research/synthesis.js.map +1 -0
- package/dist/agentic/research-agent.d.ts +54 -0
- package/dist/agentic/research-agent.d.ts.map +1 -0
- package/dist/agentic/research-agent.js +176 -0
- package/dist/agentic/research-agent.js.map +1 -0
- package/dist/agentic/sql-authorization.d.ts +131 -0
- package/dist/agentic/sql-authorization.d.ts.map +1 -0
- package/dist/agentic/sql-authorization.js +232 -0
- package/dist/agentic/sql-authorization.js.map +1 -0
- package/dist/agentic/tool-loop.d.ts +29 -0
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +129 -52
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/agentic/toolset.d.ts.map +1 -1
- package/dist/agentic/toolset.js +155 -0
- package/dist/agentic/toolset.js.map +1 -1
- package/dist/agentic/turn-plan.d.ts +35 -0
- package/dist/agentic/turn-plan.d.ts.map +1 -0
- package/dist/agentic/turn-plan.js +108 -0
- package/dist/agentic/turn-plan.js.map +1 -0
- package/dist/agentic/value-probe.d.ts +89 -0
- package/dist/agentic/value-probe.d.ts.map +1 -0
- package/dist/agentic/value-probe.js +304 -0
- package/dist/agentic/value-probe.js.map +1 -0
- package/dist/analytical-orchestration.d.ts +336 -0
- package/dist/analytical-orchestration.d.ts.map +1 -0
- package/dist/analytical-orchestration.js +631 -0
- package/dist/analytical-orchestration.js.map +1 -0
- package/dist/analytical-request-policy.d.ts +26 -0
- package/dist/analytical-request-policy.d.ts.map +1 -0
- package/dist/analytical-request-policy.js +82 -0
- package/dist/analytical-request-policy.js.map +1 -0
- package/dist/analytical-result-facts.d.ts +14 -1
- package/dist/analytical-result-facts.d.ts.map +1 -1
- package/dist/analytical-result-facts.js +76 -1
- package/dist/analytical-result-facts.js.map +1 -1
- package/dist/answer-loop.d.ts +61 -16
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +256 -36
- package/dist/answer-loop.js.map +1 -1
- package/dist/cascade/budgets.d.ts.map +1 -1
- package/dist/cascade/budgets.js +8 -1
- package/dist/cascade/budgets.js.map +1 -1
- package/dist/cascade/packer.d.ts +88 -0
- package/dist/cascade/packer.d.ts.map +1 -0
- package/dist/cascade/packer.js +106 -0
- package/dist/cascade/packer.js.map +1 -0
- package/dist/conversation/result-ops.d.ts +18 -3
- package/dist/conversation/result-ops.d.ts.map +1 -1
- package/dist/conversation/result-ops.js +77 -5
- package/dist/conversation/result-ops.js.map +1 -1
- package/dist/conversation/rolling-summary.d.ts.map +1 -1
- package/dist/conversation/rolling-summary.js +7 -2
- package/dist/conversation/rolling-summary.js.map +1 -1
- package/dist/conversation/session-store.d.ts +3 -0
- package/dist/conversation/session-store.d.ts.map +1 -1
- package/dist/conversation/session-store.js +8 -4
- package/dist/conversation/session-store.js.map +1 -1
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +13 -1
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/conversation/turn-trust.d.ts.map +1 -1
- package/dist/conversation/turn-trust.js +6 -0
- package/dist/conversation/turn-trust.js.map +1 -1
- package/dist/hints/store.d.ts.map +1 -1
- package/dist/hints/store.js +15 -5
- package/dist/hints/store.js.map +1 -1
- package/dist/index.d.ts +27 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -4
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +8 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +65 -0
- package/dist/intent-controller.js.map +1 -1
- package/dist/meaning-resolution.d.ts +7 -0
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/block-fit.d.ts +18 -0
- package/dist/metadata/block-fit.d.ts.map +1 -1
- package/dist/metadata/block-fit.js +41 -1
- package/dist/metadata/block-fit.js.map +1 -1
- package/dist/metadata/catalog.d.ts +44 -23
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +363 -53
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +101 -8
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/metadata/metric-match.d.ts +32 -0
- package/dist/metadata/metric-match.d.ts.map +1 -1
- package/dist/metadata/metric-match.js +120 -9
- package/dist/metadata/metric-match.js.map +1 -1
- package/dist/research-loop.d.ts +33 -2
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +44 -4
- package/dist/research-loop.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +92 -5
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +36 -16
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +604 -25
- package/dist/router.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact generated-SQL execution authorization.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately a server-only, in-memory capability. It is created by
|
|
5
|
+
* the analyst loop, passed through one call chain, and consumed by the local
|
|
6
|
+
* execution host. It is never an artifact, response field, cache entry, or
|
|
7
|
+
* bearer token.
|
|
8
|
+
*/
|
|
9
|
+
export type IdentifierEvidence = 'compiler' | 'preview' | 'schema_tool' | 'catalog';
|
|
10
|
+
export interface ExecutionAuthorizationBindingV1 {
|
|
11
|
+
runId: string;
|
|
12
|
+
executionId: string;
|
|
13
|
+
snapshotId: string;
|
|
14
|
+
/** The router plan that selected the target, when a plan exists. */
|
|
15
|
+
planId?: string;
|
|
16
|
+
/** The server-observed physical execution target. */
|
|
17
|
+
targetFingerprint?: string;
|
|
18
|
+
/** Canonical host bindings, never raw parameter values. */
|
|
19
|
+
bindingsFingerprint: string;
|
|
20
|
+
}
|
|
21
|
+
export interface FinalSqlAuthorizationV1 extends ExecutionAuthorizationBindingV1 {
|
|
22
|
+
version: 1;
|
|
23
|
+
/** Fully-qualified identities proven during this exact loop. */
|
|
24
|
+
provenIdentifiers: string[];
|
|
25
|
+
/** Catalog evidence informs retrieval only and never authorizes execution. */
|
|
26
|
+
evidence: Record<string, IdentifierEvidence>;
|
|
27
|
+
/** SHA-256 of the exact prepared bytes sent to the connector. */
|
|
28
|
+
sqlFingerprint: string;
|
|
29
|
+
mintedAt: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Opaque handoff from the analyst loop to its host execution closure. Do not
|
|
33
|
+
* persist or surface this type; its only valid lifetime is one request/child.
|
|
34
|
+
*/
|
|
35
|
+
export interface AgenticSqlExecutionCapabilityV1 extends Omit<ExecutionAuthorizationBindingV1, 'planId' | 'targetFingerprint'> {
|
|
36
|
+
version: 1;
|
|
37
|
+
/** Generated execution never proceeds without a frozen plan and target. */
|
|
38
|
+
planId: string;
|
|
39
|
+
targetFingerprint: string;
|
|
40
|
+
candidateSqlFingerprint: string;
|
|
41
|
+
provenIdentifiers: string[];
|
|
42
|
+
evidence: Record<string, IdentifierEvidence>;
|
|
43
|
+
}
|
|
44
|
+
export interface SqlAuthorizationVerdict {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
unproven: string[];
|
|
47
|
+
drifted: boolean;
|
|
48
|
+
reason?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface SqlAuthorizationCheck {
|
|
51
|
+
bindings?: unknown;
|
|
52
|
+
runId?: string;
|
|
53
|
+
executionId?: string;
|
|
54
|
+
snapshotId?: string;
|
|
55
|
+
planId?: string;
|
|
56
|
+
targetFingerprint?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Per-invocation, server-only one-shot gate for analyst capabilities.
|
|
60
|
+
*
|
|
61
|
+
* The host creates one gate for one answer/compound child. It has no registry,
|
|
62
|
+
* static state, persistence, or bearer representation; it merely prevents the
|
|
63
|
+
* exact same capability from crossing the physical execution boundary twice.
|
|
64
|
+
*/
|
|
65
|
+
export declare class AgenticExecutionCapabilityGate {
|
|
66
|
+
private readonly consumed;
|
|
67
|
+
consume(capability: Pick<AgenticSqlExecutionCapabilityV1, 'runId' | 'executionId'>): boolean;
|
|
68
|
+
}
|
|
69
|
+
/** Parsed references supplied by the established SQL validator. */
|
|
70
|
+
export interface SqlAuthorizationReferences {
|
|
71
|
+
relations?: readonly string[];
|
|
72
|
+
columns?: ReadonlyArray<{
|
|
73
|
+
relation?: string;
|
|
74
|
+
column: string;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Historical export retained for callers. No SQL rewriting is permitted at
|
|
79
|
+
* this boundary: lowercasing or stripping comments changes string literals and
|
|
80
|
+
* can turn two different queries into the same authorization fingerprint.
|
|
81
|
+
*/
|
|
82
|
+
export declare function normalizeSqlForFingerprint(sql: string): string;
|
|
83
|
+
/** Hash exact SQL bytes. Formatting drift is execution drift here. */
|
|
84
|
+
export declare function fingerprintSql(sql: string): string;
|
|
85
|
+
/** Stable, redacted fingerprint for positional/named bindings. */
|
|
86
|
+
export declare function fingerprintSqlBindings(bindings: unknown): string;
|
|
87
|
+
export declare function createAgenticSqlExecutionCapability(input: {
|
|
88
|
+
sql: string;
|
|
89
|
+
proven: ReadonlyArray<{
|
|
90
|
+
identifier: string;
|
|
91
|
+
evidence: IdentifierEvidence;
|
|
92
|
+
}>;
|
|
93
|
+
runId?: string;
|
|
94
|
+
executionId?: string;
|
|
95
|
+
snapshotId?: string;
|
|
96
|
+
planId?: string;
|
|
97
|
+
targetFingerprint?: string;
|
|
98
|
+
bindings?: unknown;
|
|
99
|
+
}): AgenticSqlExecutionCapabilityV1 | undefined;
|
|
100
|
+
export declare function mintFinalSqlAuthorization(input: {
|
|
101
|
+
sql: string;
|
|
102
|
+
proven: ReadonlyArray<{
|
|
103
|
+
identifier: string;
|
|
104
|
+
evidence: IdentifierEvidence;
|
|
105
|
+
}>;
|
|
106
|
+
runId?: string;
|
|
107
|
+
executionId?: string;
|
|
108
|
+
snapshotId?: string;
|
|
109
|
+
planId?: string;
|
|
110
|
+
targetFingerprint?: string;
|
|
111
|
+
bindings?: unknown;
|
|
112
|
+
now?: () => Date;
|
|
113
|
+
}): FinalSqlAuthorizationV1;
|
|
114
|
+
/**
|
|
115
|
+
* Validate that the host is still executing the precise analyst proposal before
|
|
116
|
+
* preparation. The subsequent final authorization binds the prepared bytes.
|
|
117
|
+
*/
|
|
118
|
+
export declare function verifyAgenticSqlExecutionCapability(capability: AgenticSqlExecutionCapabilityV1, candidateSql: string, check?: SqlAuthorizationCheck): SqlAuthorizationVerdict;
|
|
119
|
+
/**
|
|
120
|
+
* Does the exact prepared statement reference only observed, non-catalog
|
|
121
|
+
* identities and still match its run/target/binding authority?
|
|
122
|
+
*/
|
|
123
|
+
export declare function verifyFinalSql(authorization: FinalSqlAuthorizationV1, sql: string, referenced: ReadonlyArray<string>, check?: SqlAuthorizationCheck): SqlAuthorizationVerdict;
|
|
124
|
+
/**
|
|
125
|
+
* Convert validator references into fully-qualified execution identities using
|
|
126
|
+
* only aliases declared inside the same SQL statement. This is not a leaf-name
|
|
127
|
+
* fallback: an unqualified column in a multi-relation query stays unqualified
|
|
128
|
+
* and is therefore refused by the capability check.
|
|
129
|
+
*/
|
|
130
|
+
export declare function qualifyAuthorizationReferences(sql: string, references: SqlAuthorizationReferences): string[];
|
|
131
|
+
//# sourceMappingURL=sql-authorization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-authorization.d.ts","sourceRoot":"","sources":["../../src/agentic/sql-authorization.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact generated-SQL execution authorization.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately a server-only, in-memory capability. It is created by
|
|
5
|
+
* the analyst loop, passed through one call chain, and consumed by the local
|
|
6
|
+
* execution host. It is never an artifact, response field, cache entry, or
|
|
7
|
+
* bearer token.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
10
|
+
/**
|
|
11
|
+
* Per-invocation, server-only one-shot gate for analyst capabilities.
|
|
12
|
+
*
|
|
13
|
+
* The host creates one gate for one answer/compound child. It has no registry,
|
|
14
|
+
* static state, persistence, or bearer representation; it merely prevents the
|
|
15
|
+
* exact same capability from crossing the physical execution boundary twice.
|
|
16
|
+
*/
|
|
17
|
+
export class AgenticExecutionCapabilityGate {
|
|
18
|
+
consumed = new Set();
|
|
19
|
+
consume(capability) {
|
|
20
|
+
const identity = `${capability.runId}\u0000${capability.executionId}`;
|
|
21
|
+
if (this.consumed.has(identity))
|
|
22
|
+
return false;
|
|
23
|
+
this.consumed.add(identity);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Historical export retained for callers. No SQL rewriting is permitted at
|
|
29
|
+
* this boundary: lowercasing or stripping comments changes string literals and
|
|
30
|
+
* can turn two different queries into the same authorization fingerprint.
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeSqlForFingerprint(sql) {
|
|
33
|
+
return sql;
|
|
34
|
+
}
|
|
35
|
+
/** Hash exact SQL bytes. Formatting drift is execution drift here. */
|
|
36
|
+
export function fingerprintSql(sql) {
|
|
37
|
+
return createHash('sha256').update(sql, 'utf8').digest('hex').slice(0, 32);
|
|
38
|
+
}
|
|
39
|
+
/** Stable, redacted fingerprint for positional/named bindings. */
|
|
40
|
+
export function fingerprintSqlBindings(bindings) {
|
|
41
|
+
return createHash('sha256').update(stableBindingsValue(bindings), 'utf8').digest('hex').slice(0, 32);
|
|
42
|
+
}
|
|
43
|
+
export function createAgenticSqlExecutionCapability(input) {
|
|
44
|
+
const runId = requiredCapabilityIdentity(input.runId);
|
|
45
|
+
const snapshotId = requiredCapabilityIdentity(input.snapshotId);
|
|
46
|
+
const planId = requiredCapabilityIdentity(input.planId);
|
|
47
|
+
const targetFingerprint = requiredCapabilityIdentity(input.targetFingerprint);
|
|
48
|
+
if (!runId || !snapshotId || !planId || !targetFingerprint)
|
|
49
|
+
return undefined;
|
|
50
|
+
const normalizedProof = normalizeProof(input.proven);
|
|
51
|
+
return {
|
|
52
|
+
version: 1,
|
|
53
|
+
runId,
|
|
54
|
+
executionId: input.executionId?.trim() || randomUUID(),
|
|
55
|
+
snapshotId,
|
|
56
|
+
planId,
|
|
57
|
+
targetFingerprint,
|
|
58
|
+
bindingsFingerprint: fingerprintSqlBindings(input.bindings ?? {}),
|
|
59
|
+
candidateSqlFingerprint: fingerprintSql(input.sql),
|
|
60
|
+
...normalizedProof,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function mintFinalSqlAuthorization(input) {
|
|
64
|
+
const normalizedProof = normalizeProof(input.proven);
|
|
65
|
+
return {
|
|
66
|
+
version: 1,
|
|
67
|
+
runId: requiredIdentity(input.runId, 'run'),
|
|
68
|
+
executionId: input.executionId?.trim() || randomUUID(),
|
|
69
|
+
snapshotId: requiredIdentity(input.snapshotId, 'snapshot'),
|
|
70
|
+
...(input.planId?.trim() ? { planId: input.planId.trim() } : {}),
|
|
71
|
+
...(input.targetFingerprint?.trim() ? { targetFingerprint: input.targetFingerprint.trim() } : {}),
|
|
72
|
+
bindingsFingerprint: fingerprintSqlBindings(input.bindings ?? {}),
|
|
73
|
+
sqlFingerprint: fingerprintSql(input.sql),
|
|
74
|
+
...normalizedProof,
|
|
75
|
+
mintedAt: (input.now?.() ?? new Date()).toISOString(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate that the host is still executing the precise analyst proposal before
|
|
80
|
+
* preparation. The subsequent final authorization binds the prepared bytes.
|
|
81
|
+
*/
|
|
82
|
+
export function verifyAgenticSqlExecutionCapability(capability, candidateSql, check = {}) {
|
|
83
|
+
if (fingerprintSql(candidateSql) !== capability.candidateSqlFingerprint) {
|
|
84
|
+
return failure('The generated SQL differs from the analyst-approved proposal.', true);
|
|
85
|
+
}
|
|
86
|
+
return verifyBinding(capability, check);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Does the exact prepared statement reference only observed, non-catalog
|
|
90
|
+
* identities and still match its run/target/binding authority?
|
|
91
|
+
*/
|
|
92
|
+
export function verifyFinalSql(authorization, sql, referenced, check = {}) {
|
|
93
|
+
if (fingerprintSql(sql) !== authorization.sqlFingerprint) {
|
|
94
|
+
return failure('The statement about to execute is not the one that was authorized.', true);
|
|
95
|
+
}
|
|
96
|
+
const binding = verifyBinding(authorization, check);
|
|
97
|
+
if (!binding.ok)
|
|
98
|
+
return binding;
|
|
99
|
+
const executable = new Set(authorization.provenIdentifiers.filter((id) => authorization.evidence[id] !== 'catalog'));
|
|
100
|
+
const unproven = [...new Set(referenced
|
|
101
|
+
.map(normalizeQualifiedIdentifier)
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
// No leaf fallback. `warehouse_a.orders.id` and `warehouse_b.orders.id`
|
|
104
|
+
// are distinct capabilities even when their last component is the same.
|
|
105
|
+
.filter((name) => !executable.has(name)))];
|
|
106
|
+
if (unproven.length > 0) {
|
|
107
|
+
return { ok: false, unproven, drifted: false, reason: `Nothing in this run proved: ${unproven.join(', ')}.` };
|
|
108
|
+
}
|
|
109
|
+
return { ok: true, unproven: [], drifted: false };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Convert validator references into fully-qualified execution identities using
|
|
113
|
+
* only aliases declared inside the same SQL statement. This is not a leaf-name
|
|
114
|
+
* fallback: an unqualified column in a multi-relation query stays unqualified
|
|
115
|
+
* and is therefore refused by the capability check.
|
|
116
|
+
*/
|
|
117
|
+
export function qualifyAuthorizationReferences(sql, references) {
|
|
118
|
+
const relations = [...new Set((references.relations ?? []).filter(Boolean))];
|
|
119
|
+
const aliases = relationAliasesInSql(sql);
|
|
120
|
+
const byNormalizedRelation = new Map(relations.map((relation) => [normalizeQualifiedIdentifier(relation), relation]));
|
|
121
|
+
const resolveRelation = (relation) => {
|
|
122
|
+
if (relation) {
|
|
123
|
+
const normalized = normalizeQualifiedIdentifier(relation);
|
|
124
|
+
return aliases.get(normalized) ?? byNormalizedRelation.get(normalized);
|
|
125
|
+
}
|
|
126
|
+
return relations.length === 1 ? relations[0] : undefined;
|
|
127
|
+
};
|
|
128
|
+
return [
|
|
129
|
+
...relations,
|
|
130
|
+
...(references.columns ?? []).map((column) => {
|
|
131
|
+
const relation = resolveRelation(column.relation);
|
|
132
|
+
return relation ? `${relation}.${column.column}` : column.column;
|
|
133
|
+
}),
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
function normalizeProof(proven) {
|
|
137
|
+
const evidence = {};
|
|
138
|
+
for (const entry of proven) {
|
|
139
|
+
const key = normalizeQualifiedIdentifier(entry.identifier);
|
|
140
|
+
if (!key)
|
|
141
|
+
continue;
|
|
142
|
+
const existing = evidence[key];
|
|
143
|
+
if (!existing || evidenceRank(entry.evidence) > evidenceRank(existing))
|
|
144
|
+
evidence[key] = entry.evidence;
|
|
145
|
+
}
|
|
146
|
+
return { evidence, provenIdentifiers: Object.keys(evidence).sort() };
|
|
147
|
+
}
|
|
148
|
+
function verifyBinding(authority, check) {
|
|
149
|
+
if (check.bindings !== undefined && fingerprintSqlBindings(check.bindings) !== authority.bindingsFingerprint) {
|
|
150
|
+
return failure('The SQL bindings differ from the authorized execution.', true);
|
|
151
|
+
}
|
|
152
|
+
for (const field of ['runId', 'executionId', 'snapshotId', 'planId', 'targetFingerprint']) {
|
|
153
|
+
const expected = check[field];
|
|
154
|
+
const actual = authority[field];
|
|
155
|
+
if (expected !== undefined && expected !== actual) {
|
|
156
|
+
return failure(`The execution ${field} does not match the authorized request.`, true);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { ok: true, unproven: [], drifted: false };
|
|
160
|
+
}
|
|
161
|
+
function failure(reason, drifted) {
|
|
162
|
+
return { ok: false, unproven: [], drifted, reason };
|
|
163
|
+
}
|
|
164
|
+
function requiredIdentity(value, label) {
|
|
165
|
+
const normalized = value?.trim();
|
|
166
|
+
// A runless host cannot accidentally turn a global/default execution into a
|
|
167
|
+
// valid capability. The random fallback is isolated per invocation, never
|
|
168
|
+
// shared in a registry, and is useful only inside the supplied closure.
|
|
169
|
+
return normalized || `${label}:${randomUUID()}`;
|
|
170
|
+
}
|
|
171
|
+
/** Missing generated-execution scope is a fail-closed condition, not a value to mint. */
|
|
172
|
+
function requiredCapabilityIdentity(value) {
|
|
173
|
+
const normalized = value?.trim();
|
|
174
|
+
return normalized || undefined;
|
|
175
|
+
}
|
|
176
|
+
function normalizeQualifiedIdentifier(value) {
|
|
177
|
+
return value
|
|
178
|
+
.trim()
|
|
179
|
+
.split('.')
|
|
180
|
+
.map((part) => part.trim().replace(/^["`\[]|["`\]]$/g, '').toLowerCase())
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.join('.');
|
|
183
|
+
}
|
|
184
|
+
function relationAliasesInSql(sql) {
|
|
185
|
+
const aliases = new Map();
|
|
186
|
+
const stopwords = new Set(['where', 'group', 'order', 'limit', 'having', 'join', 'left', 'right', 'inner', 'outer', 'full', 'cross', 'on', 'union']);
|
|
187
|
+
// The validator remains the authority for SQL parsing. This small parser only
|
|
188
|
+
// maps a parser-reported alias back to its exact relation spelling, and does
|
|
189
|
+
// not try to infer a relation from the last segment of an identifier.
|
|
190
|
+
const relation = '(?:"[^"]+"|`[^`]+`|\\[[^\\]]+\\]|[A-Za-z_][A-Za-z0-9_$]*)(?:\\s*\\.\\s*(?:"[^"]+"|`[^`]+`|\\[[^\\]]+\\]|[A-Za-z_][A-Za-z0-9_$]*))*';
|
|
191
|
+
const matcher = new RegExp(`\\b(?:from|join)\\s+(${relation})(?:\\s+(?:as\\s+)?([A-Za-z_][A-Za-z0-9_$]*))?`, 'gi');
|
|
192
|
+
for (const match of sql.matchAll(matcher)) {
|
|
193
|
+
const source = match[1]?.trim();
|
|
194
|
+
const alias = match[2]?.trim();
|
|
195
|
+
if (!source)
|
|
196
|
+
continue;
|
|
197
|
+
aliases.set(normalizeQualifiedIdentifier(source), source);
|
|
198
|
+
if (alias && !stopwords.has(alias.toLowerCase()))
|
|
199
|
+
aliases.set(normalizeQualifiedIdentifier(alias), source);
|
|
200
|
+
}
|
|
201
|
+
return aliases;
|
|
202
|
+
}
|
|
203
|
+
function evidenceRank(source) {
|
|
204
|
+
switch (source) {
|
|
205
|
+
case 'compiler': return 3;
|
|
206
|
+
case 'preview': return 2;
|
|
207
|
+
case 'schema_tool': return 1;
|
|
208
|
+
case 'catalog': return 0;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function stableBindingsValue(value) {
|
|
212
|
+
if (value === null)
|
|
213
|
+
return 'null';
|
|
214
|
+
if (value === undefined)
|
|
215
|
+
return 'undefined';
|
|
216
|
+
if (typeof value === 'string')
|
|
217
|
+
return JSON.stringify(value);
|
|
218
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
219
|
+
return JSON.stringify(value);
|
|
220
|
+
if (typeof value === 'bigint')
|
|
221
|
+
return JSON.stringify(value.toString());
|
|
222
|
+
if (value instanceof Date)
|
|
223
|
+
return JSON.stringify(value.toISOString());
|
|
224
|
+
if (Array.isArray(value))
|
|
225
|
+
return `[${value.map(stableBindingsValue).join(',')}]`;
|
|
226
|
+
if (typeof value === 'object') {
|
|
227
|
+
const record = value;
|
|
228
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableBindingsValue(record[key])}`).join(',')}}`;
|
|
229
|
+
}
|
|
230
|
+
return JSON.stringify(String(value));
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=sql-authorization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-authorization.js","sourceRoot":"","sources":["../../src/agentic/sql-authorization.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,35 @@ export interface AgenticToolLoopOptions extends ProviderToolLoopOptions {
|
|
|
33
33
|
* providers already use), so callers can record evidence regardless of transport.
|
|
34
34
|
*/
|
|
35
35
|
export declare function runAgenticToolLoop(provider: AgentProvider, messages: AgentMessage[], tools: AgentToolDefinition[], options?: AgenticToolLoopOptions): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Run the agentic loop while retaining its terminal reason for internal
|
|
38
|
+
* callers. The public `runAgenticToolLoop` compatibility API remains a string,
|
|
39
|
+
* but an analytical caller must not mistake a budget-stopped tool request for
|
|
40
|
+
* final SQL.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runAgenticToolLoopDetailed(provider: AgentProvider, messages: AgentMessage[], tools: AgentToolDefinition[], options?: AgenticToolLoopOptions): Promise<TextToolLoopResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Internal terminal state for the text transport.
|
|
45
|
+
*
|
|
46
|
+
* The public compatibility API above intentionally remains `Promise<string>`:
|
|
47
|
+
* callers outside the analyst lane use it as a normal completion helper. The
|
|
48
|
+
* text transport itself must not, however, confuse "the model asked for a
|
|
49
|
+
* tool after its budget" with a normal final answer. Keeping that distinction
|
|
50
|
+
* here makes a caller able to turn it into a typed, non-executing outcome.
|
|
51
|
+
*/
|
|
52
|
+
export interface TextToolLoopResult {
|
|
53
|
+
text: string;
|
|
54
|
+
stop: 'final' | 'tool_budget_exhausted' | 'provider_dispatch_budget_exhausted';
|
|
55
|
+
toolCalls: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Text-only providers do not have a server-side tool protocol, so DQL drives a
|
|
59
|
+
* real bounded conversation: model -> one tool -> observation -> model. One
|
|
60
|
+
* physical dispatch is always reserved for a final answer. This mirrors native
|
|
61
|
+
* tool use and prevents the old one-tool implementation from collecting schema
|
|
62
|
+
* evidence and then forcing a premature, SQL-less completion.
|
|
63
|
+
*/
|
|
64
|
+
export declare function runTextProtocolToolLoopDetailed(provider: AgentProvider, baseMessages: AgentMessage[], tools: AgentToolDefinition[], options: AgenticToolLoopOptions): Promise<TextToolLoopResult>;
|
|
36
65
|
interface TextToolCall {
|
|
37
66
|
name: string;
|
|
38
67
|
input?: Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-loop.d.ts","sourceRoot":"","sources":["../../src/agentic/tool-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACrE;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,YAAY,EAAE,EACxB,KAAK,EAAE,mBAAmB,EAAE,EAC5B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"tool-loop.d.ts","sourceRoot":"","sources":["../../src/agentic/tool-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACrE;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,YAAY,EAAE,EACxB,KAAK,EAAE,mBAAmB,EAAE,EAC5B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,YAAY,EAAE,EACxB,KAAK,EAAE,mBAAmB,EAAE,EAC5B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,kBAAkB,CAAC,CAmC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,uBAAuB,GAAG,oCAAoC,CAAC;IAC/E,SAAS,EAAE,MAAM,CAAC;CACnB;AA+CD;;;;;;GAMG;AACH,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,aAAa,EACvB,YAAY,EAAE,YAAY,EAAE,EAC5B,KAAK,EAAE,mBAAmB,EAAE,EAC5B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CA+G7B;AAUD,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAsBvE"}
|
|
@@ -26,6 +26,15 @@ import { assertProviderPayloadAllowed, boundProviderResultRows } from '../provid
|
|
|
26
26
|
* providers already use), so callers can record evidence regardless of transport.
|
|
27
27
|
*/
|
|
28
28
|
export async function runAgenticToolLoop(provider, messages, tools, options = {}) {
|
|
29
|
+
return (await runAgenticToolLoopDetailed(provider, messages, tools, options)).text;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run the agentic loop while retaining its terminal reason for internal
|
|
33
|
+
* callers. The public `runAgenticToolLoop` compatibility API remains a string,
|
|
34
|
+
* but an analytical caller must not mistake a budget-stopped tool request for
|
|
35
|
+
* final SQL.
|
|
36
|
+
*/
|
|
37
|
+
export async function runAgenticToolLoopDetailed(provider, messages, tools, options = {}) {
|
|
29
38
|
const resultRowBudgetUsage = new Map();
|
|
30
39
|
const usable = tools
|
|
31
40
|
.filter((tool) => tool.name && tool.description)
|
|
@@ -34,13 +43,41 @@ export async function runAgenticToolLoop(provider, messages, tools, options = {}
|
|
|
34
43
|
? [{ role: 'system', content: options.toolPolicy }]
|
|
35
44
|
: [];
|
|
36
45
|
if (usable.length === 0) {
|
|
37
|
-
|
|
46
|
+
try {
|
|
47
|
+
return { text: await provider.generate([...messages, ...policyMessages], options), stop: 'final', toolCalls: 0 };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const terminal = providerDispatchTerminal(error);
|
|
51
|
+
if (terminal)
|
|
52
|
+
return terminal;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
38
55
|
}
|
|
39
56
|
// Native tool use owns its own loop; hand it the same policy + tools.
|
|
40
57
|
if (provider.generateWithTools) {
|
|
41
|
-
|
|
58
|
+
try {
|
|
59
|
+
return {
|
|
60
|
+
text: await provider.generateWithTools([...messages, ...policyMessages], usable, options),
|
|
61
|
+
stop: 'final',
|
|
62
|
+
toolCalls: 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const terminal = providerDispatchTerminal(error);
|
|
67
|
+
if (terminal)
|
|
68
|
+
return terminal;
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
42
71
|
}
|
|
43
|
-
return
|
|
72
|
+
return runTextProtocolToolLoopDetailed(provider, [...messages, ...policyMessages], usable, options);
|
|
73
|
+
}
|
|
74
|
+
function providerDispatchTerminal(error) {
|
|
75
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
76
|
+
? error.code
|
|
77
|
+
: undefined;
|
|
78
|
+
return code === 'PROVIDER_DISPATCH_BUDGET_EXHAUSTED'
|
|
79
|
+
? { text: '', stop: 'provider_dispatch_budget_exhausted', toolCalls: 0 }
|
|
80
|
+
: undefined;
|
|
44
81
|
}
|
|
45
82
|
function guardToolOutput(tool, options, resultRowBudgetUsage) {
|
|
46
83
|
const policy = options.providerPayloadGuard;
|
|
@@ -75,19 +112,25 @@ function guardToolOutput(tool, options, resultRowBudgetUsage) {
|
|
|
75
112
|
};
|
|
76
113
|
}
|
|
77
114
|
/**
|
|
78
|
-
* Text-
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
115
|
+
* Text-only providers do not have a server-side tool protocol, so DQL drives a
|
|
116
|
+
* real bounded conversation: model -> one tool -> observation -> model. One
|
|
117
|
+
* physical dispatch is always reserved for a final answer. This mirrors native
|
|
118
|
+
* tool use and prevents the old one-tool implementation from collecting schema
|
|
119
|
+
* evidence and then forcing a premature, SQL-less completion.
|
|
82
120
|
*/
|
|
83
|
-
async function
|
|
121
|
+
export async function runTextProtocolToolLoopDetailed(provider, baseMessages, tools, options) {
|
|
84
122
|
const maxToolCalls = Math.max(0, Math.min(30, options.maxToolCalls ?? 8));
|
|
85
123
|
const toolMap = new Map(tools.map((tool) => [tool.name, tool]));
|
|
86
124
|
// Preserve the complete transport contract across both text-protocol turns.
|
|
87
125
|
// Reconstructing a small options subset silently dropped the physical-send
|
|
88
126
|
// observer, dispatch cap, redaction guard, and egress accounting for CLI and
|
|
89
127
|
// Ollama providers.
|
|
90
|
-
|
|
128
|
+
// A tool turn without a following answer is not useful. When callers do
|
|
129
|
+
// not set a dispatch cap, make room for every permitted tool plus the final
|
|
130
|
+
// composition turn. When they do set one, reserve its last slot for that
|
|
131
|
+
// composition instead of silently spending it on another observation.
|
|
132
|
+
const dispatchLimit = Math.max(1, Math.min(30, options.maxProviderDispatches ?? (maxToolCalls + 1)));
|
|
133
|
+
const effectiveToolBudget = Math.min(maxToolCalls, Math.max(0, dispatchLimit - 1));
|
|
91
134
|
let physicalDispatches = 0;
|
|
92
135
|
const outerObserver = options.onProviderDispatch;
|
|
93
136
|
const runOptions = {
|
|
@@ -102,56 +145,90 @@ async function runTextProtocolToolLoop(provider, baseMessages, tools, options) {
|
|
|
102
145
|
};
|
|
103
146
|
const messages = [
|
|
104
147
|
...baseMessages,
|
|
105
|
-
|
|
148
|
+
// Tell the model the *effective* ceiling, not a larger policy cap that
|
|
149
|
+
// cannot physically leave room for its final response.
|
|
150
|
+
{ role: 'system', content: buildTextToolContract(tools, effectiveToolBudget) },
|
|
106
151
|
];
|
|
107
152
|
let lastText = '';
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const call = maxToolCalls > 0 ? parseTextToolCall(text) : undefined;
|
|
112
|
-
if (!call)
|
|
113
|
-
return text || lastText;
|
|
114
|
-
const tool = toolMap.get(call.name);
|
|
115
|
-
let output;
|
|
116
|
-
let isError = false;
|
|
117
|
-
const startedAt = Date.now();
|
|
118
|
-
if (!tool) {
|
|
119
|
-
output = { error: `Unknown tool: ${call.name}. Available: ${tools.map((t) => t.name).join(', ')}` };
|
|
120
|
-
isError = true;
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
153
|
+
let toolCalls = 0;
|
|
154
|
+
while (true) {
|
|
155
|
+
let text;
|
|
123
156
|
try {
|
|
124
|
-
|
|
125
|
-
output = await tool.run(call.input ?? {});
|
|
157
|
+
text = await provider.generate(messages, runOptions);
|
|
126
158
|
}
|
|
127
|
-
catch (
|
|
128
|
-
|
|
159
|
+
catch (error) {
|
|
160
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
161
|
+
? error.code
|
|
162
|
+
: undefined;
|
|
163
|
+
if (code === 'PROVIDER_DISPATCH_BUDGET_EXHAUSTED') {
|
|
164
|
+
return { text: lastText, stop: 'provider_dispatch_budget_exhausted', toolCalls };
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
if (text.trim())
|
|
169
|
+
lastText = text;
|
|
170
|
+
const requestedCall = parseTextToolCall(text);
|
|
171
|
+
if (!requestedCall)
|
|
172
|
+
return { text: text || lastText, stop: 'final', toolCalls };
|
|
173
|
+
// A tool-shaped reply is not a final answer merely because the host has no
|
|
174
|
+
// dispatch left for another observation. Keep this typed distinction so a
|
|
175
|
+
// caller cannot mistake it for executable SQL or prose.
|
|
176
|
+
if (toolCalls >= effectiveToolBudget) {
|
|
177
|
+
return { text: text || lastText, stop: 'tool_budget_exhausted', toolCalls };
|
|
178
|
+
}
|
|
179
|
+
const call = requestedCall;
|
|
180
|
+
const tool = toolMap.get(call.name);
|
|
181
|
+
let output;
|
|
182
|
+
let isError = false;
|
|
183
|
+
const startedAt = Date.now();
|
|
184
|
+
if (!tool) {
|
|
185
|
+
output = { error: `Unknown tool: ${call.name}. Available: ${tools.map((t) => t.name).join(', ')}` };
|
|
129
186
|
isError = true;
|
|
130
187
|
}
|
|
188
|
+
else {
|
|
189
|
+
try {
|
|
190
|
+
assertMayStartToolCall(options);
|
|
191
|
+
output = await tool.run(call.input ?? {});
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
output = { error: err instanceof Error ? err.message : String(err) };
|
|
195
|
+
isError = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
toolCalls += 1;
|
|
199
|
+
options.onToolCall?.({ name: call.name, input: call.input, output, isError, durationMs: Date.now() - startedAt });
|
|
200
|
+
messages.push({ role: 'assistant', content: text });
|
|
201
|
+
messages.push({ role: 'user', content: renderObservation(call.name, output) });
|
|
202
|
+
if (toolCalls >= effectiveToolBudget) {
|
|
203
|
+
messages.push({
|
|
204
|
+
role: 'user',
|
|
205
|
+
content: 'Tool budget reached — do not call any more tools. Answer now using only the tool results above, as a single ```json fenced object with summary, sql, viz, outputs.',
|
|
206
|
+
});
|
|
207
|
+
// The next iteration is the reserved final dispatch. If the model
|
|
208
|
+
// nevertheless emits a tool shape, `call` is deliberately disabled and
|
|
209
|
+
// the caller receives that text as a typed terminal, not an execution.
|
|
210
|
+
const finalText = await provider.generate(messages, runOptions).catch((error) => {
|
|
211
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
212
|
+
? error.code
|
|
213
|
+
: undefined;
|
|
214
|
+
if (code === 'PROVIDER_DISPATCH_BUDGET_EXHAUSTED')
|
|
215
|
+
return '';
|
|
216
|
+
throw error;
|
|
217
|
+
});
|
|
218
|
+
if (!finalText.trim()) {
|
|
219
|
+
return {
|
|
220
|
+
text: lastText,
|
|
221
|
+
stop: 'provider_dispatch_budget_exhausted',
|
|
222
|
+
toolCalls,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
text: finalText,
|
|
227
|
+
stop: parseTextToolCall(finalText) ? 'tool_budget_exhausted' : 'final',
|
|
228
|
+
toolCalls,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
131
231
|
}
|
|
132
|
-
options.onToolCall?.({ name: call.name, input: call.input, output, isError, durationMs: Date.now() - startedAt });
|
|
133
|
-
messages.push({ role: 'assistant', content: text });
|
|
134
|
-
messages.push({ role: 'user', content: renderObservation(call.name, output) });
|
|
135
|
-
// One provider proposal plus one final generation is the complete ordinary
|
|
136
|
-
// tool round. The final instruction prohibits a second tool proposal.
|
|
137
|
-
messages.push({
|
|
138
|
-
role: 'user',
|
|
139
|
-
content: 'Tool budget reached — do not call any more tools. Answer now using only the tool results above, as a single ```json fenced object with summary, sql, viz, outputs.',
|
|
140
|
-
});
|
|
141
|
-
const finalText = await provider.generate(messages, runOptions).catch((error) => {
|
|
142
|
-
const code = error && typeof error === 'object' && 'code' in error
|
|
143
|
-
? error.code
|
|
144
|
-
: undefined;
|
|
145
|
-
// Running out of wall clock is not a reason to discard a usable draft: the
|
|
146
|
-
// model already produced text and a tool observation. Fall back to that
|
|
147
|
-
// instead of failing the whole run with nothing.
|
|
148
|
-
if (code === 'RUN_DEADLINE_INSUFFICIENT' && lastText.trim())
|
|
149
|
-
return '';
|
|
150
|
-
if (code === 'PROVIDER_DISPATCH_BUDGET_EXHAUSTED')
|
|
151
|
-
throw error;
|
|
152
|
-
return '';
|
|
153
|
-
});
|
|
154
|
-
return finalText.trim() ? finalText : lastText;
|
|
155
232
|
}
|
|
156
233
|
function assertMayStartToolCall(options) {
|
|
157
234
|
if (options.mayStartToolCall?.() === false) {
|