@openapi-flow/langchain 0.3.14 → 0.3.16

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.
Files changed (46) hide show
  1. package/README.md +14 -0
  2. package/dist/arguments/analyze-api-input-answers.js +118 -84
  3. package/dist/arguments/generate-api-arguments.js +105 -81
  4. package/dist/bindings/plan-api-bindings-by-call.d.ts +4 -0
  5. package/dist/bindings/plan-api-bindings-by-call.js +77 -0
  6. package/dist/bindings/plan-api-bindings.js +1 -0
  7. package/dist/bindings/plan-api-call-bindings.d.ts +3 -0
  8. package/dist/bindings/plan-api-call-bindings.js +82 -0
  9. package/dist/bindings/select-api-binding-sources.d.ts +3 -0
  10. package/dist/bindings/select-api-binding-sources.js +55 -0
  11. package/dist/model/invoke-structured-model.js +2 -2
  12. package/dist/prompts/api-call-bindings-prompt.d.ts +2 -0
  13. package/dist/prompts/api-call-bindings-prompt.js +8 -0
  14. package/dist/prompts/incremental-workflow-connections-prompt.d.ts +2 -0
  15. package/dist/prompts/incremental-workflow-connections-prompt.js +10 -0
  16. package/dist/public-api.d.ts +8 -0
  17. package/dist/public-api.js +6 -0
  18. package/dist/schemas/api-call-binding-plan-schema.d.ts +7 -0
  19. package/dist/schemas/api-call-binding-plan-schema.js +46 -0
  20. package/dist/schemas/connection-planning-field-schemas.d.ts +8 -0
  21. package/dist/schemas/connection-planning-field-schemas.js +27 -0
  22. package/dist/schemas/workflow-connection-goals-schema.d.ts +4 -0
  23. package/dist/schemas/workflow-connection-goals-schema.js +53 -0
  24. package/dist/schemas/workflow-connection-step-schema.d.ts +4 -0
  25. package/dist/schemas/workflow-connection-step-schema.js +69 -0
  26. package/dist/selection/select-api-operations.js +1 -0
  27. package/dist/types/api-call-binding-planning.d.ts +17 -0
  28. package/dist/types/api-call-binding-planning.js +1 -0
  29. package/dist/types/incremental-connection-planning.d.ts +60 -0
  30. package/dist/types/incremental-connection-planning.js +1 -0
  31. package/dist/types/native-node-planning.d.ts +3 -0
  32. package/dist/types/structured-model.d.ts +4 -1
  33. package/dist/workflow/connections/invoke-connection-planning-model.d.ts +3 -0
  34. package/dist/workflow/connections/invoke-connection-planning-model.js +35 -0
  35. package/dist/workflow/connections/plan-workflow-connection-goals.d.ts +4 -0
  36. package/dist/workflow/connections/plan-workflow-connection-goals.js +55 -0
  37. package/dist/workflow/connections/plan-workflow-connection-step.d.ts +2 -0
  38. package/dist/workflow/connections/plan-workflow-connection-step.js +98 -0
  39. package/dist/workflow/connections/resolve-connection-port-selection.d.ts +4 -0
  40. package/dist/workflow/connections/resolve-connection-port-selection.js +15 -0
  41. package/dist/workflow/connections/workflow-connection-planning-error.d.ts +8 -0
  42. package/dist/workflow/connections/workflow-connection-planning-error.js +26 -0
  43. package/dist/workflow/generate-native-node-parameters.js +3 -1
  44. package/dist/workflow/plan-native-instance-dependencies.js +3 -1
  45. package/dist/workflow/select-native-node-instances.js +3 -1
  46. package/package.json +2 -2
package/README.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @openapi-flow/langchain
2
2
 
