@openapi-flow/core 0.3.6 → 0.3.8

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Zod `^4.6.5` is a required peer, shared with the host rather than installed as a separately pinned runtime copy. Development tests use 4.6.5; the strict installed-consumer check also uses the host's 4.6.7. This preserves the public schema type boundary without casts or dependency overrides.
4
4
 
5
- Version **0.3.5**. Canonical draft, clarification and runtime-input APIs described below are included in this release source. Original-choice follow-ups now derive their display and typed reply in Core. Ajv 8.20.0 does not correctly validate a declared `properties.__proto__`; generated validation may reject valid data or omit that property's constraints. This previously accepted limitation remains unchanged, without renaming data or skipping its failing regression. See the [release record](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.5.md) for publication and host status.
5
+ Version **0.3.8**. Canonical draft, clarification and runtime-input APIs described below are included in this release source. Original-choice follow-ups derive their display and typed reply in Core. This patch aligns the three libraries for the new n8n response-object capability without adding a Core-specific composition path. Ajv 8.20.0 does not correctly validate a declared `properties.__proto__`; generated validation may reject valid data or omit that property's constraints. This previously accepted limitation remains unchanged, without renaming data or skipping its failing regression. See the [release record](https://github.com/carefreelife98/openapi-flow/blob/main/docs/releases/0.3.8.md) for publication and host status.
6
6
 
7
7
  Native and fixed-material connection planning accept the optional, existing `WorkflowEntryPointContract` as `entrypoint`. Core validates and snapshots this owner-provided start alongside the other inputs; it does not infer one when absent, add a selectable trigger or let model output modify it.
8
8
 
@@ -0,0 +1,4 @@
1
+ import type { ResolveApiOperationChoiceAnswerInput } from '../../types/api-clarification.js';
2
+ import type { SelectedApiOperation } from '../../types/api-catalog.js';
3
+ /** Resolve only the explicitly declared choice mode; prose never determines cardinality. */
4
+ export declare function resolveApiOperationChoiceAnswer(input: ResolveApiOperationChoiceAnswerInput): SelectedApiOperation[];
@@ -0,0 +1,38 @@
1
+ import { apiOperationAnswersSchema } from '../../schemas/api-operation-answers-schema.js';
2
+ import { validateApiSelectionQuestion } from './validate-api-selection-question.js';
3
+ import { apiOperationKeyEquals } from '../catalog/api-operation-key-equals.js';
4
+ /** Resolve only the explicitly declared choice mode; prose never determines cardinality. */
5
+ export function resolveApiOperationChoiceAnswer(input) {
6
+ const { question } = input;
7
+ validateApiSelectionQuestion(question);
8
+ const answer = apiOperationAnswersSchema.element.parse(input.answer);
9
+ if (answer.questionId !== question.id)
10
+ throw new Error(`Operation answer questionId does not match ${question.id}`);
11
+ let keys;
12
+ if (question.kind === 'operation-choice') {
13
+ if (!('operation' in answer))
14
+ throw new Error(`Operation question ${question.id} requires one operation`);
15
+ keys = [answer.operation];
16
+ }
17
+ else if (question.kind === 'operation-multiple-choice') {
18
+ if (!('operations' in answer))
19
+ throw new Error(`Operation question ${question.id} requires an operations array`);
20
+ keys = answer.operations;
21
+ if (keys.length < question.minSelections ||
22
+ keys.length > question.maxSelections)
23
+ throw new Error(`Operation question ${question.id} requires ${question.minSelections}..${question.maxSelections} selections`);
24
+ }
25
+ else {
26
+ throw new Error('Operation question has no supported source-owned choice kind');
27
+ }
28
+ const seen = [];
29
+ return keys.map((key) => {
30
+ if (seen.some((previous) => apiOperationKeyEquals(previous, key)))
31
+ throw new Error(`Operation answer ${question.id} contains a duplicate selection`);
32
+ const candidate = question.candidates.find((item) => apiOperationKeyEquals(item.key, key));
33
+ if (!candidate)
34
+ throw new Error(`Operation answer ${question.id} does not match a current offered candidate`);
35
+ seen.push(key);
36
+ return { key: structuredClone(candidate.key), purpose: question.purpose };
37
+ });
38
+ }
@@ -1,5 +1,5 @@
1
1
  import { apiOperationAnswersSchema } from '../../schemas/api-operation-answers-schema.js';
2
- import { apiOperationKeyEquals } from '../catalog/api-operation-key-equals.js';
2
+ import { resolveApiOperationChoiceAnswer } from './resolve-api-operation-choice-answer.js';
3
3
  import { resolveApiOperations } from '../resolve-api-operations.js';
