@openapi-flow/langchain 0.3.15 → 0.3.17
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/README.md +14 -0
- package/dist/arguments/analyze-api-input-answers.js +118 -84
- package/dist/arguments/generate-api-arguments.js +105 -81
- package/dist/bindings/plan-api-bindings.js +1 -0
- package/dist/bindings/plan-api-call-bindings.js +1 -0
- package/dist/bindings/select-api-binding-sources.js +1 -0
- package/dist/model/invoke-structured-model.js +1 -1
- package/dist/prompts/incremental-workflow-connections-prompt.d.ts +2 -0
- package/dist/prompts/incremental-workflow-connections-prompt.js +11 -0
- package/dist/prompts/workflow-requirements-prompt.d.ts +1 -1
- package/dist/prompts/workflow-requirements-prompt.js +2 -0
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.js +5 -0
- package/dist/requirements/clarify-workflow-requirements.js +17 -3
- package/dist/schemas/connection-planning-field-schemas.d.ts +8 -0
- package/dist/schemas/connection-planning-field-schemas.js +27 -0
- package/dist/schemas/workflow-connection-goal-rejection-schema.d.ts +14 -0
- package/dist/schemas/workflow-connection-goal-rejection-schema.js +20 -0
- package/dist/schemas/workflow-connection-goals-schema.d.ts +4 -0
- package/dist/schemas/workflow-connection-goals-schema.js +53 -0
- package/dist/schemas/workflow-connection-step-schema.d.ts +4 -0
- package/dist/schemas/workflow-connection-step-schema.js +69 -0
- package/dist/selection/select-api-operations.js +1 -0
- package/dist/types/incremental-connection-planning.d.ts +69 -0
- package/dist/types/incremental-connection-planning.js +1 -0
- package/dist/types/native-node-planning.d.ts +3 -0
- package/dist/types/structured-model.d.ts +3 -1
- package/dist/workflow/connections/create-workflow-connection-goal-rejection.d.ts +3 -0
- package/dist/workflow/connections/create-workflow-connection-goal-rejection.js +27 -0
- package/dist/workflow/connections/invoke-connection-planning-model.d.ts +3 -0
- package/dist/workflow/connections/invoke-connection-planning-model.js +38 -0
- package/dist/workflow/connections/plan-workflow-connection-goals.d.ts +4 -0
- package/dist/workflow/connections/plan-workflow-connection-goals.js +72 -0
- package/dist/workflow/connections/plan-workflow-connection-step.d.ts +2 -0
- package/dist/workflow/connections/plan-workflow-connection-step.js +98 -0
- package/dist/workflow/connections/resolve-connection-port-selection.d.ts +4 -0
- package/dist/workflow/connections/resolve-connection-port-selection.js +15 -0
- package/dist/workflow/connections/workflow-connection-planning-error.d.ts +9 -0
- package/dist/workflow/connections/workflow-connection-planning-error.js +28 -0
- package/dist/workflow/generate-native-node-parameters.js +3 -1
- package/dist/workflow/plan-native-instance-dependencies.js +3 -1
- package/dist/workflow/select-native-node-instances.js +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const connectionDecisionTextSchema = z
|
|
3
|
+
.string()
|
|
4
|
+
.min(1)
|
|
5
|
+
.refine((value) => value.trim().length > 0);
|
|
6
|
+
export function connectionChoicesSchema(choices) {
|
|
7
|
+
if (!choices.length)
|
|
8
|
+
throw new Error('Connection schema requires an available choice');
|
|
9
|
+
return choices.length === 1 ? choices[0] : z.union(choices);
|
|
10
|
+
}
|
|
11
|
+
export function connectionIdListSchema(ids) {
|
|
12
|
+
return ids.length ? z.array(z.enum(ids)) : z.array(z.string()).max(0);
|
|
13
|
+
}
|
|
14
|
+
/** A single port is a code-owned fact. Only a genuine port choice is generated. */
|
|
15
|
+
export function connectionPortSelectionListSchema(nodes, direction) {
|
|
16
|
+
const choices = nodes
|
|
17
|
+
.filter((node) => node[direction].length)
|
|
18
|
+
.map((node) => {
|
|
19
|
+
const identity = z.strictObject({ nodeId: z.literal(node.id) });
|
|
20
|
+
return node[direction].length === 1
|
|
21
|
+
? identity
|
|
22
|
+
: identity.extend({ port: z.enum(node[direction]) });
|
|
23
|
+
});
|
|
24
|
+
return choices.length
|
|
25
|
+
? z.array(connectionChoicesSchema(choices))
|
|
26
|
+
: z.array(z.strictObject({ nodeId: z.string() })).max(0);
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const workflowConnectionGoalRejectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
3
|
+
stage: z.ZodLiteral<"model-output">;
|
|
4
|
+
code: z.ZodLiteral<"MODEL_OUTPUT_INVALID">;
|
|
5
|
+
sourceFingerprint: z.ZodString;
|
|
6
|
+
output: z.ZodJSONSchema;
|
|
7
|
+
reason: z.ZodString;
|
|
8
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9
|
+
stage: z.ZodLiteral<"contract-validation">;
|
|
10
|
+
code: z.ZodLiteral<"WORKFLOW_GRAPH_PLANNING_FAILED">;
|
|
11
|
+
sourceFingerprint: z.ZodString;
|
|
12
|
+
output: z.ZodJSONSchema;
|
|
13
|
+
reason: z.ZodString;
|
|
14
|
+
}, z.core.$strict>], "stage">;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { workflowConnectionStepExpectationSchema } from '@openapi-flow/core';
|
|
3
|
+
/** Private planning feedback; never part of the public error JSON or progress projection. */
|
|
4
|
+
const source = {
|
|
5
|
+
sourceFingerprint: workflowConnectionStepExpectationSchema.shape.sourceFingerprint,
|
|
6
|
+
output: z.json(),
|
|
7
|
+
reason: z.string().min(1),
|
|
8
|
+
};
|
|
9
|
+
export const workflowConnectionGoalRejectionSchema = z.discriminatedUnion('stage', [
|
|
10
|
+
z.strictObject({
|
|
11
|
+
...source,
|
|
12
|
+
stage: z.literal('model-output'),
|
|
13
|
+
code: z.literal('MODEL_OUTPUT_INVALID'),
|
|
14
|
+
}),
|
|
15
|
+
z.strictObject({
|
|
16
|
+
...source,
|
|
17
|
+
stage: z.literal('contract-validation'),
|
|
18
|
+
code: z.literal('WORKFLOW_GRAPH_PLANNING_FAILED'),
|
|
19
|
+
}),
|
|
20
|
+
]);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { WorkflowConnectionGoalPlanningDescription } from '@openapi-flow/core';
|
|
3
|
+
import type { WorkflowConnectionGoalsModelOutput } from '../types/incremental-connection-planning.js';
|
|
4
|
+
export declare function createWorkflowConnectionGoalsSchema(description: WorkflowConnectionGoalPlanningDescription): z.ZodType<WorkflowConnectionGoalsModelOutput>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { connectionChoicesSchema, connectionDecisionTextSchema as text, connectionPortSelectionListSchema, } from './connection-planning-field-schemas.js';
|
|
3
|
+
export function createWorkflowConnectionGoalsSchema(description) {
|
|
4
|
+
const nodes = description.context.nodes;
|
|
5
|
+
const ids = nodes.map((node) => node.id);
|
|
6
|
+
if (!ids.length)
|
|
7
|
+
throw new Error('Connection goals require supplied nodes');
|
|
8
|
+
const basis = {
|
|
9
|
+
factIds: z
|
|
10
|
+
.array(z.enum(description.requirements.map((fact) => fact.id)))
|
|
11
|
+
.min(1)
|
|
12
|
+
.describe('Exact confirmed requirement IDs that justify this goal. Cover every supplied fact without changing it.'),
|
|
13
|
+
reason: text.describe('Brief evidence-based explanation, not private reasoning or new requirements.'),
|
|
14
|
+
};
|
|
15
|
+
const choices = [
|
|
16
|
+
z.strictObject({ kind: z.literal('semantic'), ...basis }),
|
|
17
|
+
];
|
|
18
|
+
if (ids.length > 1) {
|
|
19
|
+
choices.push(z.strictObject({
|
|
20
|
+
kind: z.literal('precedence'),
|
|
21
|
+
before: z.enum(ids),
|
|
22
|
+
after: z.enum(ids),
|
|
23
|
+
...basis,
|
|
24
|
+
}), z.strictObject({
|
|
25
|
+
kind: z.literal('independent'),
|
|
26
|
+
nodeIds: z.array(z.enum(ids)).min(2),
|
|
27
|
+
...basis,
|
|
28
|
+
}));
|
|
29
|
+
if (nodes.some((node) => node.outputs.length))
|
|
30
|
+
choices.push(z.strictObject({
|
|
31
|
+
kind: z.literal('route'),
|
|
32
|
+
source: connectionPortSelectionListSchema(nodes, 'outputs').element,
|
|
33
|
+
to: z.enum(ids),
|
|
34
|
+
...basis,
|
|
35
|
+
}));
|
|
36
|
+
const joinIds = nodes
|
|
37
|
+
.filter((node) => node.waitsForAllInputs)
|
|
38
|
+
.map((node) => node.id);
|
|
39
|
+
if (ids.length > 2 && joinIds.length)
|
|
40
|
+
choices.push(z.strictObject({
|
|
41
|
+
kind: z.literal('join'),
|
|
42
|
+
nodeId: z.enum(joinIds),
|
|
43
|
+
producerIds: z.array(z.enum(ids)).min(2),
|
|
44
|
+
...basis,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
return z.strictObject({
|
|
48
|
+
goals: z
|
|
49
|
+
.array(connectionChoicesSchema(choices))
|
|
50
|
+
.min(1)
|
|
51
|
+
.describe('Interpret confirmed requirements as goals, not as a complete edge list. Structural goals are checked by code. Use semantic only where a structural goal cannot express the requirement.'),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { WorkflowConnectionStepDescription, WorkflowCapability } from '@openapi-flow/core';
|
|
3
|
+
import type { WorkflowConnectionStepModelOutput } from '../types/incremental-connection-planning.js';
|
|
4
|
+
export declare function createWorkflowConnectionStepSchema(description: WorkflowConnectionStepDescription, capabilities: WorkflowCapability[]): z.ZodType<WorkflowConnectionStepModelOutput>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { workflowConnectionAssessmentSchema, workflowConnectionMaterialRequestSchema, workflowConnectionUserDecisionSchema, } from '@openapi-flow/core';
|
|
3
|
+
import { connectionChoicesSchema, connectionDecisionTextSchema as text, connectionIdListSchema, connectionPortSelectionListSchema, } from './connection-planning-field-schemas.js';
|
|
4
|
+
export function createWorkflowConnectionStepSchema(description, capabilities) {
|
|
5
|
+
const nodes = description.context.nodes;
|
|
6
|
+
const ids = nodes.map((node) => node.id);
|
|
7
|
+
if (!ids.length)
|
|
8
|
+
throw new Error('Connection steps require supplied nodes');
|
|
9
|
+
const factIds = z
|
|
10
|
+
.array(z.enum(description.requirements.map((fact) => fact.id)))
|
|
11
|
+
.min(1);
|
|
12
|
+
const sources = connectionPortSelectionListSchema(nodes, 'outputs');
|
|
13
|
+
const targets = connectionPortSelectionListSchema(nodes, 'inputs');
|
|
14
|
+
const edge = z.strictObject({
|
|
15
|
+
source: sources.element,
|
|
16
|
+
target: targets.element,
|
|
17
|
+
});
|
|
18
|
+
const canConnect = nodes.some((node) => node.outputs.length) &&
|
|
19
|
+
nodes.some((node) => node.inputs.length);
|
|
20
|
+
const roots = nodes
|
|
21
|
+
.filter((node) => !node.waitsForAllInputs &&
|
|
22
|
+
node.rejoinsExclusiveOutputs === undefined &&
|
|
23
|
+
!node.references.length &&
|
|
24
|
+
!node.dependencyNodeIds.length &&
|
|
25
|
+
!Object.values(node.inputReferences ?? {}).some((references) => references.length))
|
|
26
|
+
.map((node) => node.id);
|
|
27
|
+
const terminalNodes = nodes.map((node) => ({
|
|
28
|
+
...node,
|
|
29
|
+
outputs: node.outputs.filter((output) => description.terminals.some((terminal) => terminal.nodeId === node.id && terminal.output === output)),
|
|
30
|
+
}));
|
|
31
|
+
const semanticGoalIds = description.goals
|
|
32
|
+
.filter((goal) => goal.decision.kind === 'semantic')
|
|
33
|
+
.map((goal) => goal.id);
|
|
34
|
+
const assessments = semanticGoalIds.length
|
|
35
|
+
? z.array(workflowConnectionAssessmentSchema.extend({
|
|
36
|
+
goalId: z.enum(semanticGoalIds),
|
|
37
|
+
nodeIds: z.array(z.enum(ids)),
|
|
38
|
+
}))
|
|
39
|
+
: z.array(workflowConnectionAssessmentSchema).max(0);
|
|
40
|
+
const choices = [
|
|
41
|
+
z.strictObject({
|
|
42
|
+
kind: z.literal('patch'),
|
|
43
|
+
addEdges: (canConnect ? z.array(edge) : z.array(edge).max(0)).describe('One complete connection bundle, including both conditional branches or the intended fan-out/join where needed. Not a full graph replacement.'),
|
|
44
|
+
removeEdgeIds: connectionIdListSchema(description.edgeCatalog
|
|
45
|
+
.filter((item) => item.editable)
|
|
46
|
+
.map((item) => item.id)),
|
|
47
|
+
addRootNodeIds: connectionIdListSchema(roots).describe('Explicit independent roots. Unconnected nodes are not automatic roots.'),
|
|
48
|
+
removeRootNodeIds: connectionIdListSchema(description.rootNodeIds),
|
|
49
|
+
addTerminals: sources,
|
|
50
|
+
removeTerminals: connectionPortSelectionListSchema(terminalNodes, 'outputs'),
|
|
51
|
+
factIds,
|
|
52
|
+
reason: text,
|
|
53
|
+
}),
|
|
54
|
+
z.strictObject({
|
|
55
|
+
kind: z.literal('request-completion'),
|
|
56
|
+
assessments,
|
|
57
|
+
reason: text,
|
|
58
|
+
}),
|
|
59
|
+
workflowConnectionUserDecisionSchema
|
|
60
|
+
.extend({ factIds })
|
|
61
|
+
.describe('Only a genuine unresolved BUSINESS decision. Never ask the user to fix ports, cycles, model output or a missing connection.'),
|
|
62
|
+
];
|
|
63
|
+
if (capabilities.length)
|
|
64
|
+
choices.push(workflowConnectionMaterialRequestSchema.extend({
|
|
65
|
+
capability: z.enum(capabilities.map((capability) => capability.name)),
|
|
66
|
+
factIds,
|
|
67
|
+
}));
|
|
68
|
+
return z.strictObject({ decision: connectionChoicesSchema(choices) });
|
|
69
|
+
}
|
|
@@ -50,6 +50,7 @@ export async function selectApiOperations(input) {
|
|
|
50
50
|
operation: 'selectApiOperations',
|
|
51
51
|
name: 'select_api_operations',
|
|
52
52
|
method: 'jsonSchema',
|
|
53
|
+
strict: true,
|
|
53
54
|
messages: [
|
|
54
55
|
new SystemMessage(`${operationSelectionPrompt}\n${apiSelectionExplanationPrompt}`),
|
|
55
56
|
new HumanMessage(JSON.stringify({ scenario, candidates })),
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
2
|
+
import type { ZodType, z } from 'zod';
|
|
3
|
+
import type { workflowConnectionGoalRejectionSchema } from '../schemas/workflow-connection-goal-rejection-schema.js';
|
|
4
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
5
|
+
import type { WorkflowConnectionSessionSource, WorkflowConnectionSessionInput, WorkflowConnectionGoalProposal, WorkflowConnectionStepProposal, WorkflowConnectionPatch, WorkflowConnectionPlanningDecision, WorkflowConnectionMaterialRequest, WorkflowConnectionUserDecision, WorkflowConnectionRejectedProposal, WorkflowConnectionStepExpectation } from '@openapi-flow/core';
|
|
6
|
+
export interface PlanWorkflowConnectionGoalsInput extends WorkflowConnectionSessionSource {
|
|
7
|
+
model: BaseChatModel;
|
|
8
|
+
/** Exact rejected output and code-owned validation feedback for this unchanged source. */
|
|
9
|
+
rejectedProposals?: WorkflowConnectionGoalRejection[];
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export type WorkflowConnectionGoalRejection = z.infer<typeof workflowConnectionGoalRejectionSchema>;
|
|
13
|
+
export interface CreateWorkflowConnectionGoalRejectionInput extends WorkflowConnectionSessionSource {
|
|
14
|
+
error: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface PlanWorkflowConnectionStepInput extends WorkflowConnectionSessionInput {
|
|
17
|
+
model: BaseChatModel;
|
|
18
|
+
rejectedProposals?: WorkflowConnectionRejectedProposal[];
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
export type WorkflowConnectionRouteGoal = Extract<WorkflowConnectionGoalProposal, {
|
|
22
|
+
kind: 'route';
|
|
23
|
+
}>;
|
|
24
|
+
export interface WorkflowConnectionRouteSelection extends Omit<WorkflowConnectionRouteGoal, 'from' | 'output'> {
|
|
25
|
+
source: WorkflowConnectionEndpointSelection;
|
|
26
|
+
}
|
|
27
|
+
export type WorkflowConnectionGoalSelection = Exclude<WorkflowConnectionGoalProposal, WorkflowConnectionRouteGoal> | WorkflowConnectionRouteSelection;
|
|
28
|
+
export interface WorkflowConnectionGoalsModelOutput {
|
|
29
|
+
goals: WorkflowConnectionGoalSelection[];
|
|
30
|
+
}
|
|
31
|
+
export interface WorkflowConnectionEndpointSelection {
|
|
32
|
+
nodeId: string;
|
|
33
|
+
/** Omitted in the model contract exactly when this node has only one such port. */
|
|
34
|
+
port?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface WorkflowConnectionEdgeSelection {
|
|
37
|
+
source: WorkflowConnectionEndpointSelection;
|
|
38
|
+
target: WorkflowConnectionEndpointSelection;
|
|
39
|
+
}
|
|
40
|
+
export interface WorkflowConnectionPatchSelection extends Omit<WorkflowConnectionPatch, 'addEdges' | 'addTerminals' | 'removeTerminals'> {
|
|
41
|
+
addEdges: WorkflowConnectionEdgeSelection[];
|
|
42
|
+
addTerminals: WorkflowConnectionEndpointSelection[];
|
|
43
|
+
removeTerminals: WorkflowConnectionEndpointSelection[];
|
|
44
|
+
}
|
|
45
|
+
export type WorkflowConnectionCompletionRequest = Exclude<WorkflowConnectionStepProposal, WorkflowConnectionPatch>;
|
|
46
|
+
export type WorkflowConnectionStepModelDecision = WorkflowConnectionPatchSelection | WorkflowConnectionCompletionRequest | WorkflowConnectionMaterialRequest | WorkflowConnectionUserDecision;
|
|
47
|
+
export interface WorkflowConnectionStepModelOutput {
|
|
48
|
+
decision: WorkflowConnectionStepModelDecision;
|
|
49
|
+
}
|
|
50
|
+
export interface PlanWorkflowConnectionStepResult {
|
|
51
|
+
expected: WorkflowConnectionStepExpectation;
|
|
52
|
+
decision: WorkflowConnectionPlanningDecision;
|
|
53
|
+
}
|
|
54
|
+
export interface InvokeConnectionPlanningModelInput<Output extends object> {
|
|
55
|
+
model: BaseChatModel;
|
|
56
|
+
operation: string;
|
|
57
|
+
name: string;
|
|
58
|
+
schema: ZodType<Output>;
|
|
59
|
+
messages: BaseMessage[];
|
|
60
|
+
sourceFingerprint?: string;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
}
|
|
63
|
+
export interface WorkflowConnectionPlanningFailure {
|
|
64
|
+
operation: string;
|
|
65
|
+
stage: 'model-output' | 'contract-validation';
|
|
66
|
+
code: 'MODEL_OUTPUT_INVALID' | 'WORKFLOW_GRAPH_PLANNING_FAILED';
|
|
67
|
+
output: unknown;
|
|
68
|
+
sourceFingerprint?: string;
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -67,10 +67,13 @@ export type NativeNodePlanningFailure = {
|
|
|
67
67
|
};
|
|
68
68
|
export interface SelectNativeNodeInstancesInput extends NativeInstanceSelectionInput {
|
|
69
69
|
model?: BaseChatModel;
|
|
70
|
+
signal?: AbortSignal;
|
|
70
71
|
}
|
|
71
72
|
export interface GenerateNativeNodeParametersInput extends NativeNodeConfigurationInput {
|
|
72
73
|
model?: BaseChatModel;
|
|
74
|
+
signal?: AbortSignal;
|
|
73
75
|
}
|
|
74
76
|
export interface PlanNativeInstanceDependenciesInput extends NativeSelectedPlanningInput {
|
|
75
77
|
model?: BaseChatModel;
|
|
78
|
+
signal?: AbortSignal;
|
|
76
79
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
2
2
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
3
|
import type { ZodType } from 'zod';
|
|
4
|
+
import type { JsonObject } from '@openapi-flow/core';
|
|
4
5
|
export interface InvokeStructuredModelInput<Output extends object> {
|
|
5
6
|
model: BaseChatModel;
|
|
6
|
-
schema: ZodType<Output
|
|
7
|
+
schema: ZodType<Output> | JsonObject;
|
|
7
8
|
name: string;
|
|
8
9
|
operation: string;
|
|
9
10
|
method: 'functionCalling' | 'jsonSchema';
|
|
11
|
+
strict: boolean | undefined;
|
|
10
12
|
messages: BaseMessage[];
|
|
11
13
|
signal?: AbortSignal;
|
|
12
14
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CreateWorkflowConnectionGoalRejectionInput, WorkflowConnectionGoalRejection } from '../../types/incremental-connection-planning.js';
|
|
2
|
+
/** Classify an unchanged-source proposal rejection. Other failures must remain failures. */
|
|
3
|
+
export declare function createWorkflowConnectionGoalRejection(input: CreateWorkflowConnectionGoalRejectionInput): WorkflowConnectionGoalRejection | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { describeWorkflowConnectionGoals } from '@openapi-flow/core';
|
|
3
|
+
import { workflowConnectionGoalRejectionSchema } from '../../schemas/workflow-connection-goal-rejection-schema.js';
|
|
4
|
+
import { WorkflowConnectionPlanningError } from './workflow-connection-planning-error.js';
|
|
5
|
+
/** Classify an unchanged-source proposal rejection. Other failures must remain failures. */
|
|
6
|
+
export function createWorkflowConnectionGoalRejection(input) {
|
|
7
|
+
const { error } = input;
|
|
8
|
+
if (!(error instanceof WorkflowConnectionPlanningError) ||
|
|
9
|
+
error.diagnostic.operation !== 'planWorkflowConnectionGoals' ||
|
|
10
|
+
!['model-output', 'contract-validation'].includes(error.diagnostic.stage) ||
|
|
11
|
+
error.sourceFingerprint === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
const current = describeWorkflowConnectionGoals(input);
|
|
14
|
+
if (error.sourceFingerprint !== current.sourceFingerprint)
|
|
15
|
+
return undefined;
|
|
16
|
+
const parsed = workflowConnectionGoalRejectionSchema.safeParse({
|
|
17
|
+
sourceFingerprint: error.sourceFingerprint,
|
|
18
|
+
stage: error.diagnostic.stage,
|
|
19
|
+
code: error.diagnostic.code,
|
|
20
|
+
output: error.output,
|
|
21
|
+
reason: error.message,
|
|
22
|
+
});
|
|
23
|
+
// Non-JSON results cannot be checkpointed losslessly; never repair them to enable a retry.
|
|
24
|
+
if (!parsed.success || !isDeepStrictEqual(parsed.data.output, error.output))
|
|
25
|
+
return undefined;
|
|
26
|
+
return structuredClone(parsed.data);
|
|
27
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { InvokeConnectionPlanningModelInput } from '../../types/incremental-connection-planning.js';
|
|
2
|
+
/** Exactly one invocation. Loop policy and rejected-proposal handling belong to the host. */
|
|
3
|
+
export declare function invokeConnectionPlanningModel<Output extends object>(input: InvokeConnectionPlanningModelInput<Output>): Promise<Output>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { runOpenApiFlowOperation } from '@openapi-flow/core';
|
|
3
|
+
import { invokeStructuredModel } from '../../model/invoke-structured-model.js';
|
|
4
|
+
import { serializeStructuredOutputSchema } from '../../schemas/serialize-structured-output-schema.js';
|
|
5
|
+
import { validateConnectionProposal } from './workflow-connection-planning-error.js';
|
|
6
|
+
/** Exactly one invocation. Loop policy and rejected-proposal handling belong to the host. */
|
|
7
|
+
export async function invokeConnectionPlanningModel(input) {
|
|
8
|
+
const wireSchema = runOpenApiFlowOperation({
|
|
9
|
+
owner: 'langchain',
|
|
10
|
+
operation: input.operation,
|
|
11
|
+
stage: 'input-validation',
|
|
12
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
13
|
+
}, () => serializeStructuredOutputSchema(input.schema, 'draft-2020-12'));
|
|
14
|
+
const output = await invokeStructuredModel({
|
|
15
|
+
model: input.model,
|
|
16
|
+
operation: input.operation,
|
|
17
|
+
name: input.name,
|
|
18
|
+
schema: wireSchema,
|
|
19
|
+
method: 'functionCalling',
|
|
20
|
+
strict: true,
|
|
21
|
+
messages: input.messages,
|
|
22
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
23
|
+
});
|
|
24
|
+
return validateConnectionProposal({
|
|
25
|
+
operation: input.operation,
|
|
26
|
+
stage: 'model-output',
|
|
27
|
+
code: 'MODEL_OUTPUT_INVALID',
|
|
28
|
+
output,
|
|
29
|
+
...(input.sourceFingerprint === undefined
|
|
30
|
+
? {}
|
|
31
|
+
: { sourceFingerprint: input.sourceFingerprint }),
|
|
32
|
+
}, () => {
|
|
33
|
+
const parsed = input.schema.parse(output);
|
|
34
|
+
if (!isDeepStrictEqual(parsed, output))
|
|
35
|
+
throw new Error('Connection output schema must not default, coerce or transform model data');
|
|
36
|
+
return parsed;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { WorkflowConnectionSession } from '@openapi-flow/core';
|
|
2
|
+
import type { PlanWorkflowConnectionGoalsInput } from '../../types/incremental-connection-planning.js';
|
|
3
|
+
/** One semantic interpretation; code validates coverage/identities and creates the initial session. */
|
|
4
|
+
export declare function planWorkflowConnectionGoals(input: PlanWorkflowConnectionGoalsInput): Promise<WorkflowConnectionSession>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
|
+
import { describeWorkflowConnectionGoals, createWorkflowConnectionSession, runOpenApiFlowOperation, } from '@openapi-flow/core';
|
|
3
|
+
import { createWorkflowConnectionGoalsSchema } from '../../schemas/workflow-connection-goals-schema.js';
|
|
4
|
+
import { workflowConnectionGoalRejectionSchema } from '../../schemas/workflow-connection-goal-rejection-schema.js';
|
|
5
|
+
import { workflowConnectionGoalsPrompt } from '../../prompts/incremental-workflow-connections-prompt.js';
|
|
6
|
+
import { invokeConnectionPlanningModel } from './invoke-connection-planning-model.js';
|
|
7
|
+
import { resolveConnectionPortSelection } from './resolve-connection-port-selection.js';
|
|
8
|
+
import { validateConnectionProposal } from './workflow-connection-planning-error.js';
|
|
9
|
+
/** One semantic interpretation; code validates coverage/identities and creates the initial session. */
|
|
10
|
+
export async function planWorkflowConnectionGoals(input) {
|
|
11
|
+
const operation = 'planWorkflowConnectionGoals';
|
|
12
|
+
const { description, schema } = runOpenApiFlowOperation({
|
|
13
|
+
owner: 'langchain',
|
|
14
|
+
operation,
|
|
15
|
+
stage: 'input-validation',
|
|
16
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
17
|
+
}, () => {
|
|
18
|
+
const description = describeWorkflowConnectionGoals(input);
|
|
19
|
+
if (input.rejectedProposals !== undefined) {
|
|
20
|
+
if (!Array.isArray(input.rejectedProposals))
|
|
21
|
+
throw new Error('rejectedProposals must be an array when supplied');
|
|
22
|
+
for (const rejection of input.rejectedProposals) {
|
|
23
|
+
workflowConnectionGoalRejectionSchema.parse(rejection);
|
|
24
|
+
if (rejection.sourceFingerprint !== description.sourceFingerprint)
|
|
25
|
+
throw new Error('Rejected goals must belong to the unchanged source and requirements');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
description,
|
|
30
|
+
schema: createWorkflowConnectionGoalsSchema(description),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
const output = await invokeConnectionPlanningModel({
|
|
34
|
+
model: input.model,
|
|
35
|
+
operation,
|
|
36
|
+
name: 'plan_workflow_connection_goals',
|
|
37
|
+
schema,
|
|
38
|
+
sourceFingerprint: description.sourceFingerprint,
|
|
39
|
+
messages: [
|
|
40
|
+
new SystemMessage(workflowConnectionGoalsPrompt),
|
|
41
|
+
new HumanMessage(JSON.stringify({
|
|
42
|
+
...description,
|
|
43
|
+
...(input.rejectedProposals === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: { rejectedProposals: input.rejectedProposals }),
|
|
46
|
+
})),
|
|
47
|
+
],
|
|
48
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
49
|
+
});
|
|
50
|
+
return validateConnectionProposal({
|
|
51
|
+
operation,
|
|
52
|
+
stage: 'contract-validation',
|
|
53
|
+
code: 'WORKFLOW_GRAPH_PLANNING_FAILED',
|
|
54
|
+
output,
|
|
55
|
+
sourceFingerprint: description.sourceFingerprint,
|
|
56
|
+
}, () => {
|
|
57
|
+
if (describeWorkflowConnectionGoals(input).sourceFingerprint !==
|
|
58
|
+
description.sourceFingerprint)
|
|
59
|
+
throw new Error('Connection goal source changed during model invocation');
|
|
60
|
+
const goals = output.goals.map((goal) => goal.kind !== 'route'
|
|
61
|
+
? goal
|
|
62
|
+
: {
|
|
63
|
+
kind: goal.kind,
|
|
64
|
+
from: goal.source.nodeId,
|
|
65
|
+
to: goal.to,
|
|
66
|
+
factIds: goal.factIds,
|
|
67
|
+
reason: goal.reason,
|
|
68
|
+
output: resolveConnectionPortSelection(goal.source, description.context.nodes, 'outputs'),
|
|
69
|
+
});
|
|
70
|
+
return createWorkflowConnectionSession({ ...input, goals });
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
+
import { describeWorkflowConnectionStep, workflowConnectionPlanningDecisionSchema, workflowConnectionRejectedProposalSchema, runOpenApiFlowOperation, } from '@openapi-flow/core';
|
|
4
|
+
import { createWorkflowConnectionStepSchema } from '../../schemas/workflow-connection-step-schema.js';
|
|
5
|
+
import { workflowConnectionStepPrompt } from '../../prompts/incremental-workflow-connections-prompt.js';
|
|
6
|
+
import { invokeConnectionPlanningModel } from './invoke-connection-planning-model.js';
|
|
7
|
+
import { resolveConnectionPortSelection } from './resolve-connection-port-selection.js';
|
|
8
|
+
import { validateConnectionProposal } from './workflow-connection-planning-error.js';
|
|
9
|
+
export async function planWorkflowConnectionStep(input) {
|
|
10
|
+
const operation = 'planWorkflowConnectionStep';
|
|
11
|
+
const { description, capabilities, capabilityIndex, schema } = runOpenApiFlowOperation({
|
|
12
|
+
owner: 'langchain',
|
|
13
|
+
operation,
|
|
14
|
+
stage: 'input-validation',
|
|
15
|
+
code: 'PLANNING_INPUT_INVALID',
|
|
16
|
+
}, () => {
|
|
17
|
+
const description = describeWorkflowConnectionStep(input);
|
|
18
|
+
if (input.rejectedProposals !== undefined) {
|
|
19
|
+
if (!Array.isArray(input.rejectedProposals))
|
|
20
|
+
throw new Error('rejectedProposals must be an array when supplied');
|
|
21
|
+
for (const proposal of input.rejectedProposals) {
|
|
22
|
+
workflowConnectionRejectedProposalSchema.parse(proposal);
|
|
23
|
+
if (proposal.expected.sessionId !== description.expected.sessionId ||
|
|
24
|
+
proposal.expected.sourceFingerprint !==
|
|
25
|
+
description.expected.sourceFingerprint ||
|
|
26
|
+
proposal.expected.revision > description.expected.revision)
|
|
27
|
+
throw new Error('Rejected proposal must belong to this session/source and not a future revision');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// An omitted optional registry has no augmentation choices; never infer an engine registry.
|
|
31
|
+
const capabilities = input.capabilities === undefined ? [] : input.capabilities;
|
|
32
|
+
return {
|
|
33
|
+
description,
|
|
34
|
+
capabilities,
|
|
35
|
+
capabilityIndex: capabilities.map(({ name, description }) => ({
|
|
36
|
+
name,
|
|
37
|
+
description,
|
|
38
|
+
})),
|
|
39
|
+
schema: createWorkflowConnectionStepSchema(description, capabilities),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
const output = await invokeConnectionPlanningModel({
|
|
43
|
+
model: input.model,
|
|
44
|
+
operation,
|
|
45
|
+
name: 'plan_workflow_connection_step',
|
|
46
|
+
schema,
|
|
47
|
+
messages: [
|
|
48
|
+
new SystemMessage(workflowConnectionStepPrompt),
|
|
49
|
+
new HumanMessage(JSON.stringify({
|
|
50
|
+
...description,
|
|
51
|
+
availableNativeCapabilities: capabilityIndex,
|
|
52
|
+
...(input.rejectedProposals === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { rejectedProposals: input.rejectedProposals }),
|
|
55
|
+
})),
|
|
56
|
+
],
|
|
57
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
58
|
+
});
|
|
59
|
+
return validateConnectionProposal({
|
|
60
|
+
operation,
|
|
61
|
+
stage: 'contract-validation',
|
|
62
|
+
code: 'WORKFLOW_GRAPH_PLANNING_FAILED',
|
|
63
|
+
output,
|
|
64
|
+
}, () => {
|
|
65
|
+
if (!isDeepStrictEqual(describeWorkflowConnectionStep(input), description))
|
|
66
|
+
throw new Error('Connection session or source changed during model invocation');
|
|
67
|
+
if (!isDeepStrictEqual(capabilities.map(({ name, description }) => ({ name, description })), capabilityIndex))
|
|
68
|
+
throw new Error('Available native capabilities changed during model invocation');
|
|
69
|
+
const raw = output.decision;
|
|
70
|
+
const decision = raw.kind !== 'patch'
|
|
71
|
+
? raw
|
|
72
|
+
: {
|
|
73
|
+
...raw,
|
|
74
|
+
addEdges: raw.addEdges.map((edge) => ({
|
|
75
|
+
from: edge.source.nodeId,
|
|
76
|
+
output: resolveConnectionPortSelection(edge.source, description.context.nodes, 'outputs'),
|
|
77
|
+
to: edge.target.nodeId,
|
|
78
|
+
input: resolveConnectionPortSelection(edge.target, description.context.nodes, 'inputs'),
|
|
79
|
+
})),
|
|
80
|
+
addTerminals: raw.addTerminals.map((selection) => ({
|
|
81
|
+
nodeId: selection.nodeId,
|
|
82
|
+
output: resolveConnectionPortSelection(selection, description.context.nodes, 'outputs'),
|
|
83
|
+
})),
|
|
84
|
+
removeTerminals: raw.removeTerminals.map((selection) => ({
|
|
85
|
+
nodeId: selection.nodeId,
|
|
86
|
+
output: resolveConnectionPortSelection(selection, description.context.nodes.map((node) => ({
|
|
87
|
+
...node,
|
|
88
|
+
outputs: node.outputs.filter((port) => description.terminals.some((terminal) => terminal.nodeId === node.id &&
|
|
89
|
+
terminal.output === port)),
|
|
90
|
+
})), 'outputs'),
|
|
91
|
+
})),
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
expected: structuredClone(description.expected),
|
|
95
|
+
decision: workflowConnectionPlanningDecisionSchema.parse(decision),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { WorkflowPlanNodeContract } from '@openapi-flow/core';
|
|
2
|
+
import type { WorkflowConnectionEndpointSelection } from '../../types/incremental-connection-planning.js';
|
|
3
|
+
/** The schema deliberately excludes a unique port from the model's decision. This is not a fallback. */
|
|
4
|
+
export declare function resolveConnectionPortSelection(selection: WorkflowConnectionEndpointSelection, contracts: WorkflowPlanNodeContract[], direction: 'inputs' | 'outputs'): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The schema deliberately excludes a unique port from the model's decision. This is not a fallback. */
|
|
2
|
+
export function resolveConnectionPortSelection(selection, contracts, direction) {
|
|
3
|
+
const node = contracts.find((contract) => contract.id === selection.nodeId);
|
|
4
|
+
if (!node)
|
|
5
|
+
throw new Error(`Connection selection has no node ${selection.nodeId}`);
|
|
6
|
+
const ports = node[direction];
|
|
7
|
+
if (ports.length === 1) {
|
|
8
|
+
if (selection.port !== undefined)
|
|
9
|
+
throw new Error(`Connection selection ${node.id} must not regenerate its unique port`);
|
|
10
|
+
return ports[0];
|
|
11
|
+
}
|
|
12
|
+
if (selection.port === undefined || !ports.includes(selection.port))
|
|
13
|
+
throw new Error(`Connection selection ${node.id} requires an actual ${direction} port`);
|
|
14
|
+
return selection.port;
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OpenApiFlowError } from '@openapi-flow/core';
|
|
2
|
+
import type { WorkflowConnectionPlanningFailure } from '../../types/incremental-connection-planning.js';
|
|
3
|
+
/** Raw output is retained for a private host diagnostic, not in the public error projection. */
|
|
4
|
+
export declare class WorkflowConnectionPlanningError extends OpenApiFlowError {
|
|
5
|
+
readonly output: unknown;
|
|
6
|
+
readonly sourceFingerprint: string | undefined;
|
|
7
|
+
constructor(failure: WorkflowConnectionPlanningFailure, cause: unknown);
|
|
8
|
+
}
|
|
9
|
+
export declare function validateConnectionProposal<Result>(failure: WorkflowConnectionPlanningFailure, execute: () => Result): Result;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { OpenApiFlowError } from '@openapi-flow/core';
|
|
2
|
+
/** Raw output is retained for a private host diagnostic, not in the public error projection. */
|
|
3
|
+
export class WorkflowConnectionPlanningError extends OpenApiFlowError {
|
|
4
|
+
output;
|
|
5
|
+
sourceFingerprint;
|
|
6
|
+
constructor(failure, cause) {
|
|
7
|
+
super({
|
|
8
|
+
message: cause instanceof Error ? cause.message : failure.operation,
|
|
9
|
+
diagnostic: {
|
|
10
|
+
owner: 'langchain',
|
|
11
|
+
operation: failure.operation,
|
|
12
|
+
stage: failure.stage,
|
|
13
|
+
code: failure.code,
|
|
14
|
+
},
|
|
15
|
+
cause,
|
|
16
|
+
});
|
|
17
|
+
this.output = failure.output;
|
|
18
|
+
this.sourceFingerprint = failure.sourceFingerprint;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function validateConnectionProposal(failure, execute) {
|
|
22
|
+
try {
|
|
23
|
+
return execute();
|
|
24
|
+
}
|
|
25
|
+
catch (cause) {
|
|
26
|
+
throw new WorkflowConnectionPlanningError(failure, cause);
|
|
27
|
+
}
|
|
28
|
+
}
|