3
+ Version **0.3.16** adds `planWorkflowConnectionGoals` and
4
+ `planWorkflowConnectionStep`. Each makes one structured model call; the host owns
5
+ looping, checkpointing and review. Native planning stages accept optional abort
6
+ signals. See the [connection guide](https://github.com/carefreelife98/openapi-flow/blob/main/docs/incremental-connection-planning.md)
7
+ and [release status and limitations](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.16.md).
8
+
9
+ Version **0.3.15** adds `selectApiBindingSources`, `planApiCallBindings` and
10
+ `planApiBindingsByCall` for scoped API binding work. The model no longer needs to
11
+ regenerate a whole selected-call inventory in this path. Code checks complete
12
+ coverage and cross-call cycles, and the host explicitly chooses concurrency and
13
+ stores validated progress for resume. Existing `planApiBindings` stays compatible.
14
+ See [scalable bindings](https://github.com/carefreelife98/openapi-flow/blob/main/docs/scalable-api-binding-planning.md)
15
+ and the [release record](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.15.md).
16
+
3
17
  Version **0.3.14** adds source-owned structured errors and preserves initial
4
18
  argument provenance failures across optional enum selection. Existing prompts,
5
19
  values, schemas and retry behavior are unchanged; the host still chooses the
@@ -1,25 +1,34 @@
1
1
  import { HumanMessage, SystemMessage } from '@langchain/core/messages';
2
- import { captureApiInputAnswerContext, createApiInputAnswerExtractionRecord, resolveApiInputAnswerInterpretations, describeApiInputEnumAnswerChoices, describeApiInputSources, resolveApiInputSourceSelections, } from '@openapi-flow/core';
2
+ import { captureApiInputAnswerContext, createApiInputAnswerExtractionRecord, resolveApiInputAnswerInterpretations, describeApiInputEnumAnswerChoices, describeApiInputSources, resolveApiInputSourceSelections, runOpenApiFlowOperation, } from '@openapi-flow/core';
3
3
  import { createApiInputAnswerSourceSchema, apiInputAnswerExplanationOptionsSchema, } from '../schemas/api-input-answer-source-schema.js';
4
4
  import { apiInputAnswersPrompt } from '../prompts/api-input-answers-prompt.js';
5
5
  import { serializeStructuredOutputSchema } from '../schemas/serialize-structured-output-schema.js';
6
6
  import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
7
+ import { invokeStructuredModel } from '../model/invoke-structured-model.js';
7
8
  export async function analyzeApiInputAnswers(input) {
8
- const options = apiInputAnswerExplanationOptionsSchema.parse({
9
- includeExplanations: input.includeExplanations,
10
- selectOasEnums: input.selectOasEnums,
11
- });
12
- const snapshot = captureApiInputAnswerContext({
13
- reply: input.reply,
14
- requirements: input.requirements,
15
- ...(input.purpose === undefined ? {} : { purpose: input.purpose }),
16
- ...(options.selectOasEnums === undefined
17
- ? {}
18
- : { selectOasEnums: options.selectOasEnums }),
9
+ const { options, snapshot } = runOpenApiFlowOperation({
10
+ owner: 'langchain',
11
+ operation: 'analyzeApiInputAnswers',
12
+ stage: 'input-validation',
13
+ code: 'PLANNING_INPUT_INVALID',
14
+ }, () => {
15
+ const options = apiInputAnswerExplanationOptionsSchema.parse({
16
+ includeExplanations: input.includeExplanations,
17
+ selectOasEnums: input.selectOasEnums,
18
+ });
19
+ const snapshot = captureApiInputAnswerContext({
20
+ reply: input.reply,
21
+ requirements: input.requirements,
22
+ ...(input.purpose === undefined ? {} : { purpose: input.purpose }),
23
+ ...(options.selectOasEnums === undefined
24
+ ? {}
25
+ : { selectOasEnums: options.selectOasEnums }),
26
+ });
27
+ if (input.model !== undefined &&
28
+ (!input.model || typeof input.model.withStructuredOutput !== 'function'))
29
+ throw new Error('analyzeApiInputAnswers.model must support structured output when supplied');
30
+ return { options, snapshot };
19
31
  });
20
- if (input.model !== undefined &&
21
- (!input.model || typeof input.model.withStructuredOutput !== 'function'))
22
- throw new Error('analyzeApiInputAnswers.model must support structured output when supplied');
23
32
  if (!snapshot.requirements.length || snapshot.reply === '')
24
33
  return {
25
34
  status: snapshot.requirements.length
@@ -37,82 +46,107 @@ export async function analyzeApiInputAnswers(input) {
37
46
  sources: [],
38
47
  unansweredRequirementIds: snapshot.requirements.map((item) => item.id),
39
48
  };
40
- if (!input.model)
41
- throw new Error('analyzeApiInputAnswers.model is required for a nonempty reply with pending inputs');
42
- const enumChoices = describeApiInputEnumAnswerChoices(snapshot);
43
- const schema = createApiInputAnswerSourceSchema(snapshot.requirements, catalog, options.includeExplanations === true, enumChoices);
44
- const output = await input.model
45
- .withStructuredOutput(serializeStructuredOutputSchema(schema, 'draft-2020-12'), {
49
+ const { model, schema, wireSchema, enumChoices } = runOpenApiFlowOperation({
50
+ owner: 'langchain',
51
+ operation: 'analyzeApiInputAnswers',
52
+ stage: 'input-validation',
53
+ code: 'PLANNING_INPUT_INVALID',
54
+ }, () => {
55
+ if (!input.model)
56
+ throw new Error('analyzeApiInputAnswers.model is required for a nonempty reply with pending inputs');
57
+ const enumChoices = describeApiInputEnumAnswerChoices(snapshot);
58
+ const schema = createApiInputAnswerSourceSchema(snapshot.requirements, catalog, options.includeExplanations === true, enumChoices);
59
+ return {
60
+ model: input.model,
61
+ schema,
62
+ enumChoices,
63
+ wireSchema: serializeStructuredOutputSchema(schema, 'draft-2020-12'),
64
+ };
65
+ });
66
+ const output = await invokeStructuredModel({
67
+ model,
68
+ schema: wireSchema,
69
+ operation: 'analyzeApiInputAnswers',
46
70
  name: 'extract_api_input_answers',
47
71
  method: 'functionCalling',
48
72
  strict: true,
49
- })
50
- .invoke([
51
- new SystemMessage(apiInputAnswersPrompt),
52
- new HumanMessage(JSON.stringify({
53
- reply: snapshot.reply,
54
- questions: snapshot.requirements,
55
- ...(snapshot.purpose === undefined
56
- ? {}
57
- : { purpose: snapshot.purpose }),
58
- sources: catalog,
59
- ...(enumChoices.length ? { enumChoices } : {}),
60
- })),
61
- ]);
62
- const parsed = parseStructuredOutput(schema, output, 'model clarification answer sources');
63
- const selections = resolveApiInputSourceSelections({
64
- ...snapshot,
65
- selections: parsed.answers.flatMap(({ requirementId, source }) => source.kind === 'oas-enum' ? [] : [{ requirementId, source }]),
73
+ messages: [
74
+ new SystemMessage(apiInputAnswersPrompt),
75
+ new HumanMessage(JSON.stringify({
76
+ reply: snapshot.reply,
77
+ questions: snapshot.requirements,
78
+ ...(snapshot.purpose === undefined
79
+ ? {}
80
+ : { purpose: snapshot.purpose }),
81
+ sources: catalog,
82
+ ...(enumChoices.length ? { enumChoices } : {}),
83
+ })),
84
+ ],
66
85
  });
67
- const enumSelections = parsed.answers.flatMap((answer) => {
68
- if (answer.source.kind !== 'oas-enum')
69
- return [];
70
- if (typeof answer.reason !== 'string')
71
- throw new Error(`Enum answer ${answer.requirementId} requires an explanation`);
72
- const [intent] = resolveApiInputSourceSelections({
86
+ return runOpenApiFlowOperation({
87
+ owner: 'langchain',
88
+ operation: 'analyzeApiInputAnswers',
89
+ stage: 'contract-validation',
90
+ code: 'API_ARGUMENT_VALIDATION_FAILED',
91
+ }, () => {
92
+ const parsed = parseStructuredOutput(schema, output, 'model clarification answer sources');
93
+ const selections = resolveApiInputSourceSelections({
73
94
  ...snapshot,
74
- selections: [
75
- { requirementId: answer.requirementId, source: answer.source.intent },
76
- ],
95
+ selections: parsed.answers.flatMap(({ requirementId, source }) => source.kind === 'oas-enum' ? [] : [{ requirementId, source }]),
77
96
  });
78
- return [
79
- {
80
- requirementId: answer.requirementId,
81
- candidateId: answer.source.candidateId,
82
- intent: {
83
- sourceFormat: intent.sourceFormat,
84
- sourceText: intent.sourceText,
97
+ const enumSelections = parsed.answers.flatMap((answer) => {
98
+ if (answer.source.kind !== 'oas-enum')
99
+ return [];
100
+ if (typeof answer.reason !== 'string')
101
+ throw new Error(`Enum answer ${answer.requirementId} requires an explanation`);
102
+ const [intent] = resolveApiInputSourceSelections({
103
+ ...snapshot,
104
+ selections: [
105
+ {
106
+ requirementId: answer.requirementId,
107
+ source: answer.source.intent,
108
+ },
109
+ ],
110
+ });
111
+ return [
112
+ {
113
+ requirementId: answer.requirementId,
114
+ candidateId: answer.source.candidateId,
115
+ intent: {
116
+ sourceFormat: intent.sourceFormat,
117
+ sourceText: intent.sourceText,
118
+ },
119
+ reason: answer.reason,
85
120
  },
86
- reason: answer.reason,
87
- },
88
- ];
89
- });
90
- const resolved = resolveApiInputAnswerInterpretations({
91
- ...snapshot,
92
- selections,
93
- ...(enumSelections.length ? { enumSelections } : {}),
94
- });
95
- let extraction;
96
- if (options.includeExplanations === true) {
97
- if (typeof parsed.reason !== 'string')
98
- throw new Error('model clarification answer sources require an explanation');
99
- extraction = createApiInputAnswerExtractionRecord({
121
+ ];
122
+ });
123
+ const resolved = resolveApiInputAnswerInterpretations({
100
124
  ...snapshot,
101
- decision: {
102
- reason: parsed.reason,
103
- answers: selections.map((selection) => {
104
- const source = parsed.answers.find((answer) => answer.requirementId === selection.requirementId);
105
- if (typeof source?.reason !== 'string')
106
- throw new Error(`model answer ${selection.requirementId} requires an explanation`);
107
- return { ...selection, reason: source.reason };
108
- }),
109
- ...(enumSelections.length ? { enumSelections } : {}),
110
- },
125
+ selections,
126
+ ...(enumSelections.length ? { enumSelections } : {}),
111
127
  });
112
- }
113
- return {
114
- status: 'extracted',
115
- ...resolved,
116
- ...(extraction === undefined ? {} : { extraction }),
117
- };
128
+ let extraction;
129
+ if (options.includeExplanations === true) {
130
+ if (typeof parsed.reason !== 'string')
131
+ throw new Error('model clarification answer sources require an explanation');
132
+ extraction = createApiInputAnswerExtractionRecord({
133
+ ...snapshot,
134
+ decision: {
135
+ reason: parsed.reason,
136
+ answers: selections.map((selection) => {
137
+ const source = parsed.answers.find((answer) => answer.requirementId === selection.requirementId);
138
+ if (typeof source?.reason !== 'string')
139
+ throw new Error(`model answer ${selection.requirementId} requires an explanation`);
140
+ return { ...selection, reason: source.reason };
141
+ }),
142
+ ...(enumSelections.length ? { enumSelections } : {}),
143
+ },
144
+ });
145
+ }
146
+ return {
147
+ status: 'extracted',
148
+ ...resolved,
149
+ ...(extraction === undefined ? {} : { extraction }),
150
+ };
151
+ });
118
152
  }
@@ -1,23 +1,36 @@
1
1
  import { SystemMessage, HumanMessage } from '@langchain/core/messages';
2
- import { describeApiLiteralGeneration, createApiLiteralGenerationRecord, applyConfirmedApiInputs, createApiRequestSchema, inspectApiArgumentRequirements, resolveApiLiteralProposalSources, } from '@openapi-flow/core';
2
+ import { describeApiLiteralGeneration, createApiLiteralGenerationRecord, applyConfirmedApiInputs, createApiRequestSchema, inspectApiArgumentRequirements, resolveApiLiteralProposalSources, runOpenApiFlowOperation, runOpenApiFlowAsyncOperation, } from '@openapi-flow/core';
3
3
  import { apiLiteralGenerationOptionsSchema, createApiLiteralGenerationOutputSchema, } from '../schemas/api-literal-generation-output-schema.js';
4
4
  import { apiArgumentsPrompt } from '../prompts/api-arguments-prompt.js';
5
5
  import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
6
6
  import { interpretApiArgumentEnums } from './interpret-api-argument-enums.js';
7
+ import { invokeStructuredModel } from '../model/invoke-structured-model.js';
7
8
  export async function generateApiArguments(input) {
8
- if (input.model !== undefined &&
9
- (!input.model || typeof input.model.withStructuredOutput !== 'function'))
10
- throw new Error('generateApiArguments.model must support structured output when supplied');
11
- if (typeof input.callId !== 'string' || !input.callId.trim())
12
- throw new Error('callId must be non-empty');
13
- const options = apiLiteralGenerationOptionsSchema.parse({
14
- includeExplanations: input.includeExplanations,
15
- selectOasEnums: input.selectOasEnums,
16
- trackRequestedInputs: input.trackRequestedInputs,
9
+ const { options, snapshot, contract, context, trackInputs, schema } = runOpenApiFlowOperation({
10
+ owner: 'langchain',
11
+ operation: 'generateApiArguments',
12
+ stage: 'input-validation',
13
+ code: 'PLANNING_INPUT_INVALID',
14
+ }, () => {
15
+ if (input.model !== undefined &&
16
+ (!input.model ||
17
+ typeof input.model.withStructuredOutput !== 'function'))
18
+ throw new Error('generateApiArguments.model must support structured output when supplied');
19
+ if (typeof input.callId !== 'string' || !input.callId.trim())
20
+ throw new Error('callId must be non-empty');
21
+ const options = apiLiteralGenerationOptionsSchema.parse({
22
+ includeExplanations: input.includeExplanations,
23
+ selectOasEnums: input.selectOasEnums,
24
+ trackRequestedInputs: input.trackRequestedInputs,
25
+ });
26
+ const { input: snapshot, contract, context, } = describeApiLiteralGeneration(input);
27
+ const trackInputs = options.trackRequestedInputs === true ||
28
+ options.selectOasEnums === true;
29
+ const schema = createApiLiteralGenerationOutputSchema(contract, options.includeExplanations === true, trackInputs ? createApiRequestSchema(snapshot) : undefined);
30
+ if (contract.hasLiteralInputs && !input.model)
31
+ throw new Error('generateApiArguments.model must support structured output when literal inputs remain');
32
+ return { options, snapshot, contract, context, trackInputs, schema };
17
33
  });
18
- const { input: snapshot, contract, context, } = describeApiLiteralGeneration(input);
19
- const trackInputs = options.trackRequestedInputs === true || options.selectOasEnums === true;
20
- const schema = createApiLiteralGenerationOutputSchema(contract, options.includeExplanations === true, trackInputs ? createApiRequestSchema(snapshot) : undefined);
21
34
  let proposed = {
22
35
  values: {},
23
36
  ...(trackInputs ? { requestedInputs: [], literalSources: [] } : {}),
@@ -26,78 +39,89 @@ export async function generateApiArguments(input) {
26
39
  const model = input.model;
27
40
  if (!model || typeof model.withStructuredOutput !== 'function')
28
41
  throw new Error('generateApiArguments.model must support structured output when literal inputs remain');
29
- proposed = await model
30
- .withStructuredOutput(schema, {
42
+ proposed = await invokeStructuredModel({
43
+ model,
44
+ schema,
45
+ operation: 'generateApiArguments',
31
46
  name: 'generate_api_arguments',
32
47
  method: 'functionCalling',
33
- })
34
- .invoke([
35
- new SystemMessage(apiArgumentsPrompt),
36
- new HumanMessage(JSON.stringify({
37
- ...context,
38
- ...(options.selectOasEnums === true
39
- ? { selectOasEnums: true }
40
- : {}),
41
- })),
42
- ]);
48
+ // Preserve the existing provider-default strictness of literal generation.
49
+ strict: undefined,
50
+ messages: [
51
+ new SystemMessage(apiArgumentsPrompt),
52
+ new HumanMessage(JSON.stringify({
53
+ ...context,
54
+ ...(options.selectOasEnums === true
55
+ ? { selectOasEnums: true }
56
+ : {}),
57
+ })),
58
+ ],
59
+ });
43
60
  }
44
- const parsed = parseStructuredOutput(schema, proposed, `model arguments for ${snapshot.callId}`);
45
- const values = applyConfirmedApiInputs({
46
- ...snapshot,
47
- values: parsed.values,
48
- });
49
- const requestedInputs = 'requestedInputs' in parsed ? parsed.requestedInputs : undefined;
50
- const literalSources = 'literalSources' in parsed
51
- ? resolveApiLiteralProposalSources({
52
- callId: snapshot.callId,
61
+ return runOpenApiFlowAsyncOperation({
62
+ owner: 'langchain',
63
+ operation: 'generateApiArguments',
64
+ stage: 'contract-validation',
65
+ code: 'API_ARGUMENT_VALIDATION_FAILED',
66
+ }, async () => {
67
+ const parsed = parseStructuredOutput(schema, proposed, `model arguments for ${snapshot.callId}`);
68
+ const values = applyConfirmedApiInputs({
69
+ ...snapshot,
53
70
  values: parsed.values,
54
- sources: parsed.literalSources.map((source) => ({
55
- ...source,
56
- messageId: 'scenario',
57
- })),
58
- messages: [{ id: 'scenario', text: snapshot.scenario }],
59
- })
60
- : undefined;
61
- const inspection = inspectApiArgumentRequirements({
62
- ...snapshot,
63
- values,
64
- requestedInputs,
65
- });
66
- const result = {
67
- callId: snapshot.callId,
68
- values,
69
- bindings: structuredClone(snapshot.bindings),
70
- unresolvedInputs: inspection.requirements.map((item) => item.targetPointer),
71
- ...(literalSources === undefined ? {} : { literalSources }),
72
- ...(requestedInputs === undefined ? {} : { requestedInputs }),
73
- ...(snapshot.requestMediaType === undefined
74
- ? {}
75
- : { requestMediaType: snapshot.requestMediaType }),
76
- ...(options.includeExplanations === true
77
- ? {
78
- literalGeneration: createApiLiteralGenerationRecord({
79
- ...snapshot,
80
- ...('explanation' in parsed
81
- ? {
82
- proposal: { values: parsed.values },
83
- reason: parsed.explanation.reason,
84
- }
85
- : {}),
86
- }),
87
- }
88
- : {}),
89
- };
90
- if (options.selectOasEnums === true) {
91
- if (literalSources === undefined)
92
- throw new Error('generateApiArguments enum interpretation requires source-verified literalSources');
93
- return interpretApiArgumentEnums({
71
+ });
72
+ const requestedInputs = 'requestedInputs' in parsed ? parsed.requestedInputs : undefined;
73
+ const literalSources = 'literalSources' in parsed
74
+ ? resolveApiLiteralProposalSources({
75
+ callId: snapshot.callId,
76
+ values: parsed.values,
77
+ sources: parsed.literalSources.map((source) => ({
78
+ ...source,
79
+ messageId: 'scenario',
80
+ })),
81
+ messages: [{ id: 'scenario', text: snapshot.scenario }],
82
+ })
83
+ : undefined;
84
+ const inspection = inspectApiArgumentRequirements({
94
85
  ...snapshot,
95
- ...options,
96
- arguments: result,
97
- proposal: { values: parsed.values },
98
- literalSources,
99
- model: input.model,
86
+ values,
87
+ requestedInputs,
100
88
  });
101
- }
102
- return result;
89
+ const result = {
90
+ callId: snapshot.callId,
91
+ values,
92
+ bindings: structuredClone(snapshot.bindings),
93
+ unresolvedInputs: inspection.requirements.map((item) => item.targetPointer),
94
+ ...(literalSources === undefined ? {} : { literalSources }),
95
+ ...(requestedInputs === undefined ? {} : { requestedInputs }),
96
+ ...(snapshot.requestMediaType === undefined
97
+ ? {}
98
+ : { requestMediaType: snapshot.requestMediaType }),
99
+ ...(options.includeExplanations === true
100
+ ? {
101
+ literalGeneration: createApiLiteralGenerationRecord({
102
+ ...snapshot,
103
+ ...('explanation' in parsed
104
+ ? {
105
+ proposal: { values: parsed.values },
106
+ reason: parsed.explanation.reason,
107
+ }
108
+ : {}),
109
+ }),
110
+ }
111
+ : {}),
112
+ };
113
+ if (options.selectOasEnums === true) {
114
+ if (literalSources === undefined)
115
+ throw new Error('generateApiArguments enum interpretation requires source-verified literalSources');
116
+ return interpretApiArgumentEnums({
117
+ ...snapshot,
118
+ ...options,
119
+ arguments: result,
120
+ proposal: { values: parsed.values },
121
+ literalSources,
122
+ model: input.model,
123
+ });
124
+ }
125
+ return result;
126
+ });
103
127
  }
@@ -0,0 +1,4 @@
1
+ import type { ApiBindingPlan } from '@openapi-flow/core';
2
+ import type { PlanApiBindingsByCallInput } from '../types/api-call-binding-planning.js';
3
+ /** Resource-bounded scheduling; each result can also be checkpointed by a host graph. */
4
+ export declare function planApiBindingsByCall(input: PlanApiBindingsByCallInput): Promise<ApiBindingPlan>;
@@ -0,0 +1,77 @@
1
+ import { describeApiBindingPlanning, validateApiCallBindingPlan, assembleApiBindingPlan, runOpenApiFlowOperation, } from '@openapi-flow/core';
2
+ import { apiBindingCallExecutionSchema } from '../schemas/api-call-binding-plan-schema.js';
3
+ import { selectApiBindingSources } from './select-api-binding-sources.js';
4
+ import { planApiCallBindings } from './plan-api-call-bindings.js';
5
+ /** Resource-bounded scheduling; each result can also be checkpointed by a host graph. */
6
+ export async function planApiBindingsByCall(input) {
7
+ const { snapshot, concurrency, completed } = runOpenApiFlowOperation({
8
+ owner: 'langchain',
9
+ operation: 'planApiBindingsByCall',
10
+ stage: 'input-validation',
11
+ code: 'PLANNING_INPUT_INVALID',
12
+ }, () => {
13
+ const { concurrency } = apiBindingCallExecutionSchema.parse(input);
14
+ if (input.onCallPlanned !== undefined &&
15
+ typeof input.onCallPlanned !== 'function')
16
+ throw new Error('onCallPlanned must be a function when supplied');
17
+ const snapshot = describeApiBindingPlanning(input).input;
18
+ const completed = input.completedCalls === undefined
19
+ ? []
20
+ : structuredClone(input.completedCalls);
21
+ if (!Array.isArray(completed))
22
+ throw new Error('completedCalls must be an array');
23
+ if (new Set(completed.map((item) => item.callId)).size !== completed.length)
24
+ throw new Error('completedCalls contains duplicate API call identities');
25
+ for (const result of completed)
26
+ validateApiCallBindingPlan({
27
+ ...snapshot,
28
+ callId: result.callId,
29
+ includeExplanations: input.includeExplanations,
30
+ result,
31
+ });
32
+ return { snapshot, concurrency, completed };
33
+ });
34
+ input.signal?.throwIfAborted();
35
+ const results = new Map(completed.map((result) => [result.callId, result]));
36
+ const pending = snapshot.materials.filter((material) => !results.has(material.callId));
37
+ let next = 0;
38
+ let failed = false;
39
+ let failure;
40
+ async function worker() {
41
+ while (!failed && next < pending.length) {
42
+ const material = pending[next++];
43
+ try {
44
+ input.signal?.throwIfAborted();
45
+ const callInput = {
46
+ ...snapshot,
47
+ callId: material.callId,
48
+ includeExplanations: input.includeExplanations,
49
+ model: input.model,
50
+ signal: input.signal,
51
+ };
52
+ const sources = await selectApiBindingSources(callInput);
53
+ if (failed)
54
+ return;
55
+ const result = await planApiCallBindings({ ...callInput, sources });
56
+ input.signal?.throwIfAborted();
57
+ if (input.onCallPlanned !== undefined)
58
+ await input.onCallPlanned(structuredClone(result));
59
+ results.set(result.callId, result);
60
+ }
61
+ catch (error) {
62
+ if (!failed)
63
+ failure = error;
64
+ failed = true;
65
+ }
66
+ }
67
+ }
68
+ await Promise.all(Array.from({ length: Math.min(concurrency, pending.length) }, () => worker()));
69
+ if (failed)
70
+ throw failure;
71
+ input.signal?.throwIfAborted();
72
+ return assembleApiBindingPlan({
73
+ ...snapshot,
74
+ results: [...results.values()],
75
+ includeExplanations: input.includeExplanations,
76
+ });
77
+ }
@@ -26,6 +26,7 @@ export async function planApiBindings(input) {
26
26
  operation: 'planApiBindings',
27
27
  name: 'plan_api_bindings',
28
28
  method: 'functionCalling',
29
+ strict: true,
29
30
  messages: [
30
31
  new SystemMessage(apiBindingsPrompt),
31
32
  new HumanMessage(JSON.stringify(context)),
@@ -0,0 +1,3 @@
1
+ import type { ApiCallBindingPlan } from '@openapi-flow/core';
2
+ import type { PlanApiCallBindingsInput } from '../types/api-call-binding-planning.js';
3
+ export declare function planApiCallBindings(input: PlanApiCallBindingsInput): Promise<ApiCallBindingPlan>;
@@ -0,0 +1,82 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { HumanMessage, SystemMessage } from '@langchain/core/messages';
3
+ import { describeApiCallBindingPlanning, resolveApiBindingSourceSelection, resolveApiCallBindingPlan, listApiResponsePointers, runOpenApiFlowOperation, } from '@openapi-flow/core';
4
+ import { createApiCallBindingPlanSchema } from '../schemas/api-call-binding-plan-schema.js';
5
+ import { apiCallBindingsPrompt } from '../prompts/api-call-bindings-prompt.js';
6
+ import { invokeStructuredModel } from '../model/invoke-structured-model.js';
7
+ import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
8
+ export async function planApiCallBindings(input) {
9
+ const { description, sources } = runOpenApiFlowOperation({
10
+ owner: 'langchain',
11
+ operation: 'planApiCallBindings',
12
+ stage: 'input-validation',
13
+ code: 'PLANNING_INPUT_INVALID',
14
+ }, () => {
15
+ const description = describeApiCallBindingPlanning(input);
16
+ const sources = structuredClone(input.sources);
17
+ const expected = resolveApiBindingSourceSelection({
18
+ ...description.input,
19
+ proposal: {
20
+ sourceNodeIds: sources.sourceNodeIds,
21
+ gaps: sources.gaps,
22
+ ...(sources.reason === undefined ? {} : { reason: sources.reason }),
23
+ },
24
+ });
25
+ if (!isDeepStrictEqual(sources, expected))
26
+ throw new Error('binding source selection changed or belongs to another call or source snapshot');
27
+ return { description, sources };
28
+ });
29
+ input.signal?.throwIfAborted();
30
+ if (!sources.sourceNodeIds.length)
31
+ return resolveApiCallBindingPlan({
32
+ ...description.input,
33
+ sources,
34
+ proposal: {
35
+ bindings: [],
36
+ gaps: [],
37
+ ...(sources.reason === undefined ? {} : { reason: sources.reason }),
38
+ },
39
+ });
40
+ const selected = new Set(sources.sourceNodeIds);
41
+ const schema = createApiCallBindingPlanSchema(description, sources.sourceNodeIds);
42
+ const output = await invokeStructuredModel({
43
+ model: input.model,
44
+ schema,
45
+ operation: 'planApiCallBindings',
46
+ name: 'plan_api_call_bindings',
47
+ method: 'functionCalling',
48
+ strict: true,
49
+ signal: input.signal,
50
+ messages: [
51
+ new SystemMessage(apiCallBindingsPrompt),
52
+ new HumanMessage(JSON.stringify({
53
+ scenario: description.input.scenario,
54
+ target: description.target,
55
+ requestSchema: description.requestSchema,
56
+ apiSources: description.input.materials
57
+ .filter((material) => selected.has(material.callId))
58
+ .map((material) => ({
59
+ nodeId: material.callId,
60
+ key: material.operation.key,
61
+ method: material.operation.method,
62
+ path: material.operation.path,
63
+ purpose: material.purpose,
64
+ responses: material.operation.operation.responses,
65
+ responsePointerCatalog: listApiResponsePointers(material.operation),
66
+ })),
67
+ nativeSources: description.input.nativeOutputs?.filter((source) => selected.has(source.nodeId)),
68
+ })),
69
+ ],
70
+ });
71
+ input.signal?.throwIfAborted();
72
+ return runOpenApiFlowOperation({
73
+ owner: 'langchain',
74
+ operation: 'planApiCallBindings',
75
+ stage: 'contract-validation',
76
+ code: 'API_BINDING_VALIDATION_FAILED',
77
+ }, () => resolveApiCallBindingPlan({
78
+ ...description.input,
79
+ sources,
80
+ proposal: parseStructuredOutput(schema, output, 'API call bindings'),
81
+ }));
82
+ }
@@ -0,0 +1,3 @@
1
+ import type { ApiBindingSourceSelection } from '@openapi-flow/core';
2
+ import type { SelectApiBindingSourcesInput } from '../types/api-call-binding-planning.js';
3
+ export declare function selectApiBindingSources(input: SelectApiBindingSourcesInput): Promise<ApiBindingSourceSelection>;