4
4
  export async function resolveApiSelectionAnswers(input) {
5
5
  const answers = apiOperationAnswersSchema.parse(input.answers);
@@ -13,13 +13,7 @@ export async function resolveApiSelectionAnswers(input) {
13
13
  const question = input.analysis.questions.find((item) => item.id === answer.questionId);
14
14
  if (!question || answered.has(answer.questionId))
15
15
  throw new Error(`Unknown or duplicate operation question ${answer.questionId}`);
16
- const candidate = question.candidates.find((item) => apiOperationKeyEquals(item.key, answer.operation));
17
- if (!candidate)
18
- throw new Error(`Operation answer ${answer.questionId} does not match a current offered candidate`);
19
- operations.push({
20
- key: structuredClone(candidate.key),
21
- purpose: question.purpose,
22
- });
16
+ operations.push(...resolveApiOperationChoiceAnswer({ question, answer }));
23
17
  answered.add(question.id);
24
18
  }
25
19
  return {
@@ -0,0 +1,2 @@
1
+ import type { ApiSelectionQuestion } from '../../types/api-clarification.js';
2
+ export declare function validateApiSelectionQuestion(question: ApiSelectionQuestion): void;
@@ -0,0 +1,13 @@
1
+ import { apiOperationChoiceBoundsSchema } from '../../schemas/api-operation-choice-bounds-schema.js';
2
+ export function validateApiSelectionQuestion(question) {
3
+ if (question.kind === 'operation-choice')
4
+ return;
5
+ if (question.kind !== 'operation-multiple-choice')
6
+ throw new Error('Operation question has no supported source-owned choice kind');
7
+ const bounds = apiOperationChoiceBoundsSchema.parse({
8
+ minSelections: question.minSelections,
9
+ maxSelections: question.maxSelections,
10
+ });
11
+ if (bounds.maxSelections > question.candidates.length)
12
+ throw new Error(`Operation question ${question.id} maxSelections exceeds offered candidates`);
13
+ }
@@ -39,6 +39,8 @@ export { inspectApiArgumentRequirements } from './arguments/clarification/inspec
39
39
  export { validateApiInputAnswers } from './arguments/clarification/validate-api-input-answers.js';
40
40
  export { createApiInputAnswersSchema } from './schemas/create-api-input-answers-schema.js';
41
41
  export { resolveApiSelectionAnswers } from './openapi/selection/resolve-api-selection-answers.js';
42
+ export { resolveApiOperationChoiceAnswer } from './openapi/selection/resolve-api-operation-choice-answer.js';
43
+ export { validateApiSelectionQuestion } from './openapi/selection/validate-api-selection-question.js';
42
44
  export type * from './types/deferred-api-inputs.js';
43
45
  export { collectDeferredApiInputSlots } from './inputs/collect-deferred-api-input-slots.js';
44
46
  export { createDeferredApiInputsSchema } from './schemas/create-deferred-api-inputs-schema.js';
@@ -29,6 +29,8 @@ export { inspectApiArgumentRequirements } from './arguments/clarification/inspec
29
29
  export { validateApiInputAnswers } from './arguments/clarification/validate-api-input-answers.js';
30
30
  export { createApiInputAnswersSchema } from './schemas/create-api-input-answers-schema.js';
31
31
  export { resolveApiSelectionAnswers } from './openapi/selection/resolve-api-selection-answers.js';
32
+ export { resolveApiOperationChoiceAnswer } from './openapi/selection/resolve-api-operation-choice-answer.js';
33
+ export { validateApiSelectionQuestion } from './openapi/selection/validate-api-selection-question.js';
32
34
  export { collectDeferredApiInputSlots } from './inputs/collect-deferred-api-input-slots.js';
33
35
  export { createDeferredApiInputsSchema } from './schemas/create-deferred-api-inputs-schema.js';
34
36
  export { inspectDeferredApiInputs } from './inputs/inspect-deferred-api-inputs.js';
@@ -1,9 +1,16 @@
1
1
  import { z } from 'zod';
2
- export declare const apiOperationAnswersSchema: z.ZodArray<z.ZodObject<{
2
+ export declare const apiOperationAnswersSchema: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
3
3
  questionId: z.ZodString;
4
4
  operation: z.ZodObject<{
5
5
  documentId: z.ZodString;
6
6
  snapshotId: z.ZodString;
7
7
  operationRef: z.ZodString;
8
8
  }, z.core.$strict>;
9
- }, z.core.$strict>>;
9
+ }, z.core.$strict>, z.ZodObject<{
10
+ questionId: z.ZodString;
11
+ operations: z.ZodArray<z.ZodObject<{
12
+ documentId: z.ZodString;
13
+ snapshotId: z.ZodString;
14
+ operationRef: z.ZodString;
15
+ }, z.core.$strict>>;
16
+ }, z.core.$strict>]>>;
@@ -1,8 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import { apiOperationKeySchema } from './api-operation-key-schema.js';
3
3
  export const apiOperationAnswersSchema = z
