@indexnetwork/protocol 66.0.0-rc.610.1 → 66.0.0-rc.611.1

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.
@@ -18,10 +18,10 @@ import type { EmbeddingGenerator } from "../platform/discovery/embedder.js";
18
18
  import type { IntentFollowUp } from "../platform/runtime/follow-up.js";
19
19
  import { ExplicitIntentInferrer } from "../internal/intents/intent.inferrer.js";
20
20
  import { SemanticVerifier } from "../internal/intents/intent.verifier.js";
21
- import type { AdmissionCheck, PrepareInput, PrepareResult, RecoveryField } from "../internal/intents/intent.preparer.js";
21
+ import type { PrepareInput, PrepareResult } from "../internal/intents/intent.preparer.js";
22
22
  export type { IntentSemanticMetadata } from "../internal/intents/intent.admission.js";
23
23
  export type { PreparedIntent } from "../internal/intents/intent.preparer.js";
24
- export type { AdmissionCheck, PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, } from "../internal/intents/intent.preparer.js";
24
+ export type { PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, } from "../internal/intents/intent.preparer.js";
25
25
  /**
26
26
  * Host capabilities the intent lifecycle needs.
27
27
  *
@@ -421,7 +421,7 @@ export declare class Intents {
421
421
  status: "needs_revision";
422
422
  payload: string;
423
423
  feedback: string;
424
- recovery: RecoveryField[];
424
+ recovery: import("../internal/intents/intent.preparer.js").RecoveryField[];
425
425
  };
426
426
  actions: never[];
427
427
  validationFailures: {
@@ -607,10 +607,6 @@ export declare class Intents {
607
607
  * @param input - The payload and any recovery answers gathered so far.
608
608
  */
609
609
  prepare(input: PrepareInput): Promise<PrepareResult>;
610
- /** Jev's pass or the constraints still missing. Does not write questions. */
611
- check(input: PrepareInput): Promise<AdmissionCheck>;
612
- /** One recovery field per missing constraint. Does not run Jev. */
613
- questions(input: PrepareInput, missing: readonly string[]): Promise<RecoveryField[]>;
614
610
  /**
615
611
  * Measure text without applying any admission filters.
616
612
  * @param content - The exact saved description.
@@ -70,16 +70,6 @@ export class Intents {
70
70
  this.preparer ?? (this.preparer = new IntentPreparer(this.deps.agents?.verifier));
71
71
  return this.preparer.invoke(input);
72
72
  }
73
- /** Jev's pass or the constraints still missing. Does not write questions. */
74
- async check(input) {
75
- this.preparer ?? (this.preparer = new IntentPreparer(this.deps.agents?.verifier));
76
- return this.preparer.check(input);
77
- }
78
- /** One recovery field per missing constraint. Does not run Jev. */
79
- async questions(input, missing) {
80
- this.preparer ?? (this.preparer = new IntentPreparer(this.deps.agents?.verifier));
81
- return this.preparer.questions(input, missing);
82
- }
83
73
  /**
84
74
  * Measure text without applying any admission filters.
85
75
  * @param content - The exact saved description.
package/dist/index.d.ts CHANGED
@@ -29,7 +29,7 @@ export type { NegotiationDatabase } from './platform/database/negotiation.js';
29
29
  export { Networks } from "./capabilities/networks.js";
30
30
  export type { NetworksDeps } from "./capabilities/networks.js";
31
31
  export { Intents } from "./capabilities/intents.js";
32
- export type { AdmissionCheck, PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, IntentsDeps, IntentSemanticMetadata, PreparedIntent, } from "./capabilities/intents.js";
32
+ export type { PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, IntentsDeps, IntentSemanticMetadata, PreparedIntent, } from "./capabilities/intents.js";
33
33
  export { normalizeTelegramHandle } from './internal/shared/utils/telegram-handle.js';
34
34
  /**
35
35
  * opportunity — the capability's sole cross-capability surface.
@@ -34,7 +34,7 @@ export async function verificationNode(state, deps) {
34
34
  const verdict = await deps.verifier.invoke(description, state.userProfile);
35
35
  agentTimingsAccum.push({ name: 'intent.verifier', durationMs: Date.now() - verifierStart });
36
36
  _traceEmitterVerifier?.({ type: "agent_end", name: "intent-verifier", durationMs: Date.now() - verifierStart, summary: `Verified: ${verdict.classification}` });
37
- const failure = admissionFailure(verdict, isExplicitUpdate);
37
+ const failure = admissionFailure(description, verdict, isExplicitUpdate);
38
38
  if (failure)
39
39
  return { failure };
40
40
  // Calculate Score
@@ -1,27 +1,7 @@
1
1
  import type { SemanticVerifierOutput } from "./intent.verifier.js";
2
2
  import type { IntentValidationFailure } from "./graph/intent.graph.state.js";
3
- /** A noul at or above this is a yes the admission check acts on. */
4
- export declare const ADMISSION_NOUL = 0.8;
5
- export declare const ADMISSION_CONSTRAINTS: readonly ["role", "outcome", "location", "timeframe", "domain", "concrete_need"];
6
- export type AdmissionConstraint = (typeof ADMISSION_CONSTRAINTS)[number];
7
- type SpeechAct = "commissive" | "directive" | "declaration" | "other";
8
- /** One Jev admission call, already read into numbers. */
9
- export interface AdmissionDecision {
10
- speechAct: SpeechAct;
11
- tooVague: number;
12
- tooBroad: number;
13
- constraints: Record<AdmissionConstraint, number>;
14
- }
15
3
  /** The admission policy shared by preparation and explicit updates. */
