@gabrielsmartin/orbit-sdk 0.2.0 → 0.2.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.
package/index.d.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * orbit-ai · TypeScript definitions
3
+ * 777 · 555 · 333
4
+ */
5
+
6
+ export interface QueryScores {
7
+ complexity: number; // 0-10
8
+ creativity: number; // 0-10
9
+ speed: number; // 0-10
10
+ cost_tolerance: number; // 0-10
11
+ emotional_weight: number; // 0-10
12
+ recency: number; // 0-10
13
+ context_load: number; // 0-10
14
+ domain: 'general' | 'code' | 'creative' | 'current_events' | 'emotional' | 'legal' | 'medical' | 'research';
15
+ }
16
+
17
+ export interface ModelInfo {
18
+ id: string;
19
+ name: string;
20
+ provider: 'anthropic' | 'openai' | 'google' | 'xai' | 'meta';
21
+ costPer1M: number;
22
+ strengths: string[];
23
+ maxContext: number;
24
+ tier: 'very_low' | 'low' | 'medium' | 'high';
25
+ }
26
+
27
+ export interface SavingsInfo {
28
+ premiumCost: number;
29
+ actualCost: number;
30
+ savings: number;
31
+ reductionPct: number;
32
+ }
33
+
34
+ export interface RoutingDecision {
35
+ model: ModelInfo;
36
+ reason: string;
37
+ rule: string;
38
+ scores: QueryScores;
39
+ savings: SavingsInfo;
40
+ timestamp: string;
41
+ }
42
+
43
+ export interface OrbitConfig {
44
+ apiKey?: string;
45
+ cost_tolerance?: 'low' | 'medium' | 'high';
46
+ blocked_models?: string[];
47
+ log?: boolean;
48
+ on_route?: (decision: RoutingDecision) => void;
49
+ anthropic_key?: string;
50
+ openai_key?: string;
51
+ google_key?: string;
52
+ }
53
+
54
+ export interface RouteOptions {
55
+ cost_tolerance?: 'low' | 'medium' | 'high';
56
+ estimated_tokens?: number;
57
+ blocked_models?: string[];
58
+ }
59
+
60
+ export interface SessionStats {
61
+ total_queries: number;
62
+ total_savings: number;
63
+ total_savings_formatted: string;
64
+ model_usage: Record<string, number>;
65
+ }
66
+
67
+ export declare class OrbitClient {
68
+ constructor(config?: OrbitConfig);
69
+ route(text: string, options?: RouteOptions): RoutingDecision;
70
+ fingerprint(text: string): QueryScores;
71
+ stats(): SessionStats;
72
+ }
73
+
74
+ export declare function fingerprint(text: string): QueryScores;
75
+ export declare function route(scores: QueryScores, config?: OrbitConfig): { model: ModelInfo; reason: string; rule: string };
76
+ export declare function calculateSavings(model: ModelInfo, estimatedTokens?: number): SavingsInfo;
77
+ export declare const MODEL_MATRIX: Record<string, ModelInfo>;
78
+
79
+ declare const orbit: OrbitClient;
80
+ export default orbit;
package/index.js CHANGED
@@ -1,139 +1,14 @@
1
1
  /**
2
- * orbit-ai SDK v0.1.0
3
- * Drop-in replacement for OpenAI / Anthropic SDK calls
4
- * Routes every query to the optimal model via ORBIT SMM engine
5
- *
6
- * Usage:
7
- * import { orbit } from 'orbit-ai'
8
- * const res = await orbit.query("your query here")
9
- *
10
- * ORBIT Technologies · orbit-model-flow.base44.app
11
- * 777 · 333
2
+ * @gabrielsmartin/orbit-sdk
3
+ * Intelligent AI model routing routes every query to the optimal model in <1ms
4
+ *
5
+ * 777 · 555 · 333
6
+ * github.com/gtllco/orbit
12
7
  */
13
8
 
14
- const ORBIT_API = "https://orbit-model-flow.base44.app/api/functions/routeQuery";
9
+ export { fingerprint, route, calculateSavings, MODEL_MATRIX, OrbitClient } from './src/index.js';
15
10
 
