@blockrun/franklin 3.0.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.
Files changed (138) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +256 -0
  3. package/dist/agent/commands.d.ts +27 -0
  4. package/dist/agent/commands.js +659 -0
  5. package/dist/agent/compact.d.ts +31 -0
  6. package/dist/agent/compact.js +366 -0
  7. package/dist/agent/context.d.ts +11 -0
  8. package/dist/agent/context.js +184 -0
  9. package/dist/agent/error-classifier.d.ts +10 -0
  10. package/dist/agent/error-classifier.js +61 -0
  11. package/dist/agent/llm.d.ts +63 -0
  12. package/dist/agent/llm.js +448 -0
  13. package/dist/agent/loop.d.ts +12 -0
  14. package/dist/agent/loop.js +346 -0
  15. package/dist/agent/optimize.d.ts +53 -0
  16. package/dist/agent/optimize.js +262 -0
  17. package/dist/agent/permissions.d.ts +39 -0
  18. package/dist/agent/permissions.js +226 -0
  19. package/dist/agent/reduce.d.ts +49 -0
  20. package/dist/agent/reduce.js +317 -0
  21. package/dist/agent/streaming-executor.d.ts +36 -0
  22. package/dist/agent/streaming-executor.js +149 -0
  23. package/dist/agent/tokens.d.ts +53 -0
  24. package/dist/agent/tokens.js +185 -0
  25. package/dist/agent/types.d.ts +125 -0
  26. package/dist/agent/types.js +5 -0
  27. package/dist/banner.d.ts +1 -0
  28. package/dist/banner.js +27 -0
  29. package/dist/commands/balance.d.ts +1 -0
  30. package/dist/commands/balance.js +40 -0
  31. package/dist/commands/config.d.ts +14 -0
  32. package/dist/commands/config.js +107 -0
  33. package/dist/commands/daemon.d.ts +3 -0
  34. package/dist/commands/daemon.js +117 -0
  35. package/dist/commands/history.d.ts +5 -0
  36. package/dist/commands/history.js +31 -0
  37. package/dist/commands/init.d.ts +3 -0
  38. package/dist/commands/init.js +92 -0
  39. package/dist/commands/logs.d.ts +5 -0
  40. package/dist/commands/logs.js +89 -0
  41. package/dist/commands/models.d.ts +1 -0
  42. package/dist/commands/models.js +56 -0
  43. package/dist/commands/plugin.d.ts +14 -0
  44. package/dist/commands/plugin.js +176 -0
  45. package/dist/commands/proxy.d.ts +13 -0
  46. package/dist/commands/proxy.js +106 -0
  47. package/dist/commands/setup.d.ts +1 -0
  48. package/dist/commands/setup.js +49 -0
  49. package/dist/commands/start.d.ts +8 -0
  50. package/dist/commands/start.js +292 -0
  51. package/dist/commands/stats.d.ts +10 -0
  52. package/dist/commands/stats.js +94 -0
  53. package/dist/commands/uninit.d.ts +1 -0
  54. package/dist/commands/uninit.js +63 -0
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +41 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +179 -0
  59. package/dist/mcp/client.d.ts +44 -0
  60. package/dist/mcp/client.js +147 -0
  61. package/dist/mcp/config.d.ts +20 -0
  62. package/dist/mcp/config.js +138 -0
  63. package/dist/plugin-sdk/channel.d.ts +100 -0
  64. package/dist/plugin-sdk/channel.js +10 -0
  65. package/dist/plugin-sdk/index.d.ts +14 -0
  66. package/dist/plugin-sdk/index.js +9 -0
  67. package/dist/plugin-sdk/plugin.d.ts +87 -0
  68. package/dist/plugin-sdk/plugin.js +7 -0
  69. package/dist/plugin-sdk/search.d.ts +13 -0
  70. package/dist/plugin-sdk/search.js +4 -0
  71. package/dist/plugin-sdk/tracker.d.ts +27 -0
  72. package/dist/plugin-sdk/tracker.js +5 -0
  73. package/dist/plugin-sdk/workflow.d.ts +126 -0
  74. package/dist/plugin-sdk/workflow.js +11 -0
  75. package/dist/plugins/registry.d.ts +33 -0
  76. package/dist/plugins/registry.js +155 -0
  77. package/dist/plugins/runner.d.ts +21 -0
  78. package/dist/plugins/runner.js +453 -0
  79. package/dist/plugins-bundled/social/index.d.ts +10 -0
  80. package/dist/plugins-bundled/social/index.js +363 -0
  81. package/dist/plugins-bundled/social/plugin.json +14 -0
  82. package/dist/plugins-bundled/social/prompts.d.ts +19 -0
  83. package/dist/plugins-bundled/social/prompts.js +67 -0
  84. package/dist/plugins-bundled/social/types.d.ts +58 -0
  85. package/dist/plugins-bundled/social/types.js +16 -0
  86. package/dist/pricing.d.ts +21 -0
  87. package/dist/pricing.js +91 -0
  88. package/dist/proxy/fallback.d.ts +38 -0
  89. package/dist/proxy/fallback.js +144 -0
  90. package/dist/proxy/server.d.ts +18 -0
  91. package/dist/proxy/server.js +576 -0
  92. package/dist/proxy/sse-translator.d.ts +29 -0
  93. package/dist/proxy/sse-translator.js +270 -0
  94. package/dist/router/index.d.ts +22 -0
  95. package/dist/router/index.js +269 -0
  96. package/dist/session/search.d.ts +33 -0
  97. package/dist/session/search.js +229 -0
  98. package/dist/session/storage.d.ts +48 -0
  99. package/dist/session/storage.js +173 -0
  100. package/dist/stats/insights.d.ts +55 -0
  101. package/dist/stats/insights.js +195 -0
  102. package/dist/stats/tracker.d.ts +54 -0
  103. package/dist/stats/tracker.js +165 -0
  104. package/dist/tools/askuser.d.ts +6 -0
  105. package/dist/tools/askuser.js +76 -0
  106. package/dist/tools/bash.d.ts +5 -0
  107. package/dist/tools/bash.js +336 -0
  108. package/dist/tools/edit.d.ts +5 -0
  109. package/dist/tools/edit.js +148 -0
  110. package/dist/tools/glob.d.ts +5 -0
  111. package/dist/tools/glob.js +158 -0
  112. package/dist/tools/grep.d.ts +5 -0
  113. package/dist/tools/grep.js +194 -0
  114. package/dist/tools/imagegen.d.ts +6 -0
  115. package/dist/tools/imagegen.js +172 -0
  116. package/dist/tools/index.d.ts +17 -0
  117. package/dist/tools/index.js +30 -0
  118. package/dist/tools/read.d.ts +11 -0
  119. package/dist/tools/read.js +90 -0
  120. package/dist/tools/subagent.d.ts +5 -0
  121. package/dist/tools/subagent.js +116 -0
  122. package/dist/tools/task.d.ts +5 -0
  123. package/dist/tools/task.js +91 -0
  124. package/dist/tools/webfetch.d.ts +5 -0
  125. package/dist/tools/webfetch.js +166 -0
  126. package/dist/tools/websearch.d.ts +5 -0
  127. package/dist/tools/websearch.js +103 -0
  128. package/dist/tools/write.d.ts +5 -0
  129. package/dist/tools/write.js +114 -0
  130. package/dist/ui/app.d.ts +26 -0
  131. package/dist/ui/app.js +545 -0
  132. package/dist/ui/model-picker.d.ts +14 -0
  133. package/dist/ui/model-picker.js +161 -0
  134. package/dist/ui/terminal.d.ts +35 -0
  135. package/dist/ui/terminal.js +337 -0
  136. package/dist/wallet/manager.d.ts +10 -0
  137. package/dist/wallet/manager.js +23 -0
  138. package/package.json +79 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Streaming Tool Executor for runcode.
