@openapi-flow/langchain 0.3.13 → 0.3.14

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 CHANGED
@@ -1,6 +1,21 @@
1
1
  # @openapi-flow/langchain
2
2
 
3
- Zod `^4.6.5` is a required host peer, alongside `@langchain/core`. The three libraries share the host's Zod for public schema types instead of creating an isolated pinned copy. Modern package managers install compatible missing peers; a host that directly creates Zod schemas should declare its own compatible Zod dependency.
3
+ Version **0.3.14** adds source-owned structured errors and preserves initial
4
+ argument provenance failures across optional enum selection. Existing prompts,
5
+ values, schemas and retry behavior are unchanged; the host still chooses the
6
+ planning inventory. The owner accepted the n8n adapter's unresolved upstream
7
+ Zod 3 advisory for this release on 2026-10-25; this does not clear the audit.
8
+ See the [0.3.14 release record](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.14.md)
9
+ for actual publication and host status; preparation is not publication.
10
+
11
+ API selection and binding planning expose Core's shared `OpenApiFlowError`
12
+ diagnostic. Existing native/graph planning error subclasses retain their identity,
13
+ failure payload and cause. A typed outer error can now wrap a previously untyped
14
+ exception; use `cause` for its original identity and serialize only the diagnostic.
15
+ No model-output repair or hidden retry is added, and not every internal model
16
+ call/provider-specific timeout has been migrated.
17
+
18
+ Zod `^4.6.7` is a required host peer, alongside `@langchain/core`. The three libraries share the host's Zod for public schema types instead of creating an isolated pinned copy. Modern package managers install compatible missing peers; a host that directly creates Zod schemas should declare its own compatible Zod dependency.
4
19
 
5
20
  Version **0.3.13**, with matching `@openapi-flow/core@0.3.13`. Native edit target selection and value generation share actual code-derived API/native source identities and response contracts. Connection edits also forward the optional supplied entrypoint rather than infer one. Model output remains subject to the existing deterministic validators; no output filtering or hidden retries are added. Optional enum interpretation from 0.3.11/0.3.12 remains available. The host supplies its LangChain model and owns LangGraph, storage and review policies. The Core/n8n Ajv `properties.__proto__` limitation and actual publication/host status are documented in the [release record](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.13.md); this release does not rewrite OAS or values to hide it.
6
21
 
@@ -1,26 +1,36 @@
1
1
  import { SystemMessage, HumanMessage } from '@langchain/core/messages';
2
- import { validateApiBindingAssignments, validateApiBindingGaps, describeApiBindingPlanning, createApiBindingPlanningRecord, } from '@openapi-flow/core';
2
+ import { validateApiBindingAssignments, validateApiBindingGaps, describeApiBindingPlanning, createApiBindingPlanningRecord, runOpenApiFlowOperation, } from '@openapi-flow/core';
3
3
  import { createApiBindingPlanSchema, apiBindingPlanningOptionsSchema, } from '../schemas/api-binding-plan-schema.js';
4
4
  import { apiBindingsPrompt } from '../prompts/api-bindings-prompt.js';
5
5
  import { assertSelectionInput } from '../legacy/planning/select-operation.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 planApiBindings(input) {
