nemoflow 0.1.0

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 ADDED
@@ -0,0 +1,84 @@
1
+ # NemoFlow TypeScript SDK
2
+
3
+ TypeScript client for the [NemoFlow API](https://api.nemoflow.ai) — the reliability oracle for AI agents. Requires Node.js 18+ (uses native `fetch`).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install nemoflow
9
+ ```
10
+
11
+ ## Quick start — one line with guard()
12
+
13
+ ```typescript
14
+ import { NemoFlow } from "nemoflow";
15
+
16
+ const nemo = new NemoFlow("nf_live_...");
17
+
18
+ // Wrap any tool call — assess, execute, report, auto-fallback
19
+ const result = await nemo.guard(
20
+ "https://api.openai.com/v1/chat/completions",
21
+ () => openai.chat.completions.create({ model: "gpt-4", messages }),
22
+ );
23
+ ```
24
+
25
+ ## Auto-fallback
26
+
27
+ ```typescript
28
+ const result = await nemo.guard(
29
+ "https://api.openai.com/v1/chat/completions",
30
+ () => openai.chat.completions.create({ model: "gpt-4", messages }),
31
+ {
32
+ minScore: 50,
33
+ fallbacks: [
34
+ {
35
+ toolIdentifier: "https://api.anthropic.com/v1/messages",
36
+ fn: () => anthropic.messages.create({ model: "claude-sonnet-4-20250514", messages }),
37
+ },
38
+ ],
39
+ },
40
+ );
41
+ ```
42
+
43
+ ## Journey tracking
44
+
45
+ ```typescript
46
+ // First attempt fails
47
+ await nemo.report({
48
+ toolIdentifier: "https://api.sendgrid.com/v3/mail/send",
49
+ success: false, errorCategory: "rate_limit",
50
+ sessionId: "session-123", attemptNumber: 1,
51
+ });
52
+
53
+ // Fallback succeeds
54
+ await nemo.report({
55
+ toolIdentifier: "https://api.resend.com/emails",
56
+ success: true, latencyMs: 180,
57
+ sessionId: "session-123", attemptNumber: 2,
58
+ previousTool: "https://api.sendgrid.com/v3/mail/send",
59
+ });
60
+ ```
61
+
62
+ ## Discovery
63
+
64
+ ```typescript
65
+ const gems = await nemo.discoverHiddenGems({ category: "email" });
66
+ const chain = await nemo.discoverFallbackChain("https://api.sendgrid.com/v3/mail/send");
67
+ ```
68
+
69
+ ## Direct API usage
70
+
71
+ ```typescript
72
+ const result = await nemo.assess({
73
+ toolIdentifier: "https://api.openai.com/v1/chat/completions",
74
+ context: "customer support chatbot",
75
+ });
76
+ console.log(result.reliabilityScore); // 89.0
77
+ console.log(result.predictedFailureRisk); // "low"
78
+ console.log(result.topAlternatives); // [{ tool: "...", score: 90 }]
79
+
80
+ await nemo.report({
81
+ toolIdentifier: "https://api.openai.com/v1/chat/completions",
82
+ success: true, latencyMs: 2500,
83
+ });
84
+ ```
@@ -0,0 +1,31 @@
1
+ import { type NemoFlowOptions, type AssessParams, type AssessResponse, type ReportParams, type ReportResponse, type HiddenGemsResponse, type FallbackChainResponse, type GuardOptions } from "./types.js";
2
+ export declare class NemoFlow {
3
+ private readonly apiKey;
4
+ private readonly baseUrl;
5
+ constructor(apiKey: string, options?: NemoFlowOptions);
6
+ /** Assess a tool's reliability and get recommendations. */
7
+ assess(params: AssessParams): Promise<AssessResponse>;
8
+ /** Report an outcome for a tool invocation. */
9
+ report(params: ReportParams): Promise<ReportResponse>;
10
+ /** Find hidden gem tools that shine as fallbacks. */
11
+ discoverHiddenGems(options?: {
12
+ category?: string;
13
+ limit?: number;
14
+ }): Promise<HiddenGemsResponse>;
15
+ /** Get the best fallback tools when a specific tool fails. */
16
+ discoverFallbackChain(toolIdentifier: string, options?: {
17
+ limit?: number;
18
+ }): Promise<FallbackChainResponse>;
19
+ /**
20
+ * Execute a tool call with automatic reliability guard.
21
+ *
22
+ * 1. Assesses the tool's reliability score
23
+ * 2. If score < minScore and fallbacks exist, skips to next
24
+ * 3. Executes the tool call
25
+ * 4. Reports success/failure back to NemoFlow
26
+ * 5. On failure with fallbacks, tries the next option
27
+ */
28
+ guard<T>(toolIdentifier: string, fn: () => Promise<T>, options?: GuardOptions<T>): Promise<T>;
29
+ private post;
30
+ private get;
31
+ }
package/dist/client.js ADDED
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NemoFlow = void 0;
4
+ const types_js_1 = require("./types.js");
5
+ const DEFAULT_BASE_URL = "https://api.nemoflow.ai";
6
+ class NemoFlow {
7
+ constructor(apiKey, options) {
8
+ if (!apiKey) {
9
+ throw new Error("An API key is required to create a NemoFlow client.");
10
+ }
11
+ this.apiKey = apiKey;
12
+ this.baseUrl = (options?.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
13
+ }
14
+ // ── Core endpoints ─────────────────────────────────────────────
15
+ /** Assess a tool's reliability and get recommendations. */
16
+ async assess(params) {
17
+ const raw = await this.post("/v1/assess", {
18
+ tool_identifier: params.toolIdentifier,
19
+ context: params.context,
20
+ sample_payload: params.samplePayload,
21
+ });
22
+ return {
23
+ reliabilityScore: raw.reliability_score,
24
+ confidence: raw.confidence,
25
+ historicalSuccessRate: raw.historical_success_rate,
26
+ predictedFailureRisk: raw.predicted_failure_risk,
27
+ commonPitfalls: raw.common_pitfalls,
28
+ recommendedMitigations: raw.recommended_mitigations,
29
+ topAlternatives: raw.top_alternatives.map((a) => ({
30
+ tool: a.tool,
31
+ score: a.score,
32
+ reason: a.reason,
33
+ })),
34
+ estimatedLatencyMs: raw.estimated_latency_ms,
35
+ lastUpdated: raw.last_updated,
36
+ };
37
+ }
38
+ /** Report an outcome for a tool invocation. */
39
+ async report(params) {
40
+ const raw = await this.post("/v1/report", {
41
+ tool_identifier: params.toolIdentifier,
42
+ success: params.success,
43
+ error_category: params.errorCategory,
44
+ latency_ms: params.latencyMs,
45
+ context: params.context,
46
+ session_id: params.sessionId,
47
+ attempt_number: params.attemptNumber,
48
+ previous_tool: params.previousTool,
49
+ });
50
+ return {
51
+ status: raw.status,
52
+ toolId: raw.tool_id,
53
+ };
54
+ }
55
+ // ── Discovery endpoints ────────────────────────────────────────
56
+ /** Find hidden gem tools that shine as fallbacks. */
57
+ async discoverHiddenGems(options) {
58
+ const params = new URLSearchParams();
59
+ if (options?.category)
60
+ params.set("category", options.category);
61
+ if (options?.limit)
62
+ params.set("limit", String(options.limit));
63
+ const raw = await this.get(`/v1/discover/hidden-gems?${params}`);
64
+ return {
65
+ hiddenGems: raw.hidden_gems.map((g) => ({
66
+ tool: g.tool,
67
+ displayName: g.display_name,
68
+ category: g.category,
69
+ fallbackSuccessRate: g.fallback_success_rate,
70
+ timesUsedAsFallback: g.times_used_as_fallback,
71
+ avgLatencyMs: g.avg_latency_ms,
72
+ })),
73
+ count: raw.count,
74
+ };
75
+ }
76
+ /** Get the best fallback tools when a specific tool fails. */
77
+ async discoverFallbackChain(toolIdentifier, options) {
78
+ const params = new URLSearchParams({
79
+ tool_identifier: toolIdentifier,
80
+ });
81
+ if (options?.limit)
82
+ params.set("limit", String(options.limit));
83
+ const raw = await this.get(`/v1/discover/fallback-chain?${params}`);
84
+ return {
85
+ tool: raw.tool,
86
+ fallbackChain: raw.fallback_chain.map((f) => ({
87
+ fallbackTool: f.fallback_tool,
88
+ displayName: f.display_name,
89
+ timesChosenAfterFailure: f.times_chosen_after_failure,
90
+ successRate: f.success_rate,
91
+ avgLatencyMs: f.avg_latency_ms,
92
+ })),
93
+ count: raw.count,
94
+ };
95
+ }
96
+ // ── Guard ──────────────────────────────────────────────────────
97
+ /**
98
+ * Execute a tool call with automatic reliability guard.
99
+ *
100
+ * 1. Assesses the tool's reliability score
101
+ * 2. If score < minScore and fallbacks exist, skips to next
102
+ * 3. Executes the tool call
103
+ * 4. Reports success/failure back to NemoFlow
104
+ * 5. On failure with fallbacks, tries the next option
105
+ */
106
+ async guard(toolIdentifier, fn, options) {
107
+ const context = options?.context ?? "";
108
+ const minScore = options?.minScore ?? 0;
109
+ const sessionId = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
110
+ const allTools = [
111
+ { toolIdentifier, fn },
112
+ ...(options?.fallbacks ?? []),
113
+ ];
114
+ let lastError;
115
+ for (let i = 0; i < allTools.length; i++) {
116
+ const attempt = i + 1;
117
+ const tool = allTools[i];
118
+ const previousTool = i > 0 ? allTools[i - 1].toolIdentifier : undefined;
119
+ // Assess
120
+ let score = 100;
121
+ try {
122
+ const assessment = await this.assess({
123
+ toolIdentifier: tool.toolIdentifier,
124
+ context,
125
+ });
126
+ score = assessment.reliabilityScore;
127
+ }
128
+ catch {
129
+ // If assess fails, don't block the tool call
130
+ }
131
+ // Skip if score too low and we have more options
132
+ if (score < minScore && attempt < allTools.length) {
133
+ try {
134
+ await this.report({
135
+ toolIdentifier: tool.toolIdentifier,
136
+ success: false,
137
+ errorCategory: "skipped_low_score",
138
+ context,
139
+ sessionId,
140
+ attemptNumber: attempt,
141
+ previousTool,
142
+ });
143
+ }
144
+ catch {
145
+ // Best-effort reporting
146
+ }
147
+ continue;
148
+ }
149
+ // Execute
150
+ const start = performance.now();
151
+ try {
152
+ const result = await tool.fn();
153
+ const latencyMs = Math.round(performance.now() - start);
154
+ // Report success (best-effort)
155
+ try {
156
+ await this.report({
157
+ toolIdentifier: tool.toolIdentifier,
158
+ success: true,
159
+ latencyMs,
160
+ context,
161
+ sessionId,
162
+ attemptNumber: attempt,
163
+ previousTool,
164
+ });
165
+ }
166
+ catch {
167
+ // Don't fail the call if reporting fails
168
+ }
169
+ return result;
170
+ }
171
+ catch (e) {
172
+ const latencyMs = Math.round(performance.now() - start);
173
+ lastError = e instanceof Error ? e : new Error(String(e));
174
+ // Report failure (best-effort)
175
+ try {
176
+ await this.report({
177
+ toolIdentifier: tool.toolIdentifier,
178
+ success: false,
179
+ errorCategory: classifyError(lastError),
180
+ latencyMs,
181
+ context,
182
+ sessionId,
183
+ attemptNumber: attempt,
184
+ previousTool,
185
+ });
186
+ }
187
+ catch {
188
+ // Best-effort reporting
189
+ }
190
+ // If no more fallbacks, throw
191
+ if (attempt >= allTools.length) {
192
+ throw lastError;
193
+ }
194
+ }
195
+ }
196
+ throw lastError;
197
+ }
198
+ // ── Internals ──────────────────────────────────────────────────
199
+ async post(path, body) {
200
+ // Strip undefined values
201
+ const clean = Object.fromEntries(Object.entries(body).filter(([, v]) => v !== undefined));
202
+ const response = await fetch(`${this.baseUrl}${path}`, {
203
+ method: "POST",
204
+ headers: {
205
+ "Content-Type": "application/json",
206
+ "X-Api-Key": this.apiKey,
207
+ },
208
+ body: JSON.stringify(clean),
209
+ });
210
+ const responseBody = await response.json();
211
+ if (!response.ok) {
212
+ throw new types_js_1.NemoFlowError(`NemoFlow API error: ${response.status} ${response.statusText}`, response.status, responseBody);
213
+ }
214
+ return responseBody;
215
+ }
216
+ async get(path) {
217
+ const response = await fetch(`${this.baseUrl}${path}`, {
218
+ method: "GET",
219
+ headers: { "X-Api-Key": this.apiKey },
220
+ });
221
+ const responseBody = await response.json();
222
+ if (!response.ok) {
223
+ throw new types_js_1.NemoFlowError(`NemoFlow API error: ${response.status} ${response.statusText}`, response.status, responseBody);
224
+ }
225
+ return responseBody;
226
+ }
227
+ }
228
+ exports.NemoFlow = NemoFlow;
229
+ // ── Error classification ─────────────────────────────────────────
230
+ function classifyError(error) {
231
+ const name = error.name.toLowerCase();
232
+ const msg = error.message.toLowerCase();
233
+ if (name.includes("timeout") || msg.includes("timeout") || msg.includes("timed out"))
234
+ return "timeout";
235
+ if (name.includes("ratelimit") || msg.includes("rate") || msg.includes("429") || msg.includes("too many"))
236
+ return "rate_limit";
237
+ if (name.includes("auth") || msg.includes("unauthorized") || msg.includes("401") || msg.includes("403"))
238
+ return "auth_failure";
239
+ if (name.includes("validation") || msg.includes("invalid") || msg.includes("422"))
240
+ return "validation_error";
241
+ if (msg.includes("not found") || msg.includes("404"))
242
+ return "not_found";
243
+ if (msg.includes("permission") || msg.includes("forbidden"))
244
+ return "permission_denied";
245
+ if (name.includes("connect") || msg.includes("connection") || msg.includes("econnrefused"))
246
+ return "connection_error";
247
+ return "server_error";
248
+ }
@@ -0,0 +1,2 @@
1
+ export { NemoFlow } from "./client.js";
2
+ export { type NemoFlowOptions, type AssessParams, type AssessResponse, type AlternativeTool, type ReportParams, type ReportResponse, type HiddenGem, type HiddenGemsResponse, type FallbackTool, type FallbackChainResponse, type GuardOptions, NemoFlowError, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NemoFlowError = exports.NemoFlow = void 0;
4
+ var client_js_1 = require("./client.js");
5
+ Object.defineProperty(exports, "NemoFlow", { enumerable: true, get: function () { return client_js_1.NemoFlow; } });
6
+ var types_js_1 = require("./types.js");
7
+ Object.defineProperty(exports, "NemoFlowError", { enumerable: true, get: function () { return types_js_1.NemoFlowError; } });
@@ -0,0 +1,83 @@
1
+ /** Options for configuring the NemoFlow client. */
2
+ export interface NemoFlowOptions {
3
+ /** Override the default API base URL. */
4
+ baseUrl?: string;
5
+ }
6
+ export interface AssessParams {
7
+ toolIdentifier: string;
8
+ context?: string;
9
+ samplePayload?: Record<string, unknown>;
10
+ }
11
+ export interface AlternativeTool {
12
+ tool: string;
13
+ score: number;
14
+ reason: string;
15
+ }
16
+ export interface AssessResponse {
17
+ reliabilityScore: number;
18
+ confidence: number;
19
+ historicalSuccessRate: string;
20
+ predictedFailureRisk: string;
21
+ commonPitfalls: string[];
22
+ recommendedMitigations: string[];
23
+ topAlternatives: AlternativeTool[];
24
+ estimatedLatencyMs: number;
25
+ lastUpdated: string;
26
+ }
27
+ export interface ReportParams {
28
+ toolIdentifier: string;
29
+ success: boolean;
30
+ errorCategory?: string;
31
+ latencyMs?: number;
32
+ context?: string;
33
+ /** Groups related tool calls in the same workflow. */
34
+ sessionId?: string;
35
+ /** Which attempt is this? 1 = first try, 2 = fallback, etc. */
36
+ attemptNumber?: number;
37
+ /** Tool identifier that was tried before this one. */
38
+ previousTool?: string;
39
+ }
40
+ export interface ReportResponse {
41
+ status: string;
42
+ toolId: string;
43
+ }
44
+ export interface HiddenGem {
45
+ tool: string;
46
+ displayName: string;
47
+ category: string;
48
+ fallbackSuccessRate: number;
49
+ timesUsedAsFallback: number;
50
+ avgLatencyMs: number | null;
51
+ }
52
+ export interface HiddenGemsResponse {
53
+ hiddenGems: HiddenGem[];
54
+ count: number;
55
+ }
56
+ export interface FallbackTool {
57
+ fallbackTool: string;
58
+ displayName: string;
59
+ timesChosenAfterFailure: number;
60
+ successRate: number;
61
+ avgLatencyMs: number | null;
62
+ }
63
+ export interface FallbackChainResponse {
64
+ tool: string;
65
+ fallbackChain: FallbackTool[];
66
+ count: number;
67
+ }
68
+ export interface GuardOptions<T> {
69
+ /** Workflow context for context-bucketed scoring. */
70
+ context?: string;
71
+ /** Minimum reliability score to proceed (0-100). Default 0 = always try. */
72
+ minScore?: number;
73
+ /** Fallback tools to try on failure, in order. */
74
+ fallbacks?: Array<{
75
+ toolIdentifier: string;
76
+ fn: () => Promise<T>;
77
+ }>;
78
+ }
79
+ export declare class NemoFlowError extends Error {
80
+ readonly status: number;
81
+ readonly body: unknown;
82
+ constructor(message: string, status: number, body: unknown);
83
+ }
package/dist/types.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NemoFlowError = void 0;
4
+ // ── Errors ──────────────────────────────────────────────────────────
5
+ class NemoFlowError extends Error {
6
+ constructor(message, status, body) {
7
+ super(message);
8
+ this.name = "NemoFlowError";
9
+ this.status = status;
10
+ this.body = body;
11
+ }
12
+ }
13
+ exports.NemoFlowError = NemoFlowError;
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "nemoflow",
3
+ "version": "0.1.0",
4
+ "description": "Reliability oracle for AI agents — pick the right tool from the start",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "nemoflow",
16
+ "ai",
17
+ "agents",
18
+ "reliability",
19
+ "tools",
20
+ "llm",
21
+ "api",
22
+ "fallback"
23
+ ],
24
+ "author": "NemoFlow <hello@nemoflow.ai>",
25
+ "license": "MIT",
26
+ "homepage": "https://nemoflow.ai",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/netvistamedia/nemoflow"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^6.0.2"
36
+ }
37
+ }