3
+ * Starts executing concurrent-safe tools while the model is still streaming.
4
+ * Non-concurrent tools wait until the full response is received.
5
+ */
6
+ export class StreamingExecutor {
7
+ handlers;
8
+ scope;
9
+ permissions;
10
+ onStart;
11
+ onProgress;
12
+ pending = [];
13
+ constructor(opts) {
14
+ this.handlers = opts.handlers;
15
+ this.scope = opts.scope;
16
+ this.permissions = opts.permissions;
17
+ this.onStart = opts.onStart;
18
+ this.onProgress = opts.onProgress;
19
+ }
20
+ /**
21
+ * Called when a tool_use block is fully received from the stream.
22
+ * If the tool is concurrent-safe, start executing immediately.
23
+ * Otherwise, queue it for later.
24
+ */
25
+ onToolReceived(invocation) {
26
+ const handler = this.handlers.get(invocation.name);
27
+ const isConcurrent = handler?.concurrent ?? false;
28
+ if (isConcurrent) {
29
+ // Concurrent tools are auto-allowed — start immediately and time from here
30
+ const preview = this.inputPreview(invocation);
31
+ this.onStart(invocation.id, invocation.name, preview);
32
+ const promise = this.executeWithPermissions(invocation, 1, false);
33
+ this.pending.push({ invocation, promise });
34
+ }
35
+ // Non-concurrent tools are NOT started here — executed via collectResults
36
+ }
37
+ /**
38
+ * After the model finishes streaming, execute any non-concurrent tools
39
+ * and collect all results (including concurrent ones that may already be done).
40
+ */
41
+ async collectResults(allInvocations) {
42
+ const results = [];
43
+ const alreadyStarted = new Set(this.pending.map(p => p.invocation.id));
44
+ const pendingSnapshot = [...this.pending];
45
+ this.pending = []; // Clear immediately so errors don't leave stale state
46
+ // Pre-count pending sequential invocations per tool type.
47
+ // Shown in permission dialog: "N pending — press [a] to allow all".
48
+ const pendingCounts = new Map();
49
+ for (const inv of allInvocations) {
50
+ if (!alreadyStarted.has(inv.id)) {
51
+ pendingCounts.set(inv.name, (pendingCounts.get(inv.name) || 0) + 1);
52
+ }
53
+ }
54
+ const remainingCounts = new Map(pendingCounts);
55
+ try {
56
+ // Wait for concurrent results that were started during streaming
57
+ for (const p of pendingSnapshot) {
58
+ const result = await p.promise;
59
+ results.push([p.invocation, result]);
60
+ }
61
+ // Execute sequential (non-concurrent) tools now
62
+ for (const inv of allInvocations) {
63
+ if (alreadyStarted.has(inv.id))
64
+ continue;
65
+ const remaining = remainingCounts.get(inv.name) ?? 1;
66
+ remainingCounts.set(inv.name, remaining - 1);
67
+ // NOTE: onStart is called INSIDE executeWithPermissions, AFTER permission is granted.
68
+ // This ensures elapsed time reflects actual execution time, not permission wait time.
69
+ const result = await this.executeWithPermissions(inv, remaining, true);
70
+ results.push([inv, result]);
71
+ }
72
+ }
73
+ catch (err) {
74
+ // Return partial results rather than losing them; caller handles errors
75
+ throw err;
76
+ }
77
+ return results;
78
+ }
79
+ async executeWithPermissions(invocation, pendingCount = 1, callStart = true // false for concurrent tools (already called in onToolReceived)
80
+ ) {
81
+ // Permission check
82
+ if (this.permissions) {
83
+ const decision = await this.permissions.check(invocation.name, invocation.input);
84
+ if (decision.behavior === 'deny') {
85
+ return {
86
+ output: `Permission denied for ${invocation.name}: ${decision.reason || 'denied by policy'}. Do not retry — explain to the user what you were trying to do and ask how they'd like to proceed.`,
87
+ isError: true,
88
+ };
89
+ }
90
+ if (decision.behavior === 'ask') {
91
+ const allowed = await this.permissions.promptUser(invocation.name, invocation.input, pendingCount);
92
+ if (!allowed) {
93
+ return {
94
+ output: `User denied permission for ${invocation.name}. Do not retry — ask the user what they'd like to do instead.`,
95
+ isError: true,
96
+ };
97
+ }
98
+ }
99
+ }
100
+ // Start timing AFTER permission is granted (accurate elapsed time)
101
+ if (callStart) {
102
+ const preview = this.inputPreview(invocation);
103
+ this.onStart(invocation.id, invocation.name, preview);
104
+ }
105
+ const handler = this.handlers.get(invocation.name);
106
+ if (!handler) {
107
+ return { output: `Unknown capability: ${invocation.name}`, isError: true };
108
+ }
109
+ // Wire per-invocation progress to onProgress callback
110
+ const progressScope = this.onProgress
111
+ ? {
112
+ ...this.scope,
113
+ onProgress: (text) => this.onProgress(invocation.id, text),
114
+ }
115
+ : this.scope;
116
+ try {
117
+ return await handler.execute(invocation.input, progressScope);
118
+ }
119
+ catch (err) {
120
+ return {
121
+ output: `Error executing ${invocation.name}: ${err.message}`,
122
+ isError: true,
123
+ };
124
+ }
125
+ }
126
+ /** Extract a short preview string from a tool invocation's input. */
127
+ inputPreview(invocation) {
128
+ const input = invocation.input;
129
+ switch (invocation.name) {
130
+ case 'Bash': {
131
+ const cmd = input.command || '';
132
+ return cmd.length > 80 ? cmd.slice(0, 80) + '…' : cmd;
133
+ }
134
+ case 'Write':
135
+ case 'Read':
136
+ case 'Edit':
137
+ return input.file_path || undefined;
138
+ case 'Grep':
139
+ return input.pattern || undefined;
140
+ case 'Glob':
141
+ return input.pattern || undefined;
142
+ case 'WebFetch':
143
+ case 'WebSearch':
144
+ return (input.url ?? input.query) || undefined;
145
+ default:
146
+ return undefined;
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Token estimation for runcode.
3
+ * Uses byte-based heuristic (no external tokenizer dependency).
4
+ * Anchors to actual API counts when available, estimates on top for new messages.
5
+ */
6
+ import type { Dialogue } from './types.js';
7
+ /**
8
+ * Update with actual token counts from API response.
9
+ * This anchors our estimates to reality.
10
+ */
11
+ export declare function updateActualTokens(inputTokens: number, outputTokens: number, messageCount: number): void;
12
+ /**
13
+ * Get token count using API anchor + estimation for new messages.
14
+ * More accurate than pure estimation because it's grounded in actual API counts.
15
+ */
16
+ export declare function getAnchoredTokenCount(history: Dialogue[]): {
17
+ estimated: number;
18
+ apiAnchored: boolean;
19
+ contextUsagePct: number;
20
+ };
21
+ /**
22
+ * Reset anchor (e.g., after compaction).
23
+ */
24
+ export declare function resetTokenAnchor(): void;
25
+ /**
26
+ * Estimate token count for a string using byte-length heuristic.
27
+ * JSON-heavy content uses 2 bytes/token; general text uses 4.
28
+ */
29
+ export declare function estimateTokens(text: string, bytesPerToken?: number): number;
30
+ /**
31
+ * Estimate total tokens for a message.
32
+ */
33
+ export declare function estimateDialogueTokens(msg: Dialogue): number;
34
+ /**
35
+ * Estimate total tokens for the entire conversation history.
36
+ */
37
+ export declare function estimateHistoryTokens(history: Dialogue[]): number;
38
+ /**
39
+ * Get the context window size for a model, with a conservative default.
40
+ */
41
+ export declare function getContextWindow(model: string): number;
42
+ /**
43
+ * Reserved tokens for the compaction summary output.
44
+ */
45
+ export declare const COMPACTION_SUMMARY_RESERVE = 16000;
46
+ /**
47
+ * Buffer before hitting the context limit to trigger auto-compact.
48
+ */
49
+ export declare const COMPACTION_TRIGGER_BUFFER = 12000;
50
+ /**
51
+ * Calculate the threshold at which auto-compaction should trigger.
52
+ */
53
+ export declare function getCompactionThreshold(model: string): number;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Token estimation for runcode.
3
+ * Uses byte-based heuristic (no external tokenizer dependency).
4
+ * Anchors to actual API counts when available, estimates on top for new messages.
5
+ */
6
+ const DEFAULT_BYTES_PER_TOKEN = 4;
7
+ // ─── API-anchored token tracking ───────────────────────���──────────────────
8
+ /** Last known actual token count from API response */
9
+ let lastApiInputTokens = 0;
10
+ let lastApiOutputTokens = 0;
11
+ let lastApiMessageCount = 0;
12
+ /**
13
+ * Update with actual token counts from API response.
14
+ * This anchors our estimates to reality.
15
+ */
16
+ export function updateActualTokens(inputTokens, outputTokens, messageCount) {
17
+ lastApiInputTokens = inputTokens;
18
+ lastApiOutputTokens = outputTokens;
19
+ lastApiMessageCount = messageCount;
20
+ }
21
+ /**
22
+ * Get token count using API anchor + estimation for new messages.
23
+ * More accurate than pure estimation because it's grounded in actual API counts.
24
+ */
25
+ export function getAnchoredTokenCount(history) {
26
+ if (lastApiInputTokens > 0 && lastApiMessageCount > 0 && history.length >= lastApiMessageCount) {
27
+ // Sanity check: if history was mutated (compaction, micro-compact), anchor may be stale.
28
+ // Detect by checking if new messages were only appended (length grew), not if content changed.
29
+ // If history grew by more than expected (e.g., resume injected many messages), fall through to estimation.
30
+ const growth = history.length - lastApiMessageCount;
31
+ if (growth <= 20) { // Reasonable growth since last API call
32
+ const newMessages = history.slice(lastApiMessageCount);
33
+ let newTokens = 0;
34
+ for (const msg of newMessages) {
35
+ newTokens += estimateDialogueTokens(msg);
36
+ }
37
+ const total = lastApiInputTokens + newTokens;
38
+ return {
39
+ estimated: total,
40
+ apiAnchored: true,
41
+ contextUsagePct: 0,
42
+ };
43
+ }
44
+ // Too much growth — anchor is unreliable, fall through to estimation
45
+ resetTokenAnchor();
46
+ }
47
+ // No anchor — pure estimation
48
+ return {
49
+ estimated: estimateHistoryTokens(history),
50
+ apiAnchored: false,
51
+ contextUsagePct: 0,
52
+ };
53
+ }
54
+ /**
55
+ * Reset anchor (e.g., after compaction).
56
+ */
57
+ export function resetTokenAnchor() {
58
+ lastApiInputTokens = 0;
59
+ lastApiOutputTokens = 0;
60
+ lastApiMessageCount = 0;
61
+ }
62
+ /**
63
+ * Estimate token count for a string using byte-length heuristic.
64
+ * JSON-heavy content uses 2 bytes/token; general text uses 4.
65
+ */
66
+ export function estimateTokens(text, bytesPerToken = DEFAULT_BYTES_PER_TOKEN) {
67
+ // Pad by 4/3 (~33%) for conservative estimation — better to over-count than under-count
68
+ return Math.ceil(Buffer.byteLength(text, 'utf-8') / bytesPerToken * 1.33);
69
+ }
70
+ /**
71
+ * Estimate tokens for a content part.
72
+ */
73
+ function estimateContentPartTokens(part) {
74
+ switch (part.type) {
75
+ case 'text':
76
+ return estimateTokens(part.text);
77
+ case 'tool_use':
78
+ // +16 tokens for tool_use framing (type, id, name fields, JSON structure)
79
+ return 16 + estimateTokens(part.name) + estimateTokens(JSON.stringify(part.input), 2);
80
+ case 'tool_result': {
81
+ const content = typeof part.content === 'string'
82
+ ? part.content
83
+ : JSON.stringify(part.content);
84
+ return estimateTokens(content, 2);
85
+ }
86
+ case 'thinking':
87
+ return estimateTokens(part.thinking);
88
+ default:
89
+ return 0;
90
+ }
91
+ }
92
+ /**
93
+ * Estimate total tokens for a message.
94
+ */
95
+ export function estimateDialogueTokens(msg) {
96
+ const overhead = 4; // role, structure overhead
97
+ if (typeof msg.content === 'string') {
98
+ return overhead + estimateTokens(msg.content);
99
+ }
100
+ let total = overhead;
101
+ for (const part of msg.content) {
102
+ total += estimateContentPartTokens(part);
103
+ }
104
+ return total;
105
+ }
106
+ /**
107
+ * Estimate total tokens for the entire conversation history.
108
+ */
109
+ export function estimateHistoryTokens(history) {
110
+ let total = 0;
111
+ for (const msg of history) {
112
+ total += estimateDialogueTokens(msg);
113
+ }
114
+ return total;
115
+ }
116
+ /**
117
+ * Context window sizes for known models.
118
+ */
119
+ const MODEL_CONTEXT_WINDOWS = {
120
+ // Anthropic
121
+ 'anthropic/claude-opus-4.6': 200_000,
122
+ 'anthropic/claude-sonnet-4.6': 200_000,
123
+ 'anthropic/claude-sonnet-4': 200_000,
124
+ 'anthropic/claude-haiku-4.5': 200_000,
125
+ 'anthropic/claude-haiku-4.5-20251001': 200_000,
126
+ // OpenAI
127
+ 'openai/gpt-5.4': 128_000,
128
+ 'openai/gpt-5.4-pro': 128_000,
129
+ 'openai/gpt-5.3': 128_000,
130
+ 'openai/gpt-5.3-codex': 128_000,
131
+ 'openai/gpt-5.2': 128_000,
132
+ 'openai/gpt-5-mini': 128_000,
133
+ 'openai/gpt-5-nano': 128_000,
134
+ 'openai/gpt-4.1': 1_000_000,
135
+ 'openai/o3': 200_000,
136
+ 'openai/o4-mini': 200_000,
137
+ // Google
138
+ 'google/gemini-2.5-pro': 1_000_000,
139
+ 'google/gemini-2.5-flash': 1_000_000,
140
+ 'google/gemini-2.5-flash-lite': 1_000_000,
141
+ 'google/gemini-3.1-pro': 1_000_000,
142
+ // DeepSeek
143
+ 'deepseek/deepseek-chat': 64_000,
144
+ 'deepseek/deepseek-reasoner': 64_000,
145
+ // xAI
146
+ 'xai/grok-3': 131_072,
147
+ 'xai/grok-4-0709': 131_072,
148
+ 'xai/grok-4-1-fast-reasoning': 131_072,
149
+ // Others
150
+ 'zai/glm-5.1': 128_000,
151
+ 'moonshot/kimi-k2.5': 128_000,
152
+ 'minimax/minimax-m2.7': 128_000,
153
+ };
154
+ /**
155
+ * Get the context window size for a model, with a conservative default.
156
+ */
157
+ export function getContextWindow(model) {
158
+ if (MODEL_CONTEXT_WINDOWS[model])
159
+ return MODEL_CONTEXT_WINDOWS[model];
160
+ // Pattern-based inference for unknown models
161
+ if (model.includes('gemini'))
162
+ return 1_000_000;
163
+ if (model.includes('claude'))
164
+ return 200_000;
165
+ if (model.includes('gpt-4.1'))
166
+ return 1_000_000;
167
+ if (model.includes('nemotron') || model.includes('qwen'))
168
+ return 128_000;
169
+ return 128_000;
170
+ }
171
+ /**
172
+ * Reserved tokens for the compaction summary output.
173
+ */
174
+ export const COMPACTION_SUMMARY_RESERVE = 16_000;
175
+ /**
176
+ * Buffer before hitting the context limit to trigger auto-compact.
177
+ */
178
+ export const COMPACTION_TRIGGER_BUFFER = 12_000;
179
+ /**
180
+ * Calculate the threshold at which auto-compaction should trigger.
181
+ */
182
+ export function getCompactionThreshold(model) {
183
+ const window = getContextWindow(model);
184
+ return window - COMPACTION_SUMMARY_RESERVE - COMPACTION_TRIGGER_BUFFER;
185
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Core types for the runcode agent system.
3
+ * All type names and structures are original designs.
4
+ */
5
+ export type Role = 'user' | 'assistant';
6
+ export interface TextSegment {
7
+ type: 'text';
8
+ text: string;
9
+ }
10
+ export interface CapabilityInvocation {
11
+ type: 'tool_use';
12
+ id: string;
13
+ name: string;
14
+ input: Record<string, unknown>;
15
+ }
16
+ export interface ThinkingSegment {
17
+ type: 'thinking';
18
+ thinking: string;
19
+ signature?: string;
20
+ }
21
+ export interface CapabilityOutcome {
22
+ type: 'tool_result';
23
+ tool_use_id: string;
24
+ content: string | ContentPart[];
25
+ is_error?: boolean;
26
+ }
27
+ export type ContentPart = TextSegment | CapabilityInvocation | ThinkingSegment;
28
+ export type UserContentPart = TextSegment | CapabilityOutcome;
29
+ export interface Dialogue {
30
+ role: Role;
31
+ content: ContentPart[] | UserContentPart[] | string;
32
+ }
33
+ export interface CapabilitySchema {
34
+ type: 'object';
35
+ properties: Record<string, unknown>;
36
+ required?: string[];
37
+ additionalProperties?: boolean;
38
+ }
39
+ export interface CapabilityDefinition {
40
+ name: string;
41
+ description: string;
42
+ input_schema: CapabilitySchema;
43
+ }
44
+ export interface CapabilityHandler {
45
+ spec: CapabilityDefinition;
46
+ execute(input: Record<string, unknown>, ctx: ExecutionScope): Promise<CapabilityResult>;
47
+ concurrent?: boolean;
48
+ }
49
+ export interface CapabilityResult {
50
+ output: string;
51
+ isError?: boolean;
52
+ }
53
+ export interface ExecutionScope {
54
+ workingDir: string;
55
+ abortSignal: AbortSignal;
56
+ onProgress?: (text: string) => void;
57
+ /** Routes AskUser questions through ink UI input to avoid raw-mode stdin conflict */
58
+ onAskUser?: (question: string, options?: string[]) => Promise<string>;
59
+ }
60
+ export interface StreamTextDelta {
61
+ kind: 'text_delta';
62
+ text: string;
63
+ }
64
+ export interface StreamThinkingDelta {
65
+ kind: 'thinking_delta';
66
+ text: string;
67
+ }
68
+ export interface StreamCapabilityStart {
69
+ kind: 'capability_start';
70
+ id: string;
71
+ name: string;
72
+ preview?: string;
73
+ }
74
+ export interface StreamCapabilityInputDelta {
75
+ kind: 'capability_input_delta';
76
+ id: string;
77
+ delta: string;
78
+ }
79
+ export interface StreamCapabilityProgress {
80
+ kind: 'capability_progress';
81
+ id: string;
82
+ text: string;
83
+ }
84
+ export interface StreamCapabilityDone {
85
+ kind: 'capability_done';
86
+ id: string;
87
+ result: CapabilityResult;
88
+ }
89
+ export interface StreamTurnDone {
90
+ kind: 'turn_done';
91
+ reason: 'completed' | 'max_turns' | 'aborted' | 'error';
92
+ error?: string;
93
+ }
94
+ export interface StreamUsageInfo {
95
+ kind: 'usage';
96
+ inputTokens: number;
97
+ outputTokens: number;
98
+ model: string;
99
+ calls: number;
100
+ }
101
+ export type StreamEvent = StreamTextDelta | StreamThinkingDelta | StreamCapabilityStart | StreamCapabilityInputDelta | StreamCapabilityProgress | StreamCapabilityDone | StreamTurnDone | StreamUsageInfo;
102
+ export interface AgentConfig {
103
+ model: string;
104
+ apiUrl: string;
105
+ chain: 'base' | 'solana';
106
+ systemInstructions: string[];
107
+ capabilities: CapabilityHandler[];
108
+ maxTurns?: number;
109
+ workingDir?: string;
110
+ permissionMode?: 'default' | 'trust' | 'deny-all' | 'plan';
111
+ onEvent?: (event: StreamEvent) => void;
112
+ debug?: boolean;
113
+ /** Ultrathink mode: inject deep-reasoning instruction into every prompt */
114
+ ultrathink?: boolean;
115
+ /**
116
+ * Permission prompt function — injected by Ink UI to avoid stdin conflict.
117
+ * Replaces the readline-based askQuestion() when running in interactive mode.
118
+ * Returns 'yes' | 'no' | 'always' (always = allow for rest of session).
119
+ */
120
+ permissionPromptFn?: (toolName: string, description: string) => Promise<'yes' | 'no' | 'always'>;
121
+ /** Routes AskUser questions through ink UI input to avoid raw-mode stdin conflict */
122
+ onAskUser?: (question: string, options?: string[]) => Promise<string>;
123
+ /** Notify UI when agent switches model (e.g. payment fallback) */
124
+ onModelChange?: (model: string) => void;
125
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Core types for the runcode agent system.
3
+ * All type names and structures are original designs.
4
+ */
5
+ export {};
@@ -0,0 +1 @@
1
+ export declare function printBanner(version: string): void;
package/dist/banner.js ADDED
@@ -0,0 +1,27 @@
1
+ import chalk from 'chalk';
2
+ // "FRANKLIN" — the AI agent with a wallet
3
+ const FRANKLIN_ART = [
4
+ ' ███████╗██████╗ █████╗ ███╗ ██╗██╗ ██╗██╗ ██╗███╗ ██╗',
5
+ ' ██╔════╝██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝██║ ██║████╗ ██║',
6
+ ' █████╗ ██████╔╝███████║██╔██╗ ██║█████╔╝ ██║ ██║██╔██╗ ██║',
7
+ ' ██╔══╝ ██╔══██╗██╔══██║██║╚██╗██║██╔═██╗ ██║ ██║██║╚██╗██║',
8
+ ' ██║ ██║ ██║██║ ██║██║ ╚████║██║ ██╗███████╗██║██║ ╚████║',
9
+ ' ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝',
10
+ ];
11
+ export function printBanner(version) {
12
+ // Gold → green gradient: money colors for "the agent with a wallet"
13
+ const gold = chalk.hex('#FFD700');
14
+ const green = chalk.hex('#10B981');
15
+ for (let i = 0; i < FRANKLIN_ART.length; i++) {
16
+ // Alternate lines between gold and green for subtle depth
17
+ const color = i < 3 ? gold : green;
18
+ console.log(color(FRANKLIN_ART[i]));
19
+ }
20
+ console.log(chalk.bold.hex('#FFD700')(' Franklin') +
21
+ chalk.dim(' · The AI agent with a wallet · v' + version));
22
+ console.log(chalk.dim(' Marketing: ') +
23
+ chalk.cyan('franklin.run') +
24
+ chalk.dim(' · Trading: ') +
25
+ chalk.hex('#10B981')('franklin.bet') +
26
+ chalk.dim('\n'));
27
+ }
@@ -0,0 +1 @@
1
+ export declare function balanceCommand(): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import chalk from 'chalk';
2
+ import { setupAgentWallet, setupAgentSolanaWallet } from '@blockrun/llm';
3
+ import { loadChain } from '../config.js';
4
+ export async function balanceCommand() {
5
+ const chain = loadChain();
6
+ try {
7
+ if (chain === 'solana') {
8
+ const client = await setupAgentSolanaWallet({ silent: true });
9
+ const address = await client.getWalletAddress();
10
+ const balance = await client.getBalance();
11
+ console.log(`Chain: ${chalk.magenta('solana')}`);
12
+ console.log(`Wallet: ${chalk.cyan(address)}`);
13
+ console.log(`USDC Balance: ${chalk.green(`$${balance.toFixed(2)}`)}`);
14
+ if (balance === 0) {
15
+ console.log(chalk.dim(`\nSend USDC on Solana to ${address} to get started.`));
16
+ }
17
+ }
18
+ else {
19
+ const client = setupAgentWallet({ silent: true });
20
+ const address = client.getWalletAddress();
21
+ const balance = await client.getBalance();
22
+ console.log(`Chain: ${chalk.magenta('base')}`);
23
+ console.log(`Wallet: ${chalk.cyan(address)}`);
24
+ console.log(`USDC Balance: ${chalk.green(`$${balance.toFixed(2)}`)}`);
25
+ if (balance === 0) {
26
+ console.log(chalk.dim(`\nSend USDC on Base to ${address} to get started.`));
27
+ }
28
+ }
29
+ }
30
+ catch (err) {
31
+ const msg = err instanceof Error ? err.message : '';
32
+ if (msg.includes('ENOENT') || msg.includes('wallet') || msg.includes('key')) {
33
+ console.log(chalk.red('No wallet found. Run `runcode setup` first.'));
34
+ }
35
+ else {
36
+ console.log(chalk.red(`Error checking balance: ${msg || 'unknown error'}`));
37
+ }
38
+ process.exit(1);
39
+ }
40
+ }
@@ -0,0 +1,14 @@
1
+ export interface AppConfig {
2
+ 'default-model'?: string;
3
+ 'sonnet-model'?: string;
4
+ 'opus-model'?: string;
5
+ 'haiku-model'?: string;
6
+ 'smart-routing'?: string;
7
+ 'permission-mode'?: string;
8
+ 'max-turns'?: string;
9
+ 'auto-compact'?: string;
10
+ 'session-save'?: string;
11
+ 'debug'?: string;
12
+ }
13
+ export declare function loadConfig(): AppConfig;
14
+ export declare function configCommand(action: string, keyOrUndefined?: string, value?: string): void;