8
- assertSelectionInput(input.scenario, input.model);
9
- const options = apiBindingPlanningOptionsSchema.parse({
10
- includeExplanations: input.includeExplanations,
9
+ const { options, snapshot, context, schema } = runOpenApiFlowOperation({
10
+ owner: 'langchain',
11
+ operation: 'planApiBindings',
12
+ stage: 'input-validation',
13
+ code: 'PLANNING_INPUT_INVALID',
14
+ }, () => {
15
+ assertSelectionInput(input.scenario, input.model);
16
+ const options = apiBindingPlanningOptionsSchema.parse({
17
+ includeExplanations: input.includeExplanations,
18
+ });
19
+ const { input: snapshot, context } = describeApiBindingPlanning(input);
20
+ const schema = createApiBindingPlanSchema(snapshot.materials, snapshot.nativeOutputs, options.includeExplanations === true);
21
+ return { options, snapshot, context, schema };
11
22
  });
12
- const { input: snapshot, context } = describeApiBindingPlanning(input);
13
- const schema = createApiBindingPlanSchema(snapshot.materials, snapshot.nativeOutputs, options.includeExplanations === true);
14
- const proposed = await input.model
15
- .withStructuredOutput(schema, {
23
+ const proposed = await invokeStructuredModel({
24
+ model: input.model,
25
+ schema,
26
+ operation: 'planApiBindings',
16
27
  name: 'plan_api_bindings',
17
28
  method: 'functionCalling',
18
- strict: true,
19
- })
20
- .invoke([
21
- new SystemMessage(apiBindingsPrompt),
22
- new HumanMessage(JSON.stringify(context)),
23
- ]);
29
+ messages: [
30
+ new SystemMessage(apiBindingsPrompt),
31
+ new HumanMessage(JSON.stringify(context)),
32
+ ],
33
+ });
24
34
  const parsed = parseStructuredOutput(schema, proposed, 'model API bindings');
25
35
  const plan = {
26
36
  calls: parsed.calls.map(({ callId, bindings }) => ({
@@ -36,7 +46,12 @@ export async function planApiBindings(input) {
36
46
  });
37
47
  validateApiBindingGaps({ plan, materials: snapshot.materials });
38
48
  if (options.includeExplanations === true) {
39
- plan.bindingPlanning = createApiBindingPlanningRecord({
49
+ plan.bindingPlanning = runOpenApiFlowOperation({
50
+ owner: 'langchain',
51
+ operation: 'planApiBindings',
52
+ stage: 'contract-validation',
53
+ code: 'API_BINDING_VALIDATION_FAILED',
54
+ }, () => createApiBindingPlanningRecord({
40
55
  ...snapshot,
41
56
  plan,
42
57
  reasons: parsed.calls.map((call) => {
@@ -44,7 +59,7 @@ export async function planApiBindings(input) {
44
59
  throw new Error(`model API bindings are missing reason for ${call.callId}`);
45
60
  return { callId: call.callId, reason: call.reason };
46
61
  }),
47
- });
62
+ }));
48
63
  }
49
64
  return plan;
50
65
  }
@@ -5,11 +5,16 @@ import { serializeStructuredOutputSchema } from '../schemas/serialize-structured
5
5
  import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
6
6
  import { WorkflowDraftApiCallPlanningError } from './workflow-draft-api-call-planning-error.js';
7
7
  /** A second call only when actual missing OAS enum choices remain; never a repair retry. */
8
- export async function selectWorkflowDraftApiEnums(input) {
8
+ export function selectWorkflowDraftApiEnums(input) {
9
9
  const original = resolveWorkflowDraftApiCallArguments(input);
10
- const { candidates, targets } = describeWorkflowDraftApiEnumChoices(input);
11
- if (!candidates.length)
12
- return original;
10
+ const choices = describeWorkflowDraftApiEnumChoices(input);
11
+ if (!choices.candidates.length)
12
+ return Promise.resolve(original);
13
+ return selectMissingWorkflowDraftApiEnums(input, choices);
14
+ }
15
+ /** Model transport remains outside the enum-output validation boundary. */
16
+ async function selectMissingWorkflowDraftApiEnums(input, choices) {
17
+ const { candidates, targets } = choices;
13
18
  const description = describeWorkflowDraftApiCallArguments(input);
14
19
  const schema = createWorkflowDraftApiEnumSelectionSchema(input);
15
20
  const output = await input.model
@@ -1,9 +1,17 @@
1
+ import { OpenApiFlowError } from '@openapi-flow/core';
1
2
  export function parseStructuredOutput(schema, value, source) {
2
3
  const parsed = schema.safeParse(value);
3
4
  if (parsed.success)
4
5
  return parsed.data;
5
6
  const issues = parsed.error.issues.map((issue) => `${issue.path.join('.') || '<root>'}: ${issue.message}`);
6
- throw new Error(`${source} is invalid: ${issues.join('; ')}`, {
7
+ throw new OpenApiFlowError({
8
+ message: `${source} is invalid: ${issues.join('; ')}`,
9
+ diagnostic: {
10
+ owner: 'langchain',
11
+ operation: 'parseStructuredOutput',
12
+ stage: 'model-output',
13
+ code: 'MODEL_OUTPUT_INVALID',
14
+ },
7
15
  cause: parsed.error,
8
16
  });
9
17
  }
@@ -0,0 +1,3 @@
1
+ import type { InvokeStructuredModelInput } from '../types/structured-model.js';
2
+ /** Provider-neutral: a failed invoke is not proof of a network or timeout failure. */
3
+ export declare function invokeStructuredModel<Output extends object>(input: InvokeStructuredModelInput<Output>): Promise<Output>;
@@ -0,0 +1,50 @@
1
+ import { OutputParserException } from '@langchain/core/output_parsers';
2
+ import { OpenApiFlowError, runOpenApiFlowOperation } from '@openapi-flow/core';
3
+ /** Provider-neutral: a failed invoke is not proof of a network or timeout failure. */
4
+ export async function invokeStructuredModel(input) {
5
+ const runnable = runOpenApiFlowOperation({
6
+ owner: 'langchain',
7
+ operation: input.operation,
8
+ stage: 'model-configuration',
9
+ code: 'MODEL_CONFIGURATION_FAILED',
10
+ }, () => input.model.withStructuredOutput(input.schema, {
11
+ name: input.name,
12
+ method: input.method,
13
+ strict: true,
14
+ }));
15
+ try {
16
+ return await runnable.invoke(input.messages);
17
+ }
18
+ catch (cause) {
19
+ if (cause instanceof OpenApiFlowError)
20
+ throw cause;
21
+ const diagnostic = {
22
+ owner: 'langchain',
23
+ operation: input.operation,
24
+ stage: cause instanceof OutputParserException
25
+ ? 'model-output'
26
+ : 'model-invocation',
27
+ code: cause instanceof OutputParserException
28
+ ? 'MODEL_OUTPUT_INVALID'
29
+ : cause instanceof DOMException && cause.name === 'TimeoutError'
30
+ ? 'MODEL_TIMEOUT'
31
+ : cause instanceof DOMException && cause.name === 'AbortError'
32
+ ? 'MODEL_ABORTED'
33
+ : 'MODEL_INVOCATION_FAILED',
34
+ };
35
+ // Record only this error's explicit HTTP status. No message/name/cause heuristics.
36
+ const status = typeof cause === 'object' && cause !== null
37
+ ? Object.getOwnPropertyDescriptor(cause, 'status')?.value
38
+ : undefined;
39
+ if (typeof status === 'number' &&
40
+ Number.isInteger(status) &&
41
+ status >= 100 &&
42
+ status <= 599)
43
+ diagnostic.httpStatus = status;
44
+ throw new OpenApiFlowError({
45
+ message: cause instanceof Error ? cause.message : input.operation,
46
+ diagnostic,
47
+ cause,
48
+ });
49
+ }
50
+ }
@@ -1,22 +1,32 @@
1
1
  import { SystemMessage, HumanMessage } from '@langchain/core/messages';
2
- import { describeApiSelectionContext, createApiSelectionExplanation, } from '@openapi-flow/core';
2
+ import { describeApiSelectionContext, createApiSelectionExplanation, runOpenApiFlowOperation, } from '@openapi-flow/core';
3
3
  import { createApiSelectionSchema } from '../schemas/api-selection-schema.js';
4
4
  import { operationSelectionPrompt } from '../prompts/operation-selection-prompt.js';
5
5
  import { assertSelectionInput } from '../legacy/planning/select-operation.js';
6
6
  import { parseStructuredOutput } from '../legacy/planning/parse-structured-output.js';
7
7
  import { resolveApiSelectionOptions } from './resolve-api-selection-options.js';
8
8
  import { apiSelectionExplanationPrompt } from '../prompts/api-selection-explanation-prompt.js';
9
+ import { invokeStructuredModel } from '../model/invoke-structured-model.js';
9
10
  export async function selectApiOperations(input) {
10
11
  const { scenario, model } = input;
11
- assertSelectionInput(scenario, model);
12
- if (!Array.isArray(input.operations))
13
- throw new Error('operations must be an array');
14
- const includeExplanations = resolveApiSelectionOptions({
15
- includeExplanations: input.includeExplanations,
16
- });
17
- const context = describeApiSelectionContext({
18
- scenario,
19
- operations: input.operations,
12
+ const { includeExplanations, context } = runOpenApiFlowOperation({
13
+ owner: 'langchain',
14
+ operation: 'selectApiOperations',
15
+ stage: 'input-validation',
16
+ code: 'PLANNING_INPUT_INVALID',
17
+ }, () => {
18
+ assertSelectionInput(scenario, model);
19
+ if (!Array.isArray(input.operations))
20
+ throw new Error('operations must be an array');
21
+ return {
22
+ includeExplanations: resolveApiSelectionOptions({
23
+ includeExplanations: input.includeExplanations,
24
+ }),
25
+ context: describeApiSelectionContext({
26
+ scenario,
27
+ operations: input.operations,
28
+ }),
29
+ };
20
30
  });
21
31
  const { operations } = context;
22
32
  if (operations.length === 0)
@@ -34,51 +44,59 @@ export async function selectApiOperations(input) {
34
44
  candidateId: `candidate-${index + 1}`,
35
45
  }));
36
46
  const schema = createApiSelectionSchema(candidates.map((candidate) => candidate.candidateId), includeExplanations);
37
- const proposed = await model
38
- .withStructuredOutput(schema, {
47
+ const proposed = await invokeStructuredModel({
48
+ model,
49
+ schema,
50
+ operation: 'selectApiOperations',
39
51
  name: 'select_api_operations',
40
52
  method: 'jsonSchema',
41
- strict: true,
42
- })
43
- .invoke([
44
- new SystemMessage(`${operationSelectionPrompt}\n${apiSelectionExplanationPrompt}`),
45
- new HumanMessage(JSON.stringify({ scenario, candidates })),
46
- ]);
47
- const selection = parseStructuredOutput(schema, proposed, 'model API selection');
48
- if (selection.operations.length === 0 && selection.gaps.length === 0)
49
- throw new Error('model API selection must contain operations or gaps');
50
- const byId = new Map(candidates.map((candidate) => [candidate.candidateId, candidate]));
51
- return {
52
- operations: selection.operations.map((item) => {
53
- const candidate = byId.get(item.candidateId);
54
- if (!candidate)
55
- throw new Error(`model API selection references unknown ${item.candidateId}`);
56
- return {
57
- key: { ...candidate.key },
58
- purpose: item.purpose,
59
- ...('explanation' in item
53
+ messages: [
54
+ new SystemMessage(`${operationSelectionPrompt}\n${apiSelectionExplanationPrompt}`),
55
+ new HumanMessage(JSON.stringify({ scenario, candidates })),
56
+ ],
57
+ });
58
+ return runOpenApiFlowOperation({
59
+ owner: 'langchain',
60
+ operation: 'selectApiOperations',
61
+ stage: 'contract-validation',
62
+ code: 'API_SELECTION_INVALID',
63
+ }, () => {
64
+ const selection = parseStructuredOutput(schema, proposed, 'model API selection');
65
+ if (selection.operations.length === 0 && selection.gaps.length === 0)
66
+ throw new Error('model API selection must contain operations or gaps');
67
+ const byId = new Map(candidates.map((candidate) => [candidate.candidateId, candidate]));
68
+ return {
69
+ operations: selection.operations.map((item) => {
70
+ const candidate = byId.get(item.candidateId);
71
+ if (!candidate)
72
+ throw new Error(`model API selection references unknown ${item.candidateId}`);
73
+ return {
74
+ key: { ...candidate.key },
75
+ purpose: item.purpose,
76
+ ...('explanation' in item
77
+ ? {
78
+ explanation: createApiSelectionExplanation({
79
+ context,
80
+ basis: { kind: 'candidates', operations: [candidate.key] },
81
+ reason: item.explanation.reason,
82
+ }),
83
+ }
84
+ : {}),
85
+ };
86
+ }),
87
+ gaps: selection.gaps.map((gap) => ({
88
+ kind: 'missing_operation',
89
+ description: gap.description,
90
+ ...('explanation' in gap
60
91
  ? {
61
92
  explanation: createApiSelectionExplanation({
62
93
  context,
63
- basis: { kind: 'candidates', operations: [candidate.key] },
64
- reason: item.explanation.reason,
94
+ basis: { kind: 'catalog' },
95
+ reason: gap.explanation.reason,
65
96
  }),
66
97
  }
67
98
  : {}),
68
- };
69
- }),
70
- gaps: selection.gaps.map((gap) => ({
71
- kind: 'missing_operation',
72
- description: gap.description,
73
- ...('explanation' in gap
74
- ? {
75
- explanation: createApiSelectionExplanation({
76
- context,
77
- basis: { kind: 'catalog' },
78
- reason: gap.explanation.reason,
79
- }),
80
- }
81
- : {}),
82
- })),
83
- };
99
+ })),
100
+ };
101
+ });
84
102
  }
@@ -0,0 +1,11 @@
1
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
+ import type { BaseMessage } from '@langchain/core/messages';
3
+ import type { ZodType } from 'zod';
4
+ export interface InvokeStructuredModelInput<Output extends object> {
5
+ model: BaseChatModel;
6
+ schema: ZodType<Output>;
7
+ name: string;
8
+ operation: string;
9
+ method: 'functionCalling' | 'jsonSchema';
10
+ messages: BaseMessage[];
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  import type { NativeNodePlanningFailure } from '../types/native-node-planning.js';
2
- export declare class NativeNodePlanningError extends Error {
2
+ import { OpenApiFlowError } from '@openapi-flow/core';
3
+ export declare class NativeNodePlanningError extends OpenApiFlowError {
3
4
  readonly failure: NativeNodePlanningFailure;
4
5
  constructor(failure: NativeNodePlanningFailure, cause: Error);
5
6
  }
@@ -1,7 +1,15 @@
1
- export class NativeNodePlanningError extends Error {
1
+ import { OpenApiFlowError } from '@openapi-flow/core';
2
+ export class NativeNodePlanningError extends OpenApiFlowError {
2
3
  failure;
3
4
  constructor(failure, cause) {
4
- super(`Native node planning failed at ${failure.stage}: ${cause.message}`, {
5
+ super({
6
+ message: `Native node planning failed at ${failure.stage}: ${cause.message}`,
7
+ diagnostic: {
8
+ owner: 'langchain',
9
+ operation: 'planNativeNodes',
10
+ stage: 'planning',
11
+ code: 'NATIVE_NODE_PLANNING_FAILED',
12
+ },
5
13
  cause,
6
14
  });
7
15
  this.name = 'NativeNodePlanningError';
@@ -1,5 +1,6 @@
1
1
  import type { ReviewableWorkflowGraphPlanningFailure } from '../types/reviewable-workflow-planning.js';
2
- export declare class ReviewableWorkflowGraphPlanningError extends Error {
2
+ import { OpenApiFlowError } from '@openapi-flow/core';
3
+ export declare class ReviewableWorkflowGraphPlanningError extends OpenApiFlowError {
3
4
  readonly failure: ReviewableWorkflowGraphPlanningFailure;
4
5
  constructor(failure: ReviewableWorkflowGraphPlanningFailure, cause: Error);
5
6
  }
@@ -1,7 +1,17 @@
1
- export class ReviewableWorkflowGraphPlanningError extends Error {
1
+ import { OpenApiFlowError } from '@openapi-flow/core';
2
+ export class ReviewableWorkflowGraphPlanningError extends OpenApiFlowError {
2
3
  failure;
3
4
  constructor(failure, cause) {
4
- super(`model reviewable workflow graph ${failure.stage} failed: ${cause.message}`, { cause });
5
+ super({
6
+ message: `model reviewable workflow graph ${failure.stage} failed: ${cause.message}`,
7
+ diagnostic: {
8
+ owner: 'langchain',
9
+ operation: 'planReviewableWorkflowGraph',
10
+ stage: 'planning',
11
+ code: 'REVIEWABLE_WORKFLOW_GRAPH_PLANNING_FAILED',
12
+ },
13
+ cause,
14
+ });
5
15
  this.name = 'ReviewableWorkflowGraphPlanningError';
6
16
  this.failure = failure;
7
17
  }
@@ -1,5 +1,6 @@
1
1
  import type { WorkflowGraphPlanningFailure } from '../types/workflow-planning.js';
2
- export declare class WorkflowGraphPlanningError extends Error {
2
+ import { OpenApiFlowError } from '@openapi-flow/core';
3
+ export declare class WorkflowGraphPlanningError extends OpenApiFlowError {
3
4
  readonly failure: WorkflowGraphPlanningFailure;
4
5
  constructor(failure: WorkflowGraphPlanningFailure, cause: Error);
5
6
  }
@@ -1,7 +1,15 @@
1
- export class WorkflowGraphPlanningError extends Error {
1
+ import { OpenApiFlowError } from '@openapi-flow/core';
2
+ export class WorkflowGraphPlanningError extends OpenApiFlowError {
2
3
  failure;
3
4
  constructor(failure, cause) {
4
- super(`model workflow graph ${failure.stage} failed: ${cause.message}`, {
5
+ super({
6
+ message: `model workflow graph ${failure.stage} failed: ${cause.message}`,
7
+ diagnostic: {
8
+ owner: 'langchain',
9
+ operation: 'planWorkflowGraph',
10
+ stage: 'planning',
11
+ code: 'WORKFLOW_GRAPH_PLANNING_FAILED',
12
+ },
5
13
  cause,
6
14
  });
7
15
  this.name = 'WorkflowGraphPlanningError';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openapi-flow/langchain",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Composable LangChain planning for OpenAPI workflows",
@@ -28,14 +28,14 @@
28
28
  "typecheck": "tsc -p tsconfig.json --noEmit"
29
29
  },
30
30
  "dependencies": {
31
- "@openapi-flow/core": "0.3.13"
31
+ "@openapi-flow/core": "0.3.14"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@langchain/core": "^1.0.0",
35
- "zod": "^4.6.5"
35
+ "zod": "^4.6.7"
36
36
  },
37
37
  "devDependencies": {
38
- "zod": "4.6.5"
38
+ "zod": "4.6.7"
39
39
  },
40
40
  "publishConfig": {
41
41
  "registry": "https://registry.npmjs.org/",