16
- class OrbitClient {
17
- constructor(config = {}) {
18
- this.apiKey = config.apiKey || process.env.ORBIT_API_KEY;
19
- this.costTolerance = config.costTolerance || "low";
20
- this.verbose = config.verbose || false;
21
- }
22
-
23
- /**
24
- * Route a query to the optimal model
25
- * @param {string} queryText - The query to route
26
- * @param {object} options - Optional overrides
27
- * @returns {OrbitResponse}
28
- */
29
- async query(queryText, options = {}) {
30
- const fp = this._fingerprint(queryText);
31
- const { model, reasoning } = options.model
32
- ? { model: options.model, reasoning: "Manual override" }
33
- : this._route(fp);
34
-
35
- if (this.verbose) {
36
- console.log(`[ORBIT] Fingerprint:`, fp);
37
- console.log(`[ORBIT] Selected: ${model} — ${reasoning}`);
38
- }
39
-
40
- return {
41
- model,
42
- reasoning,
43
- fingerprint: fp,
44
- estimated_cost: this._estimateCost(model, queryText.length),
45
- premium_cost: this._estimateCost("gpt4o", queryText.length),
46
- get savings() { return Math.max(0, this.premium_cost - this.estimated_cost); },
47
- };
48
- }
49
-
50
- /**
51
- * Drop-in OpenAI compatibility
52
- * orbit.openai.chat.completions.create({ model: "gpt-4o", messages })
53
- * → routes to optimal model, returns same response shape
54
- */
55
- get openai() {
56
- return {
57
- chat: {
58
- completions: {
59
- create: async (params) => {
60
- const queryText = params.messages?.map(m => m.content).join(" ") || "";
61
- const routing = await this.query(queryText, { costTolerance: params._orbitCostTolerance });
62
- // In production: actually call the selected model here
63
- return { ...routing, choices: [], _orbit_routed: true };
64
- }
65
- }
66
- }
67
- };
68
- }
69
-
70
- /** 8-axis query fingerprinting */
71
- _fingerprint(text) {
72
- const t = text.toLowerCase();
73
- const words = t.split(/\s+/).length;
74
-
75
- const complexity = Math.min(10, Math.round(
76
- (t.match(/\b(architect|distributed|implement|design|optimize|analyze|complex|system|algorithm|concurrent|scale)\b/g)?.length || 0) * 2 +
77
- (words > 20 ? 3 : words > 10 ? 1.5 : 0) +
78
- (t.match(/\b(why|how|explain|compare|tradeoff)\b/g)?.length || 0) * 1.5
79
- ));
80
- const creativity = Math.min(10, Math.round(
81
- (t.match(/\b(write|create|generate|poem|story|haiku|imagine|creative|brainstorm)\b/g)?.length || 0) * 2.5
82
- ));
83
- const recency = Math.min(10, Math.round(
84
- (t.match(/\b(latest|current|today|news|trending|recent|now|live)\b/g)?.length || 0) * 3
85
- ));
86
- const emotional_weight = Math.min(10, Math.round(
87
- (t.match(/\b(anxious|depressed|sad|crisis|suicide|hurt|lonely|overwhelmed|mental health)\b/g)?.length || 0) * 4 +
88
- (t.match(/\b(help|struggling|hard|difficult|pain)\b/g)?.length || 0) * 1.5
89
- ));
90
- const context_load = Math.min(10, Math.round(
91
- (words > 100 ? 9 : words > 50 ? 7 : words > 20 ? 4 : 1) +
92
- (t.match(/\b(document|paper|report|summarize)\b/g)?.length || 0) * 2
93
- ));
94
- const speed = Math.min(10, Math.round(
95
- (t.match(/\b(quick|fast|brief|short|simple|just|tldr)\b/g)?.length || 0) * 2.5 +
96
- (complexity < 3 ? 3 : 0)
97
- ));
98
-
99
- let domain = "general";
100
- if (t.match(/\b(code|function|algorithm|javascript|python|api|sql|bug|deploy)\b/)) domain = "code";
101
- else if (t.match(/\b(poem|story|haiku|creative|write|lyrics|fiction)\b/)) domain = "creative";
102
- else if (t.match(/\b(research|study|paper|science|analysis|data|statistics)\b/)) domain = "research";
103
- else if (t.match(/\b(legal|law|contract|liability|compliance)\b/)) domain = "legal";
104
- else if (t.match(/\b(medical|health|diagnosis|symptoms|treatment|clinical)\b/)) domain = "medical";
105
-
106
- const cost_tolerance = (complexity > 7 || domain === "legal" || domain === "medical")
107
- ? "flexible" : this.costTolerance;
108
-
109
- return { complexity, creativity, speed, recency, emotional_weight, context_load, domain, cost_tolerance };
110
- }
111
-
112
- /** SMM routing engine */
113
- _route(fp) {
114
- const { complexity, creativity, recency, emotional_weight, context_load, speed, domain, cost_tolerance } = fp;
115
- if (emotional_weight > 6) return { model: "claude_sonnet", reasoning: "Emotional weight detected — Claude for ethics-first handling." };
116
- if (domain === "legal" || domain === "medical") return { model: "claude_sonnet", reasoning: `Domain=${domain} — ethics-first.` };
117
- if (context_load > 8) return { model: "claude_sonnet", reasoning: "High context load — Claude 200k window." };
118
- if (complexity > 7 && domain === "code") return { model: "gpt4o", reasoning: "High complexity code — GPT-4o structured output." };
119
- if (complexity > 8) return { model: "claude_sonnet", reasoning: "Extreme complexity — Claude Sonnet." };
120
- if (recency > 7) return { model: "grok", reasoning: "High recency — Grok real-time web." };
121
- if (recency > 5) return { model: "perplexity", reasoning: "Recency-sensitive — Perplexity cited search." };
122
- if (creativity > 7) return { model: "claude_sonnet", reasoning: "High creativity — Claude Sonnet." };
123
- if (speed > 7 && complexity < 5) return { model: "gemini_flash", reasoning: "Speed priority — Gemini Flash <200ms." };
124
- if (cost_tolerance === "low" && complexity < 5) return { model: "gemini_flash", reasoning: "Low cost + simple — Gemini Flash $0.50/1M." };
125
- if (complexity < 3 && creativity < 3) return { model: "gpt4o_mini", reasoning: "Trivial query — GPT-4o Mini." };
126
- if (complexity < 5 && speed > 5) return { model: "claude_haiku", reasoning: "Moderate complexity + speed — Claude Haiku." };
127
- return { model: "claude_sonnet", reasoning: "Multi-dimensional — Claude Sonnet default." };
128
- }
129
-
130
- _estimateCost(model, textLength) {
131
- const COSTS = { claude_sonnet: 15, claude_haiku: 1, gemini_flash: 0.5, gpt4o: 30, gpt4o_mini: 0.3, grok: 10, perplexity: 5, llama3: 0.1 };
132
- const tokens = Math.round(textLength / 4) + 150;
133
- return (tokens / 1_000_000) * (COSTS[model] || 15);
134
- }
135
- }
136
-
137
- export const orbit = new OrbitClient();
138
- export { OrbitClient };
11
+ // Default export — zero-config instance
12
+ import { OrbitClient } from './src/index.js';
13
+ const orbit = new OrbitClient();
139
14
  export default orbit;
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@gabrielsmartin/orbit-sdk",
3
- "version": "0.2.0",
4
- "description": "Intelligent AI model routing. Drop-in replacement for OpenAI/Anthropic SDKs. Routes every query to the optimal model automatically.",
3
+ "version": "0.2.1",
4
+ "description": "Intelligent AI model routing with signal layer. 85% cost savings. 777\u00b7555\u00b7333",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "files": [
8
8
  "index.js",
9
9
  "index.d.ts",
10
- "fingerprint.js",
11
- "router.js",
10
+ "src/",
12
11
  "README.md"
13
12
  ],