4
- .array(z.strictObject({
5
- questionId: z.string().min(1),
6
- operation: apiOperationKeySchema,
7
- }))
4
+ .array(z.union([
5
+ z.strictObject({
6
+ questionId: z.string().min(1),
7
+ operation: apiOperationKeySchema,
8
+ }),
9
+ z.strictObject({
10
+ questionId: z.string().min(1),
11
+ operations: z.array(apiOperationKeySchema),
12
+ }),
13
+ ]))
8
14
  .min(1);
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+ export declare const apiOperationChoiceBoundsSchema: z.ZodObject<{
3
+ minSelections: z.ZodNumber;
4
+ maxSelections: z.ZodNumber;
5
+ }, z.core.$strict>;
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ export const apiOperationChoiceBoundsSchema = z
3
+ .strictObject({
4
+ minSelections: z.number().int().nonnegative().safe(),
5
+ maxSelections: z.number().int().nonnegative().safe(),
6
+ })
7
+ .refine((bounds) => bounds.minSelections <= bounds.maxSelections, {
8
+ message: 'minSelections must not exceed maxSelections',
9
+ });
@@ -5,6 +5,12 @@ export declare const workflowReviewSuggestionsRecordSchema: z.ZodObject<{
5
5
  fingerprint: z.ZodString;
6
6
  boundaryKey: z.ZodString;
7
7
  target: z.ZodObject<{
8
+ kind: z.ZodEnum<{
9
+ "requirements-clarification": "requirements-clarification";
10
+ "selection-review": "selection-review";
11
+ "planning-questions": "planning-questions";
12
+ "candidate-review": "candidate-review";
13
+ }>;
8
14
  questions: z.ZodArray<z.ZodObject<{
9
15
  id: z.ZodString;
10
16
  message: z.ZodString;
@@ -19,12 +25,6 @@ export declare const workflowReviewSuggestionsRecordSchema: z.ZodObject<{
19
25
  }, z.core.$strict>>;
20
26
  }, z.core.$strict>>;
21
27
  }, z.core.$strict>>;
22
- kind: z.ZodEnum<{
23
- "requirements-clarification": "requirements-clarification";
24
- "selection-review": "selection-review";
25
- "planning-questions": "planning-questions";
26
- "candidate-review": "candidate-review";
27
- }>;
28
28
  revision: z.ZodNumber;
29
29
  data: z.ZodJSONSchema;
30
30
  }, z.core.$strict>;
@@ -65,16 +65,30 @@ export interface ApiOperationChoice {
65
65
  candidates: ApiOperationCandidate[];
66
66
  explanation?: ApiSelectionExplanation;
67
67
  }
68
+ export interface ApiOperationMultipleChoice extends Omit<ApiOperationChoice, 'kind'> {
69
+ kind: 'operation-multiple-choice';
70
+ minSelections: number;
71
+ maxSelections: number;
72
+ }
73
+ export type ApiSelectionQuestion = ApiOperationChoice | ApiOperationMultipleChoice;
68
74
  export interface ApiSelectionAnalysis {
69
75
  selection: ApiSelection;
70
- questions: ApiOperationChoice[];
76
+ questions: ApiSelectionQuestion[];
71
77
  }
72
78
  export interface ApiOperationAnswer {
73
79
  questionId: string;
74
80
  operation: ApiOperationKey;
75
81
  }
82
+ export interface ApiOperationMultipleAnswer extends Omit<ApiOperationAnswer, 'operation'> {
83
+ operations: ApiOperationKey[];
84
+ }
85
+ export type ApiSelectionAnswer = ApiOperationAnswer | ApiOperationMultipleAnswer;
86
+ export interface ResolveApiOperationChoiceAnswerInput {
87
+ question: ApiSelectionQuestion;
88
+ answer: ApiSelectionAnswer;
89
+ }
76
90
  export interface ResolveApiSelectionAnswersInput {
77
91
  catalog: ApiCatalog;
78
92
  analysis: ApiSelectionAnalysis;
79
- answers: ApiOperationAnswer[];
93
+ answers: ApiSelectionAnswer[];
80
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openapi-flow/core",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "license": "MIT",
5
5
  "description": "Engine-independent OpenAPI catalogs, operation contracts and typed request schemas",
6
6
  "keywords": [