@openapi-flow/core 0.3.6 → 0.3.7

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.
@@ -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.7",
4
4
  "license": "MIT",
5
5
  "description": "Engine-independent OpenAPI catalogs, operation contracts and typed request schemas",
6
6
  "keywords": [