@spectyra/sdk 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,244 @@
1
+ # Spectyra SDK
2
+
3
+ **SDK-first agent runtime control: routing, budgets, tool gating, telemetry**
4
+
5
+ Spectyra SDK provides agent runtime control for Claude Agent SDK and other agent frameworks. Control model selection, budgets, tool permissions, and telemetry—all without requiring a proxy.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @spectyra/sdk
11
+ # or
12
+ pnpm add @spectyra/sdk
13
+ # or
14
+ yarn add @spectyra/sdk
15
+ ```
16
+
17
+ ## Two Integration Styles
18
+
19
+ ### A) Local SDK Mode (Default)
20
+
21
+ **No proxy required.** SDK makes local decisions about agent options.
22
+
23
+ ```typescript
24
+ import { createSpectyra } from '@spectyra/sdk';
25
+
26
+ // Local mode - works offline, no API calls
27
+ const spectyra = createSpectyra({ mode: "local" });
28
+
29
+ // One line integration with Claude Agent SDK
30
+ const options = spectyra.agentOptions(ctx, prompt);
31
+ const result = await agent.query({ prompt, options });
32
+ ```
33
+
34
+ ### B) API Control Plane Mode (Enterprise)
35
+
36
+ SDK calls Spectyra API to fetch agent options and stream events for telemetry.
37
+
38
+ ```typescript
39
+ import { createSpectyra } from '@spectyra/sdk';
40
+
41
+ const spectyra = createSpectyra({
42
+ mode: "api",
43
+ endpoint: "https://spectyra.up.railway.app/v1",
44
+ apiKey: process.env.SPECTYRA_API_KEY,
45
+ });
46
+
47
+ // Fetch options from remote API
48
+ const response = await spectyra.agentOptionsRemote(ctx, promptMeta);
49
+ const result = await agent.query({ prompt, options: response.options });
50
+
51
+ // Stream events for telemetry
52
+ for await (const event of agentStream) {
53
+ await spectyra.sendAgentEvent(ctx, event);
54
+ }
55
+ ```
56
+
57
+ ## Quick Start: Local Mode
58
+
59
+ ```typescript
60
+ import { createSpectyra } from '@spectyra/sdk';
61
+ import { Agent } from '@anthropic-ai/sdk/agent';
62
+
63
+ // Create Spectyra instance (local mode - default)
64
+ const spectyra = createSpectyra({ mode: "local" });
65
+
66
+ // Create context for this agent run
67
+ const ctx = {
68
+ runId: crypto.randomUUID(),
69
+ budgetUsd: 2.5,
70
+ tags: { project: "my-app" },
71
+ };
72
+
73
+ // Get agent options (synchronous, local decision)
74
+ const prompt = "Fix the bug in src/utils.ts";
75
+ const options = spectyra.agentOptions(ctx, prompt);
76
+
77
+ // Use with Claude Agent SDK
78
+ const agent = new Agent({
79
+ apiKey: process.env.ANTHROPIC_API_KEY,
80
+ ...options, // Model, budget, tools, permissions
81
+ });
82
+
83
+ const result = await agent.query({ prompt });
84
+ ```
85
+
86
+ ## Quick Start: API Mode
87
+
88
+ ```typescript
89
+ import { createSpectyra } from '@spectyra/sdk';
90
+
91
+ const spectyra = createSpectyra({
92
+ mode: "api",
93
+ endpoint: "https://spectyra.up.railway.app/v1",
94
+ apiKey: process.env.SPECTYRA_API_KEY,
95
+ });
96
+
97
+ const ctx = {
98
+ runId: crypto.randomUUID(),
99
+ budgetUsd: 5.0,
100
+ };
101
+
102
+ // Fetch options from remote API
103
+ const promptMeta = {
104
+ promptChars: prompt.length,
105
+ path: "code",
106
+ repoId: "my-repo",
107
+ language: "typescript",
108
+ };
109
+
110
+ const response = await spectyra.agentOptionsRemote(ctx, promptMeta);
111
+ // response.run_id is set automatically
112
+
113
+ // Use options with agent
114
+ const agent = new Agent({
115
+ apiKey: process.env.ANTHROPIC_API_KEY,
116
+ ...response.options,
117
+ });
118
+
119
+ // Stream events for telemetry
120
+ const stream = agent.queryStream({ prompt });
121
+ await spectyra.observeAgentStream(ctx, stream);
122
+ ```
123
+
124
+ ## API Reference
125
+
126
+ ### `createSpectyra(config?: SpectyraConfig)`
127
+
128
+ Create a Spectyra SDK instance.
129
+
130
+ **Config:**
131
+ - `mode?: "local" | "api"` - Default: `"local"`
132
+ - `endpoint?: string` - Required for API mode
133
+ - `apiKey?: string` - Required for API mode
134
+ - `defaults?: { budgetUsd?: number; models?: { small?, medium?, large? } }`
135
+
136
+ **Returns:** `SpectyraInstance`
137
+
138
+ ### `agentOptions(ctx: SpectyraCtx, prompt: string | PromptMeta): ClaudeAgentOptions`
139
+
140
+ Get agent options locally (synchronous, offline).
141
+
142
+ **Context:**
143
+ - `runId?: string` - Run identifier
144
+ - `budgetUsd?: number` - Budget for this run
145
+ - `tags?: Record<string, string>` - Tags for analytics
146
+
147
+ **Returns:** Claude Agent SDK-compatible options
148
+
149
+ ### `agentOptionsRemote(ctx: SpectyraCtx, promptMeta: PromptMeta): Promise<AgentOptionsResponse>`
150
+
151
+ Fetch agent options from remote API (asynchronous).
152
+
153
+ **PromptMeta:**
154
+ - `promptChars: number` - Prompt character count
155
+ - `path?: "code" | "talk"` - Path type
156
+ - `repoId?: string` - Repository identifier
157
+ - `language?: string` - Programming language
158
+ - `filesChanged?: number` - Number of files changed
159
+
160
+ **Returns:** Options with `run_id` and `reasons`
161
+
162
+ ### `sendAgentEvent(ctx: SpectyraCtx, event: any): Promise<void>`
163
+
164
+ Send agent event for telemetry (best-effort, non-blocking).
165
+
166
+ ### `observeAgentStream(ctx: SpectyraCtx, stream: AsyncIterable<any>): Promise<void>`
167
+
168
+ Observe agent stream and forward events automatically.
169
+
170
+ ## Agent Options
171
+
172
+ The SDK returns Claude Agent SDK-compatible options:
173
+
174
+ ```typescript
175
+ interface ClaudeAgentOptions {
176
+ model?: string; // e.g., "claude-3-5-sonnet-latest"
177
+ maxBudgetUsd?: number; // Budget limit
178
+ allowedTools?: string[]; // e.g., ["Read", "Edit", "Bash"]
179
+ permissionMode?: "acceptEdits"; // Permission mode
180
+ canUseTool?: (tool, input) => boolean; // Tool gate function
181
+ }
182
+ ```
183
+
184
+ ## Local Decision Logic
185
+
186
+ In local mode, the SDK uses simple heuristics:
187
+
188
+ - **Prompt length < 6k chars** → Small tier → `claude-3-5-haiku-latest`
189
+ - **Prompt length < 20k chars** → Medium tier → `claude-3-5-sonnet-latest`
190
+ - **Prompt length ≥ 20k chars** → Large tier → `claude-3-7-sonnet-latest`
191
+
192
+ Default budget: $2.5 per run
193
+ Default tools: `["Read", "Edit", "Bash", "Glob"]`
194
+ Default permissions: `"acceptEdits"`
195
+
196
+ ## Tool Gating
197
+
198
+ The SDK includes a default `canUseTool` gate that:
199
+ - ✅ Allows: Read, Edit, Bash (safe commands), Glob
200
+ - ❌ Denies: Bash commands containing `curl`, `wget`, `ssh`, `scp`, `nc`, `telnet`
201
+
202
+ You can override this by providing your own `canUseTool` function in the options.
203
+
204
+ ## Remote Chat Optimization (Optional)
205
+
206
+ For chat optimization (not agentic), use the legacy client:
207
+
208
+ ```typescript
209
+ import { SpectyraClient } from '@spectyra/sdk';
210
+
211
+ const client = new SpectyraClient({
212
+ apiUrl: 'https://spectyra.up.railway.app/v1',
213
+ spectyraKey: process.env.SPECTYRA_API_KEY,
214
+ provider: 'openai',
215
+ providerKey: process.env.OPENAI_API_KEY, // BYOK
216
+ });
217
+
218
+ const response = await client.chat({
219
+ model: 'gpt-4o-mini',
220
+ messages: [{ role: 'user', content: 'Hello' }],
221
+ path: 'talk',
222
+ optimization_level: 3,
223
+ });
224
+ ```
225
+
226
+ **Note:** `SpectyraClient` is deprecated. For agentic use cases, use `createSpectyra()`.
227
+
228
+ ## Examples
229
+
230
+ See `examples/` directory:
231
+ - `claude-agent-local.ts` - Local mode with Claude Agent SDK
232
+ - `claude-agent-remote.ts` - API mode with telemetry
233
+ - `chat-remote.ts` - Chat optimization (legacy)
234
+
235
+ ## BYOK (Bring Your Own Key)
236
+
237
+ - Provider API keys are **never stored** server-side
238
+ - Keys are only used for the duration of the request
239
+ - You maintain full control over provider billing
240
+ - Agent options/events endpoints don't require provider keys
241
+
242
+ ## License
243
+
244
+ MIT
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Claude Agent SDK Adapter
3
+ *
4
+ * Converts Spectyra decisions to Claude Agent SDK-compatible options
5
+ */
6
+ import type { AgentDecision, ClaudeAgentOptions } from "../types.js";
7
+ /**
8
+ * Convert agent decision to Claude Agent SDK options
9
+ */
10
+ export declare function toClaudeAgentOptions(decision: AgentDecision): ClaudeAgentOptions;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Claude Agent SDK Adapter
3
+ *
4
+ * Converts Spectyra decisions to Claude Agent SDK-compatible options
5
+ */
6
+ /**
7
+ * Convert agent decision to Claude Agent SDK options
8
+ */
9
+ export function toClaudeAgentOptions(decision) {
10
+ const options = {
11
+ ...decision.options,
12
+ };
13
+ // Add default canUseTool gate if not provided
14
+ if (!options.canUseTool) {
15
+ options.canUseTool = (toolName, toolInput) => {
16
+ // Deny potentially dangerous Bash commands
17
+ if (toolName === "Bash") {
18
+ const command = typeof toolInput === "string" ? toolInput : toolInput?.command || "";
19
+ const dangerous = ["curl", "wget", "ssh", "scp", "nc", "telnet"];
20
+ const hasDangerous = dangerous.some(cmd => command.toLowerCase().includes(cmd));
21
+ if (hasDangerous) {
22
+ return false;
23
+ }
24
+ }
25
+ return true;
26
+ };
27
+ }
28
+ return options;
29
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Create Spectyra SDK Instance
3
+ *
4
+ * Main entry point for SDK-first agentic integration
5
+ */
6
+ import type { SpectyraConfig, SpectyraCtx, PromptMeta, ClaudeAgentOptions, AgentOptionsResponse, ChatOptions, ChatResponse } from "./types.js";
7
+ export interface SpectyraInstance {
8
+ /**
9
+ * Get agent options locally (SDK mode - default)
10
+ * Synchronous, works offline, no API calls
11
+ */
12
+ agentOptions(ctx: SpectyraCtx, prompt: string | PromptMeta): ClaudeAgentOptions;
13
+ /**
14
+ * Get agent options from remote API (API mode)
15
+ * Asynchronous, requires endpoint and apiKey
16
+ */
17
+ agentOptionsRemote(ctx: SpectyraCtx, promptMeta: PromptMeta): Promise<AgentOptionsResponse>;
18
+ /**
19
+ * Send agent event to remote API
20
+ */
21
+ sendAgentEvent(ctx: SpectyraCtx, event: any): Promise<void>;
22
+ /**
23
+ * Observe agent stream and forward events
24
+ */
25
+ observeAgentStream(ctx: SpectyraCtx, stream: AsyncIterable<any>): Promise<void>;
26
+ /**
27
+ * Chat optimization (remote API mode)
28
+ * Optional wrapper for /v1/chat endpoint
29
+ */
30
+ chatRemote?(options: ChatOptions): Promise<ChatResponse>;
31
+ }
32
+ /**
33
+ * Create a Spectyra SDK instance
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // Local mode (default, no API required)
38
+ * const spectyra = createSpectyra({ mode: "local" });
39
+ * const options = spectyra.agentOptions(ctx, prompt);
40
+ *
41
+ * // API mode (enterprise control plane)
42
+ * const spectyra = createSpectyra({
43
+ * mode: "api",
44
+ * endpoint: "https://spectyra.up.railway.app/v1",
45
+ * apiKey: process.env.SPECTYRA_API_KEY,
46
+ * });
47
+ * const response = await spectyra.agentOptionsRemote(ctx, promptMeta);
48
+ * ```
49
+ */
50
+ export declare function createSpectyra(config?: SpectyraConfig): SpectyraInstance;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Create Spectyra SDK Instance
3
+ *
4
+ * Main entry point for SDK-first agentic integration
5
+ */
6
+ import { decideAgent } from "./local/decideAgent.js";
7
+ import { toClaudeAgentOptions } from "./adapters/claudeAgent.js";
8
+ import { fetchAgentOptions, sendAgentEvent } from "./remote/agentRemote.js";
9
+ /**
10
+ * Create a Spectyra SDK instance
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // Local mode (default, no API required)
15
+ * const spectyra = createSpectyra({ mode: "local" });
16
+ * const options = spectyra.agentOptions(ctx, prompt);
17
+ *
18
+ * // API mode (enterprise control plane)
19
+ * const spectyra = createSpectyra({
20
+ * mode: "api",
21
+ * endpoint: "https://spectyra.up.railway.app/v1",
22
+ * apiKey: process.env.SPECTYRA_API_KEY,
23
+ * });
24
+ * const response = await spectyra.agentOptionsRemote(ctx, promptMeta);
25
+ * ```
26
+ */
27
+ export function createSpectyra(config = {}) {
28
+ const mode = config.mode || "local";
29
+ const endpoint = config.endpoint;
30
+ const apiKey = config.apiKey;
31
+ // Validate API mode requirements
32
+ if (mode === "api") {
33
+ if (!endpoint) {
34
+ throw new Error("endpoint is required for API mode");
35
+ }
36
+ if (!apiKey) {
37
+ throw new Error("apiKey is required for API mode");
38
+ }
39
+ }
40
+ return {
41
+ /**
42
+ * Local agent options (synchronous, offline)
43
+ */
44
+ agentOptions(ctx, prompt) {
45
+ const decision = decideAgent({ config, ctx, prompt });
46
+ return toClaudeAgentOptions(decision);
47
+ },
48
+ /**
49
+ * Remote agent options (asynchronous, requires API)
50
+ */
51
+ async agentOptionsRemote(ctx, promptMeta) {
52
+ if (mode !== "api" || !endpoint || !apiKey) {
53
+ throw new Error("agentOptionsRemote requires API mode with endpoint and apiKey");
54
+ }
55
+ const response = await fetchAgentOptions(endpoint, apiKey, ctx, promptMeta);
56
+ // Update ctx with run_id if returned
57
+ if (response.run_id && !ctx.runId) {
58
+ ctx.runId = response.run_id;
59
+ }
60
+ return response;
61
+ },
62
+ /**
63
+ * Send agent event
64
+ */
65
+ async sendAgentEvent(ctx, event) {
66
+ if (mode !== "api" || !endpoint || !apiKey) {
67
+ // In local mode, events are no-ops (best effort)
68
+ return;
69
+ }
70
+ try {
71
+ await sendAgentEvent(endpoint, apiKey, ctx, event);
72
+ }
73
+ catch (error) {
74
+ // Best-effort: don't throw, just log if possible
75
+ console.warn("Failed to send agent event:", error);
76
+ }
77
+ },
78
+ /**
79
+ * Observe agent stream and forward events
80
+ */
81
+ async observeAgentStream(ctx, stream) {
82
+ try {
83
+ for await (const event of stream) {
84
+ await this.sendAgentEvent(ctx, event);
85
+ }
86
+ }
87
+ catch (error) {
88
+ // Best-effort: don't throw
89
+ console.warn("Error observing agent stream:", error);
90
+ }
91
+ },
92
+ /**
93
+ * Chat remote (optional, for backwards compatibility)
94
+ */
95
+ chatRemote: mode === "api" && endpoint && apiKey
96
+ ? async (options) => {
97
+ // This requires provider and providerKey which aren't in config
98
+ // So this is only available if user provides them separately
99
+ throw new Error("chatRemote requires provider and providerKey. Use legacy SpectyraClient for chat optimization.");
100
+ }
101
+ : undefined,
102
+ };
103
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Spectyra SDK
3
+ *
4
+ * SDK-first agent runtime control: routing, budgets, tool gating, telemetry
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * // Local mode (default, no API required)
9
+ * import { createSpectyra } from '@spectyra/sdk';
10
+ *
11
+ * const spectyra = createSpectyra({ mode: "local" });
12
+ *
13
+ * // Use with Claude Agent SDK
14
+ * const options = spectyra.agentOptions(ctx, prompt);
15
+ * const result = await agent.query({ prompt, options });
16
+ * ```
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // API mode (enterprise control plane)
21
+ * const spectyra = createSpectyra({
22
+ * mode: "api",
23
+ * endpoint: "https://spectyra.up.railway.app/v1",
24
+ * apiKey: process.env.SPECTYRA_API_KEY,
25
+ * });
26
+ *
27
+ * const response = await spectyra.agentOptionsRemote(ctx, promptMeta);
28
+ * ```
29
+ */
30
+ export { createSpectyra } from "./createSpectyra.js";
31
+ export type { SpectyraConfig, SpectyraMode, SpectyraCtx, PromptMeta, ClaudeAgentOptions, AgentDecision, AgentOptionsRequest, AgentOptionsResponse, AgentEventRequest, AgentEventResponse, } from "./types.js";
32
+ export { SpectyraClient } from "./legacy/SpectyraClient.js";
33
+ export type { SpectyraClientConfig, ChatOptions, ChatResponse, ChatMessage, Usage, Path, Mode, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Spectyra SDK
3
+ *
4
+ * SDK-first agent runtime control: routing, budgets, tool gating, telemetry
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * // Local mode (default, no API required)
9
+ * import { createSpectyra } from '@spectyra/sdk';
10
+ *
11
+ * const spectyra = createSpectyra({ mode: "local" });
12
+ *
13
+ * // Use with Claude Agent SDK
14
+ * const options = spectyra.agentOptions(ctx, prompt);
15
+ * const result = await agent.query({ prompt, options });
16
+ * ```
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // API mode (enterprise control plane)
21
+ * const spectyra = createSpectyra({
22
+ * mode: "api",
23
+ * endpoint: "https://spectyra.up.railway.app/v1",
24
+ * apiKey: process.env.SPECTYRA_API_KEY,
25
+ * });
26
+ *
27
+ * const response = await spectyra.agentOptionsRemote(ctx, promptMeta);
28
+ * ```
29
+ */
30
+ // New SDK-first API
31
+ export { createSpectyra } from "./createSpectyra.js";
32
+ // Legacy API (deprecated but still supported)
33
+ export { SpectyraClient } from "./legacy/SpectyraClient.js";
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Legacy Spectyra Client
3
+ *
4
+ * @deprecated Use createSpectyra({mode:"api"}).chatRemote(...) or createSpectyra({mode:"local"})
5
+ *
6
+ * This class is maintained for backwards compatibility.
7
+ * It now uses the new remote chat client internally.
8
+ */
9
+ import type { SpectyraClientConfig, ChatOptions, ChatResponse } from "../types.js";
10
+ /**
11
+ * @deprecated Use createSpectyra({mode:"api"}) instead
12
+ *
13
+ * Legacy client for chat optimization via API.
14
+ * For agentic use cases, use createSpectyra() with agentOptions().
15
+ */
16
+ export declare class SpectyraClient {
17
+ private config;
18
+ constructor(config: SpectyraClientConfig);
19
+ /**
20
+ * Send a chat request through Spectyra optimization.
21
+ *
22
+ * @deprecated Use createSpectyra({mode:"api"}).chatRemote() instead
23
+ *
24
+ * @param options Chat options
25
+ * @returns Optimized response with savings metrics
26
+ */
27
+ chat(options: ChatOptions): Promise<ChatResponse>;
28
+ /**
29
+ * Estimate savings for a conversation without making real LLM calls.
30
+ *
31
+ * @deprecated Use createSpectyra({mode:"api"}).chatRemote() with dry_run option
32
+ *
33
+ * @param options Chat options (dry_run will be set to true)
34
+ * @returns Estimated savings
35
+ */
36
+ estimateSavings(options: Omit<ChatOptions, "dry_run">): Promise<ChatResponse>;
37
+ /**
38
+ * Get the current configuration (without sensitive keys)
39
+ */
40
+ getConfig(): Omit<SpectyraClientConfig, "spectyraKey" | "providerKey">;
41
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Legacy Spectyra Client
3
+ *
4
+ * @deprecated Use createSpectyra({mode:"api"}).chatRemote(...) or createSpectyra({mode:"local"})
5
+ *
6
+ * This class is maintained for backwards compatibility.
7
+ * It now uses the new remote chat client internally.
8
+ */
9
+ import { chatRemote } from "../remote/chatRemote.js";
10
+ /**
11
+ * @deprecated Use createSpectyra({mode:"api"}) instead
12
+ *
13
+ * Legacy client for chat optimization via API.
14
+ * For agentic use cases, use createSpectyra() with agentOptions().
15
+ */
16
+ export class SpectyraClient {
17
+ config;
18
+ constructor(config) {
19
+ this.config = config;
20
+ if (!config.apiUrl) {
21
+ throw new Error("apiUrl is required");
22
+ }
23
+ if (!config.spectyraKey) {
24
+ throw new Error("spectyraKey is required");
25
+ }
26
+ if (!config.provider) {
27
+ throw new Error("provider is required");
28
+ }
29
+ if (!config.providerKey) {
30
+ throw new Error("providerKey is required (BYOK)");
31
+ }
32
+ }
33
+ /**
34
+ * Send a chat request through Spectyra optimization.
35
+ *
36
+ * @deprecated Use createSpectyra({mode:"api"}).chatRemote() instead
37
+ *
38
+ * @param options Chat options
39
+ * @returns Optimized response with savings metrics
40
+ */
41
+ async chat(options) {
42
+ const { model, messages, path, optimization_level = 2, conversation_id, dry_run = false, } = options;
43
+ // Validate messages
44
+ if (!messages || messages.length === 0) {
45
+ throw new Error("messages array cannot be empty");
46
+ }
47
+ // Use new remote chat client
48
+ return chatRemote({
49
+ endpoint: this.config.apiUrl,
50
+ apiKey: this.config.spectyraKey,
51
+ provider: this.config.provider,
52
+ providerKey: this.config.providerKey,
53
+ }, {
54
+ model,
55
+ messages,
56
+ path,
57
+ optimization_level,
58
+ conversation_id,
59
+ dry_run,
60
+ });
61
+ }
62
+ /**
63
+ * Estimate savings for a conversation without making real LLM calls.
64
+ *
65
+ * @deprecated Use createSpectyra({mode:"api"}).chatRemote() with dry_run option
66
+ *
67
+ * @param options Chat options (dry_run will be set to true)
68
+ * @returns Estimated savings
69
+ */
70
+ async estimateSavings(options) {
71
+ return this.chat({
72
+ ...options,
73
+ dry_run: true,
74
+ });
75
+ }
76
+ /**
77
+ * Get the current configuration (without sensitive keys)
78
+ */
79
+ getConfig() {
80
+ return {
81
+ apiUrl: this.config.apiUrl,
82
+ provider: this.config.provider,
83
+ };
84
+ }
85
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Local Agent Decision Engine
3
+ *
4
+ * Makes local decisions about agent options without requiring API calls.
5
+ * This is the default "SDK mode" - works offline.
6
+ */
7
+ import type { SpectyraConfig, SpectyraCtx, PromptMeta, AgentDecision } from "../types.js";
8
+ export interface DecideAgentInput {
9
+ config: SpectyraConfig;
10
+ ctx: SpectyraCtx;
11
+ prompt: string | PromptMeta;
12
+ }
13
+ /**
14
+ * Determine agent options locally based on prompt characteristics
15
+ */
16
+ export declare function decideAgent(input: DecideAgentInput): AgentDecision;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Local Agent Decision Engine
3
+ *
4
+ * Makes local decisions about agent options without requiring API calls.
5
+ * This is the default "SDK mode" - works offline.
6
+ */
7
+ /**
8
+ * Determine agent options locally based on prompt characteristics
9
+ */
10
+ export function decideAgent(input) {
11
+ const { config, ctx, prompt } = input;
12
+ const reasons = [];
13
+ // Determine prompt length
14
+ const promptLength = typeof prompt === "string" ? prompt.length : prompt.promptChars;
15
+ const path = typeof prompt === "string" ? undefined : prompt.path;
16
+ // Determine tier by prompt length
17
+ let tier;
18
+ if (promptLength < 6000) {
19
+ tier = "small";
20
+ reasons.push(`Prompt length ${promptLength} chars → small tier`);
21
+ }
22
+ else if (promptLength < 20000) {
23
+ tier = "medium";
24
+ reasons.push(`Prompt length ${promptLength} chars → medium tier`);
25
+ }
26
+ else {
27
+ tier = "large";
28
+ reasons.push(`Prompt length ${promptLength} chars → large tier`);
29
+ }
30
+ // Choose model from config or defaults
31
+ const modelDefaults = config.defaults?.models || {};
32
+ let model;
33
+ if (tier === "small") {
34
+ model = modelDefaults.small || "claude-3-5-haiku-latest";
35
+ reasons.push(`Small tier → ${model}`);
36
+ }
37
+ else if (tier === "medium") {
38
+ model = modelDefaults.medium || "claude-3-5-sonnet-latest";
39
+ reasons.push(`Medium tier → ${model}`);
40
+ }
41
+ else {
42
+ model = modelDefaults.large || "claude-3-7-sonnet-latest";
43
+ reasons.push(`Large tier → ${model}`);
44
+ }
45
+ // Set budget
46
+ const maxBudgetUsd = ctx.budgetUsd || config.defaults?.budgetUsd || 2.5;
47
+ reasons.push(`Budget: $${maxBudgetUsd} (from ctx or defaults)`);
48
+ // Default allowed tools (configurable)
49
+ const allowedTools = ["Read", "Edit", "Bash", "Glob"];
50
+ reasons.push(`Allowed tools: ${allowedTools.join(", ")}`);
51
+ // Permission mode
52
+ const permissionMode = "acceptEdits";
53
+ reasons.push(`Permission mode: ${permissionMode}`);
54
+ // Build options
55
+ const options = {
56
+ model,
57
+ maxBudgetUsd,
58
+ allowedTools,
59
+ permissionMode,
60
+ // canUseTool will be set by adapter if needed
61
+ };
62
+ // Add path-specific context if available
63
+ if (path) {
64
+ reasons.push(`Path: ${path}`);
65
+ }
66
+ return {
67
+ options,
68
+ reasons,
69
+ };
70
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Remote Agent Client
3
+ *
4
+ * Handles agent-related API calls (options, events)
5
+ */
6
+ import type { SpectyraCtx, PromptMeta, AgentOptionsResponse, AgentEventResponse } from "../types.js";
7
+ /**
8
+ * Fetch agent options from remote API
9
+ */
10
+ export declare function fetchAgentOptions(endpoint: string, apiKey: string, ctx: SpectyraCtx, promptMeta: PromptMeta): Promise<AgentOptionsResponse>;
11
+ /**
12
+ * Send agent event to remote API
13
+ */
14
+ export declare function sendAgentEvent(endpoint: string, apiKey: string, ctx: SpectyraCtx, event: any): Promise<AgentEventResponse>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Remote Agent Client
3
+ *
4
+ * Handles agent-related API calls (options, events)
5
+ */
6
+ import { postJson } from "./http.js";
7
+ /**
8
+ * Fetch agent options from remote API
9
+ */
10
+ export async function fetchAgentOptions(endpoint, apiKey, ctx, promptMeta) {
11
+ const url = `${endpoint}/agent/options`;
12
+ const request = {
13
+ run_id: ctx.runId,
14
+ prompt_meta: promptMeta,
15
+ preferences: {
16
+ budgetUsd: ctx.budgetUsd,
17
+ // allowTools can be added if needed
18
+ },
19
+ };
20
+ return postJson(url, apiKey, request);
21
+ }
22
+ /**
23
+ * Send agent event to remote API
24
+ */
25
+ export async function sendAgentEvent(endpoint, apiKey, ctx, event) {
26
+ if (!ctx.runId) {
27
+ throw new Error("runId is required to send agent events");
28
+ }
29
+ const url = `${endpoint}/agent/events`;
30
+ // Truncate large events (max 256KB JSON)
31
+ let eventToSend = event;
32
+ const eventJson = JSON.stringify(event);
33
+ if (eventJson.length > 256 * 1024) {
34
+ // Store only type + summary for large events
35
+ eventToSend = {
36
+ type: event.type || "unknown",
37
+ summary: "Event truncated (exceeds 256KB)",
38
+ originalSize: eventJson.length,
39
+ timestamp: new Date().toISOString(),
40
+ };
41
+ }
42
+ const request = {
43
+ run_id: ctx.runId,
44
+ event: eventToSend,
45
+ };
46
+ return postJson(url, apiKey, request);
47
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Remote Chat Client
3
+ *
4
+ * Handles chat optimization API calls (backwards compatibility)
5
+ */
6
+ import type { ChatOptions, ChatResponse } from "../types.js";
7
+ export interface ChatRemoteConfig {
8
+ endpoint: string;
9
+ apiKey: string;
10
+ provider: string;
11
+ providerKey: string;
12
+ }
13
+ /**
14
+ * Send chat request to Spectyra API for optimization
15
+ */
16
+ export declare function chatRemote(config: ChatRemoteConfig, options: ChatOptions): Promise<ChatResponse>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Remote Chat Client
3
+ *
4
+ * Handles chat optimization API calls (backwards compatibility)
5
+ */
6
+ /**
7
+ * Send chat request to Spectyra API for optimization
8
+ */
9
+ export async function chatRemote(config, options) {
10
+ const url = `${config.endpoint}/chat`;
11
+ const body = {
12
+ path: options.path,
13
+ provider: config.provider,
14
+ model: options.model,
15
+ messages: options.messages,
16
+ mode: "optimized",
17
+ optimization_level: options.optimization_level ?? 2,
18
+ conversation_id: options.conversation_id,
19
+ dry_run: options.dry_run ?? false,
20
+ };
21
+ // Make request with both Spectyra key and provider key (BYOK)
22
+ const response = await fetch(url, {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ "X-SPECTYRA-API-KEY": config.apiKey,
27
+ "X-PROVIDER-KEY": config.providerKey, // BYOK - never stored server-side
28
+ },
29
+ body: JSON.stringify(body),
30
+ });
31
+ if (!response.ok) {
32
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
33
+ throw new Error(`Spectyra API error: ${error.error || response.statusText}`);
34
+ }
35
+ return response.json();
36
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Remote HTTP Client
3
+ *
4
+ * Handles HTTP requests to Spectyra API with proper error handling
5
+ */
6
+ export interface ApiError {
7
+ error: string;
8
+ details?: string;
9
+ }
10
+ /**
11
+ * Make a POST request to Spectyra API
12
+ */
13
+ export declare function postJson<T>(url: string, apiKey: string, body: any): Promise<T>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Remote HTTP Client
3
+ *
4
+ * Handles HTTP requests to Spectyra API with proper error handling
5
+ */
6
+ /**
7
+ * Make a POST request to Spectyra API
8
+ */
9
+ export async function postJson(url, apiKey, body) {
10
+ const response = await fetch(url, {
11
+ method: "POST",
12
+ headers: {
13
+ "Content-Type": "application/json",
14
+ "X-SPECTYRA-API-KEY": apiKey,
15
+ },
16
+ body: JSON.stringify(body),
17
+ });
18
+ if (!response.ok) {
19
+ let errorMessage = `API error: ${response.statusText}`;
20
+ try {
21
+ const errorData = await response.json();
22
+ errorMessage = errorData.error || errorMessage;
23
+ if (errorData.details) {
24
+ errorMessage += ` - ${errorData.details}`;
25
+ }
26
+ }
27
+ catch {
28
+ // If JSON parsing fails, use status text
29
+ const text = await response.text();
30
+ if (text) {
31
+ errorMessage = text.substring(0, 200);
32
+ }
33
+ }
34
+ throw new Error(errorMessage);
35
+ }
36
+ return response.json();
37
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Spectyra SDK Types
3
+ *
4
+ * Core types for SDK-first agentic integration
5
+ */
6
+ export type SpectyraMode = "local" | "api";
7
+ export interface SpectyraConfig {
8
+ /**
9
+ * SDK mode: "local" (default, no proxy) or "api" (remote control plane)
10
+ */
11
+ mode?: SpectyraMode;
12
+ /**
13
+ * Spectyra API endpoint (required for "api" mode, optional for "local")
14
+ * Example: "https://spectyra.up.railway.app/v1"
15
+ */
16
+ endpoint?: string;
17
+ /**
18
+ * Spectyra API key (required for "api" mode, optional for "local")
19
+ */
20
+ apiKey?: string;
21
+ /**
22
+ * Default settings
23
+ */
24
+ defaults?: {
25
+ /**
26
+ * Default budget in USD per agent run
27
+ */
28
+ budgetUsd?: number;
29
+ /**
30
+ * Model preferences by tier
31
+ */
32
+ models?: {
33
+ small?: string;
34
+ medium?: string;
35
+ large?: string;
36
+ };
37
+ };
38
+ }
39
+ export interface SpectyraCtx {
40
+ /**
41
+ * Organization ID (optional; in API mode server derives from API key)
42
+ */
43
+ orgId?: string;
44
+ /**
45
+ * Project ID (optional)
46
+ */
47
+ projectId?: string;
48
+ /**
49
+ * Run ID for tracking this agent session
50
+ */
51
+ runId?: string;
52
+ /**
53
+ * Budget in USD for this run
54
+ */
55
+ budgetUsd?: number;
56
+ /**
57
+ * Tags for filtering/analytics
58
+ */
59
+ tags?: Record<string, string>;
60
+ }
61
+ export interface PromptMeta {
62
+ /**
63
+ * Prompt character count (to avoid sending full prompt by default)
64
+ */
65
+ promptChars: number;
66
+ /**
67
+ * Path: "code" for coding, "talk" for chat/Q&A
68
+ */
69
+ path?: "code" | "talk";
70
+ /**
71
+ * Repository identifier (optional)
72
+ */
73
+ repoId?: string;
74
+ /**
75
+ * Programming language (optional)
76
+ */
77
+ language?: string;
78
+ /**
79
+ * Number of files changed (optional)
80
+ */
81
+ filesChanged?: number;
82
+ /**
83
+ * Test command (optional)
84
+ */
85
+ testCommand?: string;
86
+ }
87
+ export interface ClaudeAgentOptions {
88
+ /**
89
+ * Model name (e.g., "claude-3-5-sonnet-latest")
90
+ */
91
+ model?: string;
92
+ /**
93
+ * Maximum budget in USD for this run
94
+ */
95
+ maxBudgetUsd?: number;
96
+ /**
97
+ * Working directory
98
+ */
99
+ cwd?: string;
100
+ /**
101
+ * Allowed tool names
102
+ */
103
+ allowedTools?: string[];
104
+ /**
105
+ * Permission mode
106
+ */
107
+ permissionMode?: "default" | "acceptEdits" | "bypassPermissions";
108
+ /**
109
+ * Tool usage gate function
110
+ */
111
+ canUseTool?: (toolName: string, toolInput: any) => boolean | Promise<boolean>;
112
+ }
113
+ export interface AgentDecision {
114
+ /**
115
+ * Generated agent options
116
+ */
117
+ options: ClaudeAgentOptions;
118
+ /**
119
+ * Decision reasons (for debugging)
120
+ */
121
+ reasons: string[];
122
+ }
123
+ export interface AgentOptionsRequest {
124
+ run_id?: string;
125
+ prompt_meta: PromptMeta;
126
+ preferences?: {
127
+ budgetUsd?: number;
128
+ allowTools?: string[];
129
+ };
130
+ }
131
+ export interface AgentOptionsResponse {
132
+ run_id: string;
133
+ options: ClaudeAgentOptions;
134
+ reasons: string[];
135
+ }
136
+ export interface AgentEventRequest {
137
+ run_id: string;
138
+ event: any;
139
+ }
140
+ export interface AgentEventResponse {
141
+ ok: boolean;
142
+ }
143
+ export type Path = "talk" | "code";
144
+ export type Mode = "baseline" | "optimized";
145
+ export interface ChatMessage {
146
+ role: "system" | "user" | "assistant";
147
+ content: string;
148
+ }
149
+ export interface Usage {
150
+ input_tokens: number;
151
+ output_tokens: number;
152
+ total_tokens: number;
153
+ estimated?: boolean;
154
+ }
155
+ export interface ChatResponse {
156
+ id: string;
157
+ created_at: string;
158
+ mode: Mode;
159
+ path: Path;
160
+ optimization_level: number;
161
+ provider: string;
162
+ model: string;
163
+ response_text: string;
164
+ usage: Usage;
165
+ cost_usd: number;
166
+ savings?: {
167
+ savings_type: "verified" | "estimated" | "shadow_verified";
168
+ tokens_saved: number;
169
+ pct_saved: number;
170
+ cost_saved_usd: number;
171
+ confidence_band?: "high" | "medium" | "low";
172
+ };
173
+ quality?: {
174
+ pass: boolean;
175
+ failures: string[];
176
+ };
177
+ }
178
+ export interface SpectyraClientConfig {
179
+ /**
180
+ * Spectyra API base URL (e.g., "https://spectyra.up.railway.app/v1")
181
+ */
182
+ apiUrl: string;
183
+ /**
184
+ * Your Spectyra API key
185
+ */
186
+ spectyraKey: string;
187
+ /**
188
+ * LLM provider (e.g., "openai", "anthropic", "gemini", "grok")
189
+ */
190
+ provider: string;
191
+ /**
192
+ * Your provider API key (BYOK - Bring Your Own Key)
193
+ * This is sent to Spectyra but never stored server-side.
194
+ */
195
+ providerKey: string;
196
+ }
197
+ export interface ChatOptions {
198
+ /**
199
+ * Model name (e.g., "gpt-4o-mini", "claude-3-5-sonnet")
200
+ */
201
+ model: string;
202
+ /**
203
+ * Conversation messages
204
+ */
205
+ messages: ChatMessage[];
206
+ /**
207
+ * Path: "talk" for chat/Q&A, "code" for coding workflows
208
+ */
209
+ path: Path;
210
+ /**
211
+ * Optimization level (0-4)
212
+ * 0 = Minimal, 1 = Conservative, 2 = Balanced, 3 = Aggressive, 4 = Maximum
213
+ */
214
+ optimization_level?: number;
215
+ /**
216
+ * Conversation ID for state tracking (optional)
217
+ */
218
+ conversation_id?: string;
219
+ /**
220
+ * Dry-run mode: estimate savings without making real LLM calls
221
+ */
222
+ dry_run?: boolean;
223
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Spectyra SDK Types
3
+ *
4
+ * Core types for SDK-first agentic integration
5
+ */
6
+ export {};
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@spectyra/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Spectyra SDK for real-time LLM optimization",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "llm",
19
+ "optimization",
20
+ "token-reduction",
21
+ "spectyra",
22
+ "claude",
23
+ "agent",
24
+ "sdk"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/spectyra/spectyra.git",
31
+ "directory": "packages/sdk"
32
+ },
33
+ "dependencies": {},
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "typescript": "~5.4.0"
37
+ }
38
+ }