16
- export declare function admissionFailure(verdict: SemanticVerifierOutput, isExplicitUpdate?: boolean): IntentValidationFailure | undefined;
17
- /**
18
- * The prepare-path admission check. Constraint nouls at or above {@link ADMISSION_NOUL}
19
- * are still missing; they do not fail admission on their own.
20
- */
21
- export declare function admissionFromDecision(decision: AdmissionDecision): {
22
- failure?: IntentValidationFailure;
23
- missing: AdmissionConstraint[];
24
- };
4
+ export declare function admissionFailure(description: string, verdict: SemanticVerifierOutput, isExplicitUpdate?: boolean): IntentValidationFailure | undefined;
25
5
  /** Measurements of exactly one description, independent of admission. */
26
6
  export interface IntentSemanticMetadata {
27
7
  semanticEntropy: number;
@@ -34,4 +14,3 @@ export interface IntentSemanticMetadata {
34
14
  }
35
15
  /** Map even a negative verdict to measurements without applying admission. */
36
16
  export declare function semanticMetadata(verdict: SemanticVerifierOutput): IntentSemanticMetadata;
37
- export {};
@@ -1,42 +1,21 @@
1
1
  const MAX_PERMISSIBLE_ENTROPY = 0.75;
2
2
  const MIN_CLEAR_INTENT_SCORE = 40;
3
- const NON_ACTIONABLE_MESSAGE = "Describe who you want to reach and what you want to do together.";
4
- const VAGUE_MESSAGE = "Make the goal more concrete: specify the role, outcome, or particular help you need.";
5
- const BROAD_MESSAGE = "This signal is broad and may produce many weak matches. Add a more concrete role, outcome, location, timeframe, domain, or specific need to get better recommendations.";
6
- /** A noul at or above this is a yes the admission check acts on. */
7
- export const ADMISSION_NOUL = 0.8;
8
- export const ADMISSION_CONSTRAINTS = ["role", "outcome", "location", "timeframe", "domain", "concrete_need"];
3
+ // Match the whole request so qualified job searches use the verifier's verdict.
4
+ const GENERIC_JOB_PHRASE = /^(?:(?:i(?:['’]m|\s+am)?\s+)?(?:want|need|seek(?:ing)?|looking\s+for)\s+)?(?:a|any|some)\s+job[.!?]*$/i;
5
+ const DEFAULT_SPECIFICITY_WARNING = "This signal is broad and may produce many weak matches. Add a more concrete role, outcome, location, timeframe, domain, or specific need to get better recommendations.";
9
6
  /** The admission policy shared by preparation and explicit updates. */
10
- export function admissionFailure(verdict, isExplicitUpdate = false) {
7
+ export function admissionFailure(description, verdict, isExplicitUpdate = false) {
11
8
  const details = { classification: verdict.classification, referentialBreadth: verdict.referential_breadth };
12
9
  if (!["COMMISSIVE", "DIRECTIVE", "DECLARATION"].includes(verdict.classification)) {
13
- return { ...details, category: "non_actionable", message: NON_ACTIONABLE_MESSAGE };
10
+ return { ...details, category: "non_actionable", message: "Describe who you want to reach and what you want to do together." };
14
11
  }
15
- if (verdict.semantic_entropy > MAX_PERMISSIBLE_ENTROPY || verdict.felicity_scores.clarity < MIN_CLEAR_INTENT_SCORE) {
16
- return { ...details, category: "vague_or_invalid", message: VAGUE_MESSAGE };
12
+ if (GENERIC_JOB_PHRASE.test(description.trim()) || verdict.semantic_entropy > MAX_PERMISSIBLE_ENTROPY || verdict.felicity_scores.clarity < MIN_CLEAR_INTENT_SCORE) {
13
+ return { ...details, category: "vague_or_invalid", message: "Make the goal more concrete: specify the role, outcome, or particular help you need." };
17
14
  }
18
15
  if (!isExplicitUpdate && verdict.referential_breadth === "broad") {
19
- return { ...details, category: "vague_or_invalid", message: verdict.specificity_warning?.trim() || BROAD_MESSAGE };
16
+ return { ...details, category: "vague_or_invalid", message: verdict.specificity_warning?.trim() || DEFAULT_SPECIFICITY_WARNING };
20
17
  }
21
18
  }
22
- /**
23
- * The prepare-path admission check. Constraint nouls at or above {@link ADMISSION_NOUL}
24
- * are still missing; they do not fail admission on their own.
25
- */
26
- export function admissionFromDecision(decision) {
27
- const missing = ADMISSION_CONSTRAINTS.filter((name) => decision.constraints[name] >= ADMISSION_NOUL);
28
- const classification = decision.speechAct === "other" ? "UNKNOWN" : decision.speechAct.toUpperCase();
29
- if (decision.speechAct === "other") {
30
- return { missing, failure: { category: "non_actionable", classification, message: NON_ACTIONABLE_MESSAGE } };
31
- }
32
- if (decision.tooVague >= ADMISSION_NOUL) {
33
- return { missing, failure: { category: "vague_or_invalid", classification, message: VAGUE_MESSAGE } };
34
- }
35
- if (decision.tooBroad >= ADMISSION_NOUL) {
36
- return { missing, failure: { category: "vague_or_invalid", classification, referentialBreadth: "broad", message: BROAD_MESSAGE } };
37
- }
38
- return { missing };
39
- }
40
19
  /** Map even a negative verdict to measurements without applying admission. */
41
20
  export function semanticMetadata(verdict) {
42
21
  return {
@@ -1,5 +1,4 @@
1
- import { DecisionClient } from "../shared/agent/decision.client.js";
2
- import { type AdmissionConstraint, type IntentSemanticMetadata } from "./intent.admission.js";
1
+ import { type IntentSemanticMetadata } from "./intent.admission.js";
3
2
  import { SemanticVerifier } from "./intent.verifier.js";
4
3
  /** One answer paired with the recovery field it fills. */
5
4
  export interface PrepareAnswer {
@@ -38,40 +37,16 @@ export type PrepareResult = {
38
37
  feedback: string;
39
38
  recovery: RecoveryField[];
40
39
  };
41
- /** Jev's read of the current rows. `passed` is the only state that may mint a receipt. */
42
- export interface AdmissionCheck {
43
- passed: boolean;
44
- missing: AdmissionConstraint[];
45
- message: string;
46
- }
47
40
  /** Folds recovery answers into a draft, admits its final form, and returns a dynamic recovery form on failure. Model failures propagate for retry. */
48
41
  export declare class IntentPreparer {
49
42
  private readonly verifier;
50
- private readonly decider;
51
- constructor(verifier?: Pick<SemanticVerifier, "invoke">, decider?: Pick<DecisionClient, "decide">);
52
- /**
53
- * Ask Jev whether the current rows would pass. Does not fold text or write questions.
54
- * @param input - Opening draft and the answers already on the page.
55
- * @param profileContext - The speaker's profile, if supplied by the host.
56
- * @throws When Jev fails; the caller keeps the rows and retries.
57
- */
58
- check(input: PrepareInput, profileContext?: string): Promise<AdmissionCheck>;
59
- /**
60
- * Write one recovery field per constraint id. Does not run Jev.
61
- * @param input - Opening draft and the answers already on the page.
62
- * @param missing - Constraint ids that still need a row.
63
- */
64
- questions(input: PrepareInput, missing: readonly string[]): Promise<RecoveryField[]>;
43
+ constructor(verifier?: Pick<SemanticVerifier, "invoke">);
65
44
  /**
66
- * Fold answers, admit the draft, and return a recovery form when it still fails.
45
+ * Prepare one draft using the creation admission policy.
67
46
  * @param input - Current payload and pending recovery answers.
68
47
  * @param profileContext - The speaker's profile, if supplied by the host.
48
+ * @returns An admitted draft or admission feedback plus a recovery form.
69
49
  * @throws When rewriting, verification, or recovery generation fails; callers must retain the input for retry.
70
50
  */
71
51
  invoke(input: PrepareInput, profileContext?: string): Promise<PrepareResult>;
72
- /** Today's recovery form, used only when the Jev call itself fails. */
73
- private legacy;
74
- /** One field per constraint still missing. None named means one text field from the admission message. */
75
- private revise;
76
- private fieldsFor;
77
52
  }
@@ -1,9 +1,8 @@
1
1
  import { HumanMessage, SystemMessage } from "@langchain/core/messages";
2
2
  import { z } from "zod";
3
- import { DecisionClient } from "../shared/agent/decision.client.js";
4
3
  import { createStructuredModel } from "../shared/agent/model.config.js";
5
4
  import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
6
- import { ADMISSION_CONSTRAINTS, admissionFailure, admissionFromDecision, semanticMetadata } from "./intent.admission.js";
5
+ import { admissionFailure, semanticMetadata } from "./intent.admission.js";
7
6
  import { SemanticVerifier } from "./intent.verifier.js";
8
7
  const payloadSchema = z.object({ payload: z.string().trim().min(1).max(65536) });
9
8
  const recoveryOptionSchema = z.object({
@@ -18,107 +17,34 @@ const recoveryFieldSchema = z.object({
18
17
  placeholder: z.string().max(200).nullable().optional(),
19
18
  });
20
19
  const recoverySchema = z.object({
21
- recovery: z.array(recoveryFieldSchema).min(1).max(6),
22
- });
23
- const legacyRecoverySchema = z.object({
24
20
  recovery: z.array(recoveryFieldSchema).min(2).max(6),
25
21
  });
26
- const noul = (instructions, yes, no) => ({
27
- type: "noul",
28
- instructions: `Read \`draft\` and \`answers\` together. ${instructions}`,
29
- criteria: { true: yes, false: no },
30
- });
31
- /** The admission process: the same questions judge the opening draft and a draft after answers. */
32
- const ADMISSION_QUESTIONS = {
33
- speech_act: {
34
- type: "choice",
35
- instructions: "Read `draft` and `answers` together. What speech act is `draft`?",
36
- criteria: {
37
- commissive: "The speaker commits to a future action.",
38
- directive: "The speaker is looking for someone, or asking for something to happen with another person.",
39
- declaration: "The speaker cancels, closes, or declares a state change.",
40
- other: "A fact, opinion, feeling, or anything that is not a search, a commitment, or a declaration.",
41
- },
42
- },
43
- too_vague: noul("Is `draft` still too unconstrained to match someone on? A bare request, such as wanting a job with no role or outcome, is too vague.", "The goal is too unconstrained to match on.", "The goal names a concrete role, outcome, or particular help."),
44
- too_broad: noul("Could many people still satisfy `draft`, even if it names a topic?", "Many people could plausibly match.", "A concrete role, outcome, location, timeframe, or specific need narrows who fits."),
45
- role: noul("Is a specific role still missing from `draft`?", "No specific role is stated.", "A specific role is stated."),
46
- outcome: noul("Is a concrete outcome still missing from `draft`?", "No concrete outcome is stated.", "A concrete outcome is stated."),
47
- location: noul("Is a location still missing from `draft` where one would change who fits?", "No location is stated and one would change who fits.", "A location is stated, or none is needed."),
48
- timeframe: noul("Is a timeframe still missing from `draft` where one would change who fits?", "No timeframe is stated and one would change who fits.", "A timeframe is stated, or none is needed."),
49
- domain: noul("Is a domain still missing from `draft`?", "No domain is stated.", "A domain is stated."),
50
- concrete_need: noul("Is the particular help still missing from `draft`?", "The particular help is not stated.", "The particular help is stated."),
51
- };
52
- function parseRecoveryFields(fields) {
53
- return fields.map((field) => {
22
+ function parseRecoveryFields(result) {
23
+ return result.recovery.map((field) => {
54
24
  const placeholder = field.placeholder?.trim() || undefined;
55
25
  const options = field.options?.length ? field.options : undefined;
56
26
  if (field.kind === "text") {
57
- if (!placeholder)
27
+ if (!placeholder) {
58
28
  throw new Error("Text recovery fields require a placeholder.");
29
+ }
59
30
  return { id: field.id, label: field.label, kind: field.kind, placeholder };
60
31
  }
61
- if (!options?.length)
32
+ if (!options?.length) {
62
33
  throw new Error("Single and multi recovery fields require options.");
34
+ }
63
35
  return { id: field.id, label: field.label, kind: field.kind, options };
64
36
  });
65
37
  }
66
- function readDecision(answers) {
67
- const speech = answers.speech_act;
68
- const speechAct = speech?.type === "choice" ? speech.choice : "";
69
- if (speechAct !== "commissive" && speechAct !== "directive" && speechAct !== "declaration" && speechAct !== "other") {
70
- throw new Error("Jev speech_act answer was unusable");
71
- }
72
- const probability = (key) => {
73
- const answer = answers[key];
74
- if (!answer || answer.type !== "noul")
75
- throw new Error(`Jev ${key} answer was unusable`);
76
- return answer.noul;
77
- };
78
- const constraints = Object.fromEntries(ADMISSION_CONSTRAINTS.map((name) => [name, probability(name)]));
79
- return { speechAct, tooVague: probability("too_vague"), tooBroad: probability("too_broad"), constraints };
80
- }
81
- function textField(message) {
82
- return { id: "detail", label: message, kind: "text", placeholder: "Add the missing detail" };
83
- }
84
- function isAbort(error) {
85
- return error instanceof Error && error.name === "AbortError";
86
- }
87
38
  /** Folds recovery answers into a draft, admits its final form, and returns a dynamic recovery form on failure. Model failures propagate for retry. */
88
39
  export class IntentPreparer {
89
- constructor(verifier = new SemanticVerifier(), decider = new DecisionClient()) {
40
+ constructor(verifier = new SemanticVerifier()) {
90
41
  this.verifier = verifier;
91
- this.decider = decider;
92
42
  }
93
43
  /**
94
- * Ask Jev whether the current rows would pass. Does not fold text or write questions.
95
- * @param input - Opening draft and the answers already on the page.
96
- * @param profileContext - The speaker's profile, if supplied by the host.
97
- * @throws When Jev fails; the caller keeps the rows and retries.
98
- */
99
- async check(input, profileContext = "") {
100
- const answers = (input.answers ?? []).filter((answer) => answer.answer.trim());
101
- const judged = admissionFromDecision(readDecision(await this.decider.decide({ draft: input.payload, answers, profile: profileContext }, ADMISSION_QUESTIONS)));
102
- if (!judged.failure)
103
- return { passed: true, missing: [], message: "" };
104
- return { passed: false, missing: judged.missing, message: judged.failure.message };
105
- }
106
- /**
107
- * Write one recovery field per constraint id. Does not run Jev.
108
- * @param input - Opening draft and the answers already on the page.
109
- * @param missing - Constraint ids that still need a row.
110
- */
111
- async questions(input, missing) {
112
- const answers = (input.answers ?? []).filter((answer) => answer.answer.trim());
113
- const constraints = [...new Set(missing)].filter((id) => ADMISSION_CONSTRAINTS.includes(id));
114
- if (!constraints.length)
115
- return [];
116
- return this.fieldsFor(input.payload, answers, "", constraints);
117
- }
118
- /**
119
- * Fold answers, admit the draft, and return a recovery form when it still fails.
44
+ * Prepare one draft using the creation admission policy.
120
45
  * @param input - Current payload and pending recovery answers.
121
46
  * @param profileContext - The speaker's profile, if supplied by the host.
47
+ * @returns An admitted draft or admission feedback plus a recovery form.
122
48
  * @throws When rewriting, verification, or recovery generation fails; callers must retain the input for retry.
123
49
  */
124
50
  async invoke(input, profileContext = "") {
@@ -132,28 +58,11 @@ export class IntentPreparer {
132
58
  ]);
133
59
  payload = payloadSchema.parse(result).payload;
134
60
  }
135
- let jevFailed = false;
136
- try {
137
- const judged = admissionFromDecision(readDecision(await this.decider.decide({ draft: payload, profile: profileContext }, ADMISSION_QUESTIONS)));
138
- if (judged.failure)
139
- return this.revise(payload, answers, judged.failure.message, judged.missing);
140
- }
141
- catch (error) {
142
- if (isAbort(error))
143
- throw error;
144
- jevFailed = true;
145
- }
146
61
  const verdict = await this.verifier.invoke(payload, profileContext);
147
- const failure = admissionFailure(verdict);
62
+ const failure = admissionFailure(payload, verdict);
148
63
  if (!failure)
149
64
  return { status: "ready", payload, metadata: semanticMetadata(verdict) };
150
- if (jevFailed)
151
- return this.legacy(payload, answers, failure, verdict);
152
- return this.revise(payload, answers, failure.message, verdict.missing_selectional_constraints);
153
- }
154
- /** Today's recovery form, used only when the Jev call itself fails. */
155
- async legacy(payload, answers, failure, verdict) {
156
- const model = createStructuredModel("intentClarifier", legacyRecoverySchema, { name: "intent_recovery" });
65
+ const model = createStructuredModel("intentClarifier", recoverySchema, { name: "intent_recovery" });
157
66
  const result = await invokeWithAbortSignal(model, [
158
67
  new SystemMessage("Build one recovery form that helps the person concretize their signal. Return 2-6 fields. Each field needs a draft-specific label, a kind (single, multi, or text), and for single/multi 2-5 concrete options anchored to the draft. Text fields need a placeholder. Never re-ask what the draft or answers already settle. Options must be matchable and specific. Do not expose scores, classifications, JSON, or internal vocabulary."),
159
68
  new HumanMessage(JSON.stringify({
@@ -168,27 +77,11 @@ export class IntentPreparer {
168
77
  missingConstraints: verdict.missing_selectional_constraints,
169
78
  })),
170
79
  ]);
171
- return { status: "needs_revision", payload, feedback: failure.message, recovery: parseRecoveryFields(legacyRecoverySchema.parse(result).recovery) };
172
- }
173
- /** One field per constraint still missing. None named means one text field from the admission message. */
174
- async revise(payload, answers, feedback, missing) {
175
- const constraints = [...new Set(missing)];
176
- const recovery = constraints.length === 0
177
- ? [textField(feedback)]
178
- : await this.fieldsFor(payload, answers, feedback, constraints);
179
- return { status: "needs_revision", payload, feedback, recovery };
180
- }
181
- async fieldsFor(payload, answers, feedback, constraints) {
182
- const model = createStructuredModel("intentClarifier", recoverySchema, { name: "intent_recovery" });
183
- const result = await invokeWithAbortSignal(model, [
184
- new SystemMessage(`Write one recovery field for each missing constraint, in this order: ${constraints.join(", ")}. Set each field's id to that constraint name. Each field needs a draft-specific label, a kind (single, multi, or text), and for single/multi 2-5 concrete options anchored to the draft. Text fields need a placeholder. Do not add any other field. Never re-ask what the draft or answers already settle. Do not expose scores, classifications, JSON, or internal vocabulary.`),
185
- new HumanMessage(JSON.stringify({ payload, answers, feedback, missingConstraints: constraints })),
186
- ]);
187
- const fields = parseRecoveryFields(recoverySchema.parse(result).recovery);
188
- const byId = new Map(fields.map((field) => [field.id, field]));
189
- const matched = constraints.map((id) => byId.get(id));
190
- if (matched.some((field) => !field))
191
- throw new Error("Recovery fields did not cover the missing constraints.");
192
- return matched;
80
+ return {
81
+ status: "needs_revision",
82
+ payload,
83
+ feedback: failure.message,
84
+ recovery: parseRecoveryFields(recoverySchema.parse(result)),
85
+ };
193
86
  }
194
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/protocol",
3
- "version": "66.0.0-rc.610.1",
3
+ "version": "66.0.0-rc.611.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,42 +0,0 @@
1
- export type DecisionQuestion = {
2
- type: "noul";
3
- instructions: string;
4
- criteria?: {
5
- true: string;
6
- false: string;
7
- };
8
- } | {
9
- type: "choice";
10
- instructions: string;
11
- criteria: Record<string, string>;
12
- } | {
13
- type: "score";
14
- instructions: string;
15
- criteria: string[];
16
- };
17
- export type DecisionAnswer = {
18
- type: "noul";
19
- noul: number;
20
- } | {
21
- type: "choice";
22
- choice: string;
23
- } | {
24
- type: "score";
25
- score: number;
26
- };
27
- /**
28
- * Jev, through OpenRouter's Decisions API. It returns typed answers, not text.
29
- * A missing key, a bad response, or a timeout throws. Callers keep their own fallback.
30
- */
31
- export declare class DecisionClient {
32
- private readonly apiKey?;
33
- private readonly timeout;
34
- constructor(apiKey?: string | undefined, timeout?: number);
35
- /**
36
- * @param state - The draft and whatever else the questions refer to.
37
- * @param questions - Named noul, choice, or score questions.
38
- * @param signal - Cancels this call. A request-scoped signal is used when this is omitted.
39
- * @returns One answer per question key.
40
- */
41
- decide(state: unknown, questions: Record<string, DecisionQuestion>, signal?: AbortSignal): Promise<Record<string, DecisionAnswer>>;
42
- }
@@ -1,61 +0,0 @@
1
- import { getAbortSignalConfig } from "./model-signal.js";
2
- const DECISIONS_URL = "https://openrouter.ai/api/alpha/decisions";
3
- const JEV_MODEL = "typesafe/jev-1.13";
4
- const TIMEOUT_MS = 60000;
5
- /**
6
- * Jev, through OpenRouter's Decisions API. It returns typed answers, not text.
7
- * A missing key, a bad response, or a timeout throws. Callers keep their own fallback.
8
- */
9
- export class DecisionClient {
10
- constructor(apiKey, timeout = TIMEOUT_MS) {
11
- this.apiKey = apiKey;
12
- this.timeout = timeout;
13
- }
14
- /**
15
- * @param state - The draft and whatever else the questions refer to.
16
- * @param questions - Named noul, choice, or score questions.
17
- * @param signal - Cancels this call. A request-scoped signal is used when this is omitted.
18
- * @returns One answer per question key.
19
- */
20
- async decide(state, questions, signal) {
21
- const apiKey = this.apiKey ?? process.env.OPENROUTER_API_KEY;
22
- if (!apiKey?.trim())
23
- throw new Error("DecisionClient: OPENROUTER_API_KEY is required.");
24
- const caller = signal ?? getAbortSignalConfig()?.signal;
25
- const deadline = AbortSignal.timeout(this.timeout);
26
- const stop = caller ? AbortSignal.any([caller, deadline]) : deadline;
27
- const response = await fetch(DECISIONS_URL, {
28
- method: "POST",
29
- signal: stop,
30
- headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
31
- body: JSON.stringify({ model: JEV_MODEL, state, questions }),
32
- });
33
- const body = await response.text();
34
- let data = {};
35
- try {
36
- data = JSON.parse(body);
37
- }
38
- catch {
39
- if (response.ok)
40
- throw new Error(`Jev returned a non-JSON response: ${body.slice(0, 500)}`);
41
- }
42
- if (!response.ok || data.error || !data.answers) {
43
- throw new Error(`Jev request failed (${response.status}): ${(data.error?.message ?? body).slice(0, 500)}`);
44
- }
45
- return readAnswers(data.answers);
46
- }
47
- }
48
- function readAnswers(raw) {
49
- const answers = {};
50
- for (const [key, answer] of Object.entries(raw)) {
51
- if (answer?.type === "noul" && typeof answer.noul === "number")
52
- answers[key] = { type: "noul", noul: answer.noul };
53
- else if (answer?.type === "choice" && typeof answer.choice === "string")
54
- answers[key] = { type: "choice", choice: answer.choice };
55
- else if (answer?.type === "score" && typeof answer.score === "number")
56
- answers[key] = { type: "score", score: answer.score };
57
- else
58
- throw new Error(`Jev answer "${key}" was unusable`);
59
- }
60
- return answers;
61
- }