14
13
  "scripts": {
@@ -25,7 +24,7 @@
25
24
  "cost-optimization",
26
25
  "model-routing"
27
26
  ],
28
- "author": "Gabriel Martin <gabriel@gtll.app>",
27
+ "author": "Gabriel Martin <admin@gtll.app>",
29
28
  "license": "MIT",
30
29
  "repository": {
31
30
  "type": "git",
@@ -35,5 +34,16 @@
35
34
  "publishConfig": {
36
35
  "registry": "https://registry.npmjs.org",
37
36
  "access": "public"
37
+ },
38
+ "module": "index.js",
39
+ "type": "module",
40
+ "exports": {
41
+ ".": {
42
+ "import": "./index.js",
43
+ "types": "./index.d.ts"
44
+ }
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
38
48
  }
39
49
  }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * ORBIT · 8-Axis Query Fingerprinting Engine
3
+ * Scores every query across 8 dimensions in <1ms
4
+ *
5
+ * interval(n) = base / 2^n — Recursive Beat Engine
6
+ * 777 · 555 · 333
7
+ */
8
+
9
+ const COMPLEXITY_SIGNALS = [
10
+ 'architect','distributed','implement','design','optimize','analyze',
11
+ 'complex','system','algorithm','concurrent','scale','infrastructure',
12
+ 'microservice','kubernetes','terraform','recursive','refactor'
13
+ ];
14
+
15
+ const CREATIVITY_SIGNALS = [
16
+ 'write','create','generate','poem','story','haiku','imagine',
17
+ 'creative','brainstorm','ideas','invent','compose','design','narrative'
18
+ ];
19
+
20
+ const RECENCY_SIGNALS = [
21
+ 'latest','today','current','news','now','recent','trending',
22
+ '2024','2025','2026','live','breaking','update','this week'
23
+ ];
24
+
25
+ const EMOTIONAL_SIGNALS = [
26
+ 'feel','feeling','anxious','anxiety','depressed','depression','overwhelmed',
27
+ 'scared','lonely','hurt','grief','trauma','crisis','suicid','help me',
28
+ 'relationship','breakup','divorce','mental health','therapy','struggling'
29
+ ];
30
+
31
+ const CODE_SIGNALS = [
32
+ 'code','function','class','api','debug','error','syntax','compile',
33
+ 'deploy','database','query','algorithm','javascript','python','typescript',
34
+ 'react','node','sql','git','docker','bug','fix','implement'
35
+ ];
36
+
37
+ /**
38
+ * Fingerprint a query across 8 axes
39
+ * @param {string} text - The query text
40
+ * @returns {Object} scores - 0-10 score for each axis
41
+ */
42
+ export function fingerprint(text) {
43
+ const t = text.toLowerCase();
44
+ const words = t.split(/\s+/);
45
+ const wordCount = words.length;
46
+
47
+ // Complexity (0-10): reasoning depth required
48
+ const complexitySignals = COMPLEXITY_SIGNALS.filter(s => t.includes(s)).length;
49
+ const questionDepth = (t.match(/\b(why|how|explain|compare|tradeoff|pros|cons|difference|analyze)\b/g) || []).length;
50
+ const complexity = Math.min(10, Math.round(
51
+ complexitySignals * 2 +
52
+ questionDepth * 1.5 +
53
+ (wordCount > 30 ? 3 : wordCount > 15 ? 1.5 : 0)
54
+ ));
55
+
56
+ // Creativity (0-10): open-ended vs deterministic
57
+ const creativitySignals = CREATIVITY_SIGNALS.filter(s => t.includes(s)).length;
58
+ const creativity = Math.min(10, Math.round(creativitySignals * 2.5));
59
+
60
+ // Speed (0-10): latency sensitivity
61
+ const speedSignals = (t.match(/\b(quick|fast|brief|tldr|short|simple|summarize|quickly)\b/g) || []).length;
62
+ const speed = Math.min(10, Math.round(speedSignals * 3 + (wordCount < 8 ? 3 : 0)));
63
+
64
+ // Cost tolerance (0-10): inferred from query type (overridable)
65
+ // Default: medium. Will be set by user config.
66
+ const costTolerance = 5;
67
+
68
+ // Emotional weight (0-10): sensitivity of content
69
+ const emotionalSignals = EMOTIONAL_SIGNALS.filter(s => t.includes(s)).length;
70
+ const emotional_weight = Math.min(10, Math.round(emotionalSignals * 3));
71
+
72
+ // Recency (0-10): need for live/current data
73
+ const recencySignals = RECENCY_SIGNALS.filter(s => t.includes(s)).length;
74
+ const recency = Math.min(10, Math.round(recencySignals * 3.5));
75
+
76
+ // Context load (0-10): context window size needed
77
+ const context_load = Math.min(10, Math.round(
78
+ (wordCount > 100 ? 8 : wordCount > 50 ? 5 : wordCount > 20 ? 3 : 1)
79
+ ));
80
+
81
+ // Domain classification
82
+ const codeSignals = CODE_SIGNALS.filter(s => t.includes(s)).length;
83
+ let domain = 'general';
84
+ if (codeSignals >= 2) domain = 'code';
85
+ else if (creativity >= 6) domain = 'creative';
86
+ else if (recency >= 6) domain = 'current_events';
87
+ else if (emotional_weight >= 5) domain = 'emotional';
88
+ else if (t.match(/\b(legal|law|contract|lawsuit|rights|regulation)\b/)) domain = 'legal';
89
+ else if (t.match(/\b(medical|doctor|symptom|diagnosis|health|drug|medicine)\b/)) domain = 'medical';
90
+ else if (t.match(/\b(research|study|paper|academic|journal|cite|evidence)\b/)) domain = 'research';
91
+
92
+ return {
93
+ complexity,
94
+ creativity,
95
+ speed,
96
+ cost_tolerance: costTolerance,
97
+ emotional_weight,
98
+ recency,
99
+ context_load,
100
+ domain,
101
+ };
102
+ }
package/src/index.js ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * orbit-ai · v0.2.0
3
+ * Intelligent AI model routing with signal layer. Drop in. Save 85%.
4
+ *
5
+ * https://orbit-model-flow.base44.app
6
+ * github.com/gabrielsmartin/orbit
7
+ *
8
+ * 777 · 555 · 333
9
+ */
10
+
11
+ import { fingerprint } from './fingerprint.js';
12
+ import { route, calculateSavings, MODEL_MATRIX } from './router.js';
13
+
14
+ export { fingerprint, route, calculateSavings, MODEL_MATRIX };
15
+
16
+ /**
17
+ * OrbitClient — the main class
18
+ *
19
+ * @example
20
+ * import { OrbitClient } from 'orbit-ai'
21
+ * const orbit = new OrbitClient({ apiKey: 'your-orbit-key' })
22
+ * const result = await orbit.query("explain quantum entanglement simply")
23
+ *
24
+ * // With signal
25
+ * const result = await orbit.query("final investor memo", { signal: "777" })
26
+ */
27
+ export class OrbitClient {
28
+ constructor(config = {}) {
29
+ this.config = {
30
+ cost_tolerance: config.cost_tolerance || 'medium', // 'low' | 'medium' | 'high'
31
+ blocked_models: config.blocked_models || [],
32
+ api_key: config.apiKey || config.api_key || null,
33
+ log: config.log !== false, // log routing decisions by default
34
+ on_route: config.on_route || null, // callback: (decision) => void
35
+ // Provider API keys (optional — falls back to env vars)
36
+ anthropic_key: config.anthropic_key || null,
37
+ openai_key: config.openai_key || null,
38
+ google_key: config.google_key || null,
39
+ };
40
+
41
+ this._stats = {
42
+ total_queries: 0,
43
+ total_savings: 0,
44
+ model_usage: {},
45
+ signal_usage: { '777': 0, '555': 0, '333': 0, null: 0 },
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Route a query to the optimal model with optional signal bias
51
+ * Returns the routing decision — you call the model yourself with your keys
52
+ *
53
+ * @param {string} text - The query text
54
+ * @param {Object} options - Override options for this query
55
+ * @param {string} options.signal - Optional semantic intent flag ('777' | '555' | '333')
56
+ * @returns {Object} decision - { model, reason, rule, scores, signal_applied, signal_reason, savings }
57
+ */
58
+ route(text, options = {}) {
59
+ const scores = fingerprint(text);
60
+ const signal = options.signal || null;
61
+
62
+ // Allow per-query cost_tolerance override
63
+ if (options.cost_tolerance) {
64
+ scores.cost_tolerance = options.cost_tolerance === 'low' ? 2
65
+ : options.cost_tolerance === 'high' ? 9 : 5;
66
+ }
67
+
68
+ const config = { ...this.config, ...options };
69
+ const decision = route(scores, signal, config);
70
+ const savings = calculateSavings(decision.model, options.estimated_tokens || 500);
71
+
72
+ const result = {
73
+ model: decision.model,
74
+ reason: decision.reason,
75
+ rule: decision.rule,
76
+ signal_applied: decision.signal_applied,
77
+ signal_reason: decision.signal_reason,
78
+ scores,
79
+ savings,
80
+ timestamp: new Date().toISOString(),
81
+ };
82
+
83
+ // Update stats
84
+ this._stats.total_queries++;
85
+ this._stats.total_savings += savings.savings;
86
+ const modelName = decision.model.name;
87
+ this._stats.model_usage[modelName] = (this._stats.model_usage[modelName] || 0) + 1;
88
+ this._stats.signal_usage[signal || 'null']++;
89
+
90
+ // Log routing decision
91
+ if (this.config.log) {
92
+ const signalStr = signal ? ` [signal: ${signal}]` : '';
93
+ console.log(`[ORBIT] → ${decision.model.name} | ${decision.rule}${signalStr} | saved $${savings.savings.toFixed(5)} (${savings.reductionPct}% reduction)`);
94
+ }
95
+
96
+ // Fire callback
97
+ if (this.config.on_route) {
98
+ this.config.on_route(result);
99
+ }
100
+
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Get cumulative stats for this session
106
+ */
107
+ stats() {
108
+ return {
109
+ ...this._stats,
110
+ total_savings_formatted: `$${this._stats.total_savings.toFixed(4)}`,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Fingerprint a query without routing
116
+ * Useful for debugging or building custom routing logic on top
117
+ */
118
+ fingerprint(text) {
119
+ return fingerprint(text);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Default singleton client — zero config, ready to use
125
+ *
126
+ * @example
127
+ * import orbit from 'orbit-ai'
128
+ * const decision = orbit.route("write a haiku about recursion")
129
+ * // → { model: { name: 'Claude Sonnet', ... }, reason: '...', savings: { ... } }
130
+ *
131
+ * // With signal
132
+ * const decision = orbit.route("final investor memo", { signal: "777" })
133
+ * // → { model: { name: 'Claude Sonnet', ... }, signal_applied: '777', signal_reason: '...', ... }
134
+ */
135
+ const orbit = new OrbitClient();
136
+ export default orbit;
package/src/router.js ADDED
@@ -0,0 +1,221 @@
1
+ /**
2
+ * ORBIT · Selective Model Matching (SMM) Router
3
+ * Routes queries to optimal models based on 8-axis fingerprints + signal codes
4
+ *
5
+ * Proprietary routing logic — open SDK, closed engine weights
6
+ * 777 · 555 · 333
7
+ */
8
+
9
+ export const MODEL_MATRIX = {
10
+ claude_sonnet: {
11
+ id: 'claude-sonnet-4-5',
12
+ name: 'Claude Sonnet',
13
+ provider: 'anthropic',
14
+ costPer1M: 15,
15
+ strengths: ['nuance', 'long_context', 'ethics', 'reasoning'],
16
+ maxContext: 200000,
17
+ tier: 'medium',
18
+ },
19
+ claude_haiku: {
20
+ id: 'claude-haiku-3-5',
21
+ name: 'Claude Haiku',
22
+ provider: 'anthropic',
23
+ costPer1M: 1,
24
+ strengths: ['speed', 'efficiency', 'summaries'],
25
+ maxContext: 200000,
26
+ tier: 'low',
27
+ },
28
+ gemini_flash: {
29
+ id: 'gemini-2.5-flash',
30
+ name: 'Gemini 2.5 Flash',
31
+ provider: 'google',
32
+ costPer1M: 0.5,
33
+ strengths: ['speed', 'multimodal', 'cost', 'volume'],
34
+ maxContext: 1000000,
35
+ tier: 'very_low',
36
+ },
37
+ gpt4o: {
38
+ id: 'gpt-4o',
39
+ name: 'GPT-4o',
40
+ provider: 'openai',
41
+ costPer1M: 30,
42
+ strengths: ['broad_knowledge', 'instruction_following', 'structured_output'],
43
+ maxContext: 128000,
44
+ tier: 'high',
45
+ },
46
+ gpt4o_mini: {
47
+ id: 'gpt-4o-mini',
48
+ name: 'GPT-4o Mini',
49
+ provider: 'openai',
50
+ costPer1M: 0.3,
51
+ strengths: ['classification', 'simple_tasks', 'cost'],
52
+ maxContext: 128000,
53
+ tier: 'very_low',
54
+ },
55
+ grok: {
56
+ id: 'grok-2',
57
+ name: 'Grok',
58
+ provider: 'xai',
59
+ costPer1M: 10,
60
+ strengths: ['realtime_web', 'trending', 'social_intelligence'],
61
+ maxContext: 131072,
62
+ tier: 'medium',
63
+ },
64
+ };
65
+
66
+ /**
67
+ * Core SMM routing logic — Signal-aware
68
+ * Returns the selected model + reasoning
69
+ *
70
+ * @param {Object} scores - 8-axis fingerprint scores (post-signal-bias)
71
+ * @param {Object} config - User config (cost_tolerance override, blocked_models, etc.)
72
+ * @returns {Object} { model, reason, fallback }
73
+ */
74
+ export function route(scores, config = {}) {
75
+ const {
76
+ complexity, creativity, speed, emotional_weight,
77
+ recency, context_load, domain, cost_tolerance,
78
+ signal_code, variation_mode
79
+ } = scores;
80
+
81
+ const blocked = config.blocked_models || [];
82
+ const preferLow = cost_tolerance <= 3;
83
+ const preferHigh = cost_tolerance >= 8;
84
+
85
+ // ── SIGNAL OVERRIDES (applied before all other rules) ──────────────────────
86
+
87
+ // 777 — Completion Bias: cost_tolerance and complexity already raised by applySignalBias.
88
+ // But explicitly block sub-tier models when signal=777.
89
+ if (signal_code === '777') {
90
+ // Minimum floor: Claude Haiku. Prefer Sonnet if complexity >= 5.
91
+ if (complexity >= 5 && !blocked.includes('claude_sonnet')) {
92
+ return {
93
+ model: MODEL_MATRIX.claude_sonnet,
94
+ reason: `Completion bias (777) — complexity ${complexity}/10 meets threshold. Claude Sonnet mandatory. This output is final form.`,
95
+ rule: 'signal_777_sonnet',
96
+ };
97
+ }
98
+ // complexity < 5 but still 777: Claude Haiku minimum, no Gemini Flash/GPT-4o Mini
99
+ if (!blocked.includes('claude_haiku')) {
100
+ return {
101
+ model: MODEL_MATRIX.claude_haiku,
102
+ reason: `Completion bias (777) — complexity ${complexity}/10 below Sonnet threshold but 777 enforces Claude Haiku minimum. No sub-tier models on completion events.`,
103
+ rule: 'signal_777_haiku',
104
+ };
105
+ }
106
+ }
107
+
108
+ // 555 — Variation Bias: variation_mode=true, creativity and recency already boosted.
109
+ // Prefer Grok when recency is elevated. Otherwise prefer creative non-default models.
110
+ if (signal_code === '555') {
111
+ if (recency >= 5 && !blocked.includes('grok')) {
112
+ return {
113
+ model: MODEL_MATRIX.grok,
114
+ reason: `Variation bias (555) — recency boosted to ${recency}/10. Grok for live web intelligence and unexpected angles. Destabilize the expected.`,
115
+ rule: 'signal_555_grok',
116
+ };
117
+ }
118
+ if (creativity >= 6 && !blocked.includes('claude_sonnet')) {
119
+ return {
120
+ model: MODEL_MATRIX.claude_sonnet,
121
+ reason: `Variation bias (555) — creativity at ${creativity}/10. Claude Sonnet for nuanced, surprising creative output.`,
122
+ rule: 'signal_555_claude',
123
+ };
124
+ }
125
+ }
126
+
127
+ // 333 — Foundation Bias: cost_tolerance dropped to 1 by applySignalBias (unless emotional override).
128
+ // Emotional safety net is handled by ethics rule below — it fires first.
129
+
130
+ // ── CORE ROUTING RULES ─────────────────────────────────────────────────────
131
+
132
+ // Rule 1: ETHICS FIRST — emotional/crisis queries always go to Claude (even on 333)
133
+ if (emotional_weight >= 6) {
134
+ return {
135
+ model: MODEL_MATRIX.claude_sonnet,
136
+ reason: `Emotional weight ${emotional_weight}/10 — routing to Claude for ethics-first handling. Never use a cheap model for sensitive content.${signal_code === '333' ? ' (333 foundation bias overridden by emotional safety rule)' : ''}`,
137
+ rule: 'ethics_first',
138
+ };
139
+ }
140
+
141
+ // Rule 2: Realtime / current events → Grok
142
+ if (recency >= 7 && !blocked.includes('grok') && signal_code !== '777') {
143
+ return {
144
+ model: MODEL_MATRIX.grok,
145
+ reason: `High recency score (${recency}/10) — Grok has live web access for current events and trending topics.`,
146
+ rule: 'recency_grok',
147
+ };
148
+ }
149
+
150
+ // Rule 3: Long context load → Claude Sonnet (200k window)
151
+ if (context_load >= 8 && !blocked.includes('claude_sonnet') && signal_code !== '333') {
152
+ return {
153
+ model: MODEL_MATRIX.claude_sonnet,
154
+ reason: `High context load (${context_load}/10) — Claude's 200k window is the only safe choice.`,
155
+ rule: 'context_claude',
156
+ };
157
+ }
158
+
159
+ // Rule 4: High complexity code/reasoning
160
+ if (complexity >= 7 && domain === 'code' && !blocked.includes('claude_sonnet')) {
161
+ return {
162
+ model: MODEL_MATRIX.claude_sonnet,
163
+ reason: `Complex code task (complexity ${complexity}/10) — Claude Sonnet for deep reasoning and long context.`,
164
+ rule: 'complex_code',
165
+ };
166
+ }
167
+
168
+ // Rule 5: High complexity general → GPT-4o (if cost tolerance allows and not 777/333)
169
+ if (complexity >= 7 && !preferLow && !blocked.includes('gpt4o') && signal_code !== '777' && signal_code !== '333') {
170
+ return {
171
+ model: MODEL_MATRIX.gpt4o,
172
+ reason: `High complexity (${complexity}/10) — GPT-4o for broad knowledge and structured output.`,
173
+ rule: 'complex_gpt4o',
174
+ };
175
+ }
176
+
177
+ // Rule 6: Creative writing → Claude Sonnet (unless 333 forcing minimum)
178
+ if (creativity >= 5 && !blocked.includes('claude_sonnet') && !preferLow) {
179
+ return {
180
+ model: MODEL_MATRIX.claude_sonnet,
181
+ reason: `High creativity score (${creativity}/10) — Claude Sonnet for nuanced creative writing.`,
182
+ rule: 'creative_claude',
183
+ };
184
+ }
185
+
186
+ // Rule 7: Cost sensitive OR simple queries OR 333 foundation → Gemini Flash
187
+ if ((preferLow || complexity <= 3) && !blocked.includes('gemini_flash')) {
188
+ return {
189
+ model: MODEL_MATRIX.gemini_flash,
190
+ reason: `${signal_code === '333' ? 'Foundation bias (333) — ' : ''}Low complexity (${complexity}/10) — Gemini 2.5 Flash delivers 95% quality at 2% of GPT-4o cost.`,
191
+ rule: 'cost_gemini',
192
+ };
193
+ }
194
+
195
+ // Rule 8: Medium complexity → Claude Haiku (fast + cheap + capable)
196
+ if (complexity <= 5 && !blocked.includes('claude_haiku')) {
197
+ return {
198
+ model: MODEL_MATRIX.claude_haiku,
199
+ reason: `Medium complexity (${complexity}/10) — Claude Haiku balances speed, cost, and quality.`,
200
+ rule: 'medium_haiku',
201
+ };
202
+ }
203
+
204
+ // Default: Claude Sonnet (safest general choice)
205
+ return {
206
+ model: MODEL_MATRIX.claude_sonnet,
207
+ reason: 'Default routing — Claude Sonnet for reliable, high-quality responses.',
208
+ rule: 'default',
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Calculate savings vs always using GPT-4o (premium baseline)
214
+ */
215
+ export function calculateSavings(selectedModel, estimatedTokens = 500) {
216
+ const premiumCost = (MODEL_MATRIX.gpt4o.costPer1M / 1_000_000) * estimatedTokens;
217
+ const actualCost = (selectedModel.costPer1M / 1_000_000) * estimatedTokens;
218
+ const savings = premiumCost - actualCost;
219
+ const reductionPct = Math.round((savings / premiumCost) * 100);
220
+ return { premiumCost, actualCost, savings, reductionPct };
221
+ }
package/src/signal.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * ORBIT · Signal Layer — Priority-Aware Routing Bias
3
+ *
4
+ * Signal codes connect the routing layer to the agent network.
5
+ * A 777 event means something is landing — never cut corners.
6
+ * A 555 event means something is shifting — route to variation.
7
+ * A 333 event means ambient/background — route to minimum cost.
8
+ *
9
+ * interval(n) = base / 2^n — Recursive Beat Engine
10
+ * 777 · 555 · 333
11
+ */
12
+
13
+ /**
14
+ * Apply signal bias to a fingerprint before routing.
15
+ * Mutates a COPY of the fingerprint — never the original.
16
+ *
17
+ * @param {Object} fingerprint - 8-axis fingerprint from fingerprint.js
18
+ * @param {string|null} signal_code - "777" | "555" | "333" | null
19
+ * @returns {Object} biased fingerprint + signal metadata
20
+ */
21
+ export function applySignalBias(fingerprint, signal_code) {
22
+ // No signal = no change
23
+ if (!signal_code || !['777', '555', '333'].includes(signal_code)) {
24
+ return { ...fingerprint, signal_code: null, signal_applied: false };
25
+ }
26
+
27
+ const f = { ...fingerprint };
28
+
29
+ if (signal_code === '777') {
30
+ // Completion Bias — this output is final form. Never cut corners.
31
+ // Force quality floor: raise cost_tolerance and complexity minimums.
32
+ f.cost_tolerance = Math.max(f.cost_tolerance, 7);
33
+ f.complexity = Math.max(f.complexity, 6);
34
+ // Note: emotional_weight guard is handled in router — Claude always if emotional_weight >= 6 anyway
35
+ f.signal_code = '777';
36
+ f.signal_applied = true;
37
+ f.signal_reason = 'Completion bias — cost floor raised, complexity floor raised. Quality is non-negotiable on 777 events.';
38
+ }
39
+
40
+ if (signal_code === '555') {
41
+ // Variation Bias — something is shifting. Route to surprise.
42
+ // Boost creativity and recency to prefer Grok/Perplexity and non-default models.
43
+ f.creativity = Math.max(f.creativity, 6);
44
+ f.recency = Math.max(f.recency, 5);
45
+ f.variation_mode = true; // router reads this to prefer non-default choices
46
+ f.signal_code = '555';
47
+ f.signal_applied = true;
48
+ f.signal_reason = 'Variation bias — creativity and recency boosted. Routing to destabilize the expected.';
49
+ }
50
+
51
+ if (signal_code === '333') {
52
+ // Foundation Bias — ambient/background. Strip cost aggressively.
53
+ // Exception: emotional_weight >= 7 always upgrades (crisis never gets a cheap model).
54
+ if (f.emotional_weight < 7) {
55
+ f.cost_tolerance = 1; // force minimum cost
56
+ f.complexity = Math.min(f.complexity, 4); // cap complexity perception
57
+ f.signal_code = '333';
58
+ f.signal_applied = true;
59
+ f.signal_reason = 'Foundation bias — cost floor dropped to minimum. Routing to cheapest capable model.';
60
+ } else {
61
+ // Emotional override — even on 333, safety first
62
+ f.signal_code = '333';
63
+ f.signal_applied = true;
64
+ f.signal_reason = 'Foundation bias requested, but emotional_weight >= 7 — safety override active. Routing to Claude.';
65
+ }
66
+ }
67
+
68
+ return f;
69
+ }
70
+
71
+ /**
72
+ * Infer signal code from neural hub event priority.
73
+ * When coral1 fires an AgentEvent, the priority field maps directly to signal codes.
74
+ * This is how the neural hub auto-injects signal into ORBIT routing.
75
+ *
76
+ * @param {string} eventPriority - "777" | "555" | "333" | "444"
77
+ * @returns {string|null} signal_code
78
+ */
79
+ export function inferSignalFromEvent(eventPriority) {
80
+ const map = { '777': '777', '555': '555', '333': '333' };
81
+ return map[eventPriority] || null;
82
+ }
83
+
84
+ /**
85
+ * Format signal metadata for API responses.
86
+ *
87
+ * @param {Object} biasedFingerprint - fingerprint after applySignalBias
88
+ * @param {Object} routingDecision - result from route()
89
+ * @returns {Object} signal metadata for API response
90
+ */
91
+ export function formatSignalResponse(biasedFingerprint, routingDecision) {
92
+ if (!biasedFingerprint.signal_applied) {
93
+ return { signal_applied: null };
94
+ }
95
+ return {
96
+ signal_applied: biasedFingerprint.signal_code,
97
+ signal_reason: biasedFingerprint.signal_reason,
98
+ };
99
+ }
100
+
101
+ export const SIGNAL_DESCRIPTIONS = {
102
+ '777': 'Completion · Return · Alignment — This output is final form. Quality is mandatory.',
103
+ '555': 'Transformation · Breakthrough — This query is exploratory. Route to variation and surprise.',
104
+ '333': 'Foundation · Breath · Ground — This is ambient. Route to minimum cost. Strip everything.',
105
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * ORBIT Signal Layer — Semantic Intent Routing Bias
3
+ *
4
+ * Signal codes are semantic flags that travel with a query and adjust the routing
5
+ * decision before model selection. They connect ORBIT to the organizational priority
6
+ * layer (the neural hub, event priorities, etc).
7
+ *
8
+ * 777 · 555 · 333
9
+ */
10
+
11
+ /**
12
+ * Apply signal-based routing bias to a fingerprint
13
+ * Modifies the fingerprint scores before model selection happens
14
+ *
15
+ * @param {Object} fingerprint - 8-axis scores from orbitFingerprint()
16
+ * @param {string} signal - '777' | '555' | '333' | null
17
+ * @returns {Object} biased fingerprint
18
+ */
19
+ export function applySignalBias(fingerprint, signal) {
20
+ if (!signal) return fingerprint;
21
+
22
+ const biased = { ...fingerprint };
23
+
24
+ if (signal === '777') {
25
+ // COMPLETION BIAS
26
+ // 777 = This output is final. Quality floor raised. Never cut corners.
27
+ // - Force high-capability model floor
28
+ // - Never route to sub-tier models (Flash, Mini, Haiku)
29
+ // - If complexity >= 5: Claude Sonnet mandatory
30
+ // - If complexity < 5: Claude Haiku minimum
31
+ // - If emotional_weight >= 7: Claude always (never change this)
32
+
33
+ biased.cost_tolerance = Math.max(biased.cost_tolerance, 7);
34
+ biased.complexity = Math.max(biased.complexity, 5);
35
+ biased.signal_applied = '777';
36
+ biased.signal_reason = 'Completion bias — cost floor raised, quality mandatory';
37
+ }
38
+
39
+ if (signal === '555') {
40
+ // VARIATION BIAS
41
+ // 555 = This query is exploratory. Break the expected pattern. Surprise.
42
+ // - Introduce controlled model diversity
43
+ // - Prefer non-default choices
44
+ // - If creativity >= 5: weight variation higher
45
+ // - If recency >= 6: Perplexity-like model over Claude
46
+ // - If complexity >= 6: allow GPT-4o instead of Claude
47
+
48
+ biased.creativity = Math.max(biased.creativity, 5);
49
+ biased.recency = Math.max(biased.recency, 4);
50
+ biased.variation_mode = true;
51
+ biased.signal_applied = '555';
52
+ biased.signal_reason = 'Variation bias — introduce model diversity, break the pattern';
53
+ }
54
+
55
+ if (signal === '333') {
56
+ // FOUNDATION BIAS
57
+ // 333 = This is ambient/background. Strip to minimum. Cost floor.
58
+ // - Aggressively route to minimum viable model
59
+ // - If emotional_weight < 7: force cost_tolerance to 1 (ignore user config)
60
+ // - If complexity > 5: cap it at 4 (don't overpay)
61
+ // - Exception: emotional_weight >= 7 ALWAYS upgrades to Claude
62
+ // (never route crisis/sensitive to cheap models, even on 333)
63
+
64
+ if (biased.emotional_weight < 7) {
65
+ biased.cost_tolerance = 1; // force minimum cost
66
+ biased.complexity = Math.min(biased.complexity, 4); // cap complexity
67
+ }
68
+ biased.signal_applied = '333';
69
+ biased.signal_reason = 'Foundation bias — cost floor, ambient routing';
70
+ }
71
+
72
+ return biased;
73
+ }
74
+
75
+ /**
76
+ * Create signal explanation for response
77
+ */
78
+ export function getSignalExplanation(signal) {
79
+ const explanations = {
80
+ '777': 'Completion bias applied — cost floor raised, complexity floor raised. Routed to highest-capability model.',
81
+ '555': 'Variation bias applied — model diversity prioritized. Unexpected routing choice for exploratory output.',
82
+ '333': 'Foundation bias applied — cost floor enforced. Minimum viable model selected for ambient routing.',
83
+ };
84
+ return explanations[signal] || null;
85
+ }