Skip to content

Commit 772cbdb

Browse files
committed
community fixes
1 parent b2e2b1a commit 772cbdb

File tree

11 files changed

+237
-24
lines changed

11 files changed

+237
-24
lines changed

Cargo.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ members = [
1818
]
1919

2020
[workspace.package]
21-
version = "0.3.26"
21+
version = "0.3.27"
2222
edition = "2021"
2323
license = "Apache-2.0 OR MIT"
2424
repository = "https://github.com/RightNow-AI/openfang"

crates/openfang-api/src/routes.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,14 @@ pub async fn get_agent_session(
395395
// collects all tool_use entries keyed by id; pass 2 attaches results.
396396

397397
// Pass 1: build messages and a lookup from tool_use_id → (msg_idx, tool_idx)
398+
use base64::Engine as _;
398399
let mut built_messages: Vec<serde_json::Value> = Vec::new();
399400
let mut tool_use_index: std::collections::HashMap<String, (usize, usize)> =
400401
std::collections::HashMap::new();
401402

402403
for m in &session.messages {
403404
let mut tools: Vec<serde_json::Value> = Vec::new();
405+
let mut msg_images: Vec<serde_json::Value> = Vec::new();
404406
let content = match &m.content {
405407
openfang_types::message::MessageContent::Text(t) => t.clone(),
406408
openfang_types::message::MessageContent::Blocks(blocks) => {
@@ -410,8 +412,36 @@ pub async fn get_agent_session(
410412
openfang_types::message::ContentBlock::Text { text } => {
411413
texts.push(text.clone());
412414
}
413-
openfang_types::message::ContentBlock::Image { .. } => {
415+
openfang_types::message::ContentBlock::Image {
416+
media_type,
417+
data,
418+
} => {
414419
texts.push("[Image]".to_string());
420+
// Persist image to upload dir so it can be
421+
// served back when loading session history.
422+
let file_id = uuid::Uuid::new_v4().to_string();
423+
let upload_dir =
424+
std::env::temp_dir().join("openfang_uploads");
425+
let _ = std::fs::create_dir_all(&upload_dir);
426+
if let Ok(bytes) =
427+
base64::engine::general_purpose::STANDARD.decode(data)
428+
{
429+
let _ = std::fs::write(
430+
upload_dir.join(&file_id),
431+
&bytes,
432+
);
433+
UPLOAD_REGISTRY.insert(
434+
file_id.clone(),
435+
UploadMeta {
436+
filename: format!("image.{}", media_type.rsplit('/').next().unwrap_or("png")),
437+
content_type: media_type.clone(),
438+
},
439+
);
440+
msg_images.push(serde_json::json!({
441+
"file_id": file_id,
442+
"filename": format!("image.{}", media_type.rsplit('/').next().unwrap_or("png")),
443+
}));
444+
}
415445
}
416446
openfang_types::message::ContentBlock::ToolUse {
417447
id,
@@ -455,6 +485,9 @@ pub async fn get_agent_session(
455485
if !tools.is_empty() {
456486
msg["tools"] = serde_json::Value::Array(tools);
457487
}
488+
if !msg_images.is_empty() {
489+
msg["images"] = serde_json::Value::Array(msg_images);
490+
}
458491
built_messages.push(msg);
459492
}
460493

crates/openfang-api/static/index_body.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3042,6 +3042,30 @@ <h4>LLM Providers</h4>
30423042
</div>
30433043
</template>
30443044
</div>
3045+
<!-- Add Custom Provider -->
3046+
<div class="info-card mt-4" style="border:1px solid var(--border)">
3047+
<h4 style="margin-top:0">Add Custom Provider</h4>
3048+
<p class="text-xs text-dim mb-2">Connect any OpenAI-compatible API (vLLM, LiteLLM, LocalAI, etc.)</p>
3049+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem">
3050+
<div>
3051+
<label class="text-xs text-dim">Provider Name</label>
3052+
<input class="form-input" x-model="customProviderName" placeholder="e.g. my-local-llm">
3053+
</div>
3054+
<div>
3055+
<label class="text-xs text-dim">Base URL (required)</label>
3056+
<input class="form-input" x-model="customProviderUrl" placeholder="http://localhost:8080/v1">
3057+
</div>
3058+
</div>
3059+
<div class="mt-2">
3060+
<label class="text-xs text-dim">API Key (optional)</label>
3061+
<input class="form-input" type="password" x-model="customProviderKey" placeholder="sk-... (leave blank if not needed)">
3062+
</div>
3063+
<button class="btn btn-primary btn-sm mt-2" @click="addCustomProvider()" :disabled="!customProviderName.trim() || !customProviderUrl.trim() || addingCustomProvider">
3064+
<span x-show="!addingCustomProvider">Add Provider</span>
3065+
<span x-show="addingCustomProvider" class="spinner" style="width:10px;height:10px;border-width:2px"></span>
3066+
</button>
3067+
<span class="text-xs text-dim ml-2" x-text="customProviderStatus"></span>
3068+
</div>
30453069
<div class="empty-state" x-show="!providers.length">
30463070
<h4>No providers found</h4>
30473071
<p class="hint">Provider information could not be loaded. Check that the API is running.</p>

crates/openfang-api/static/js/pages/chat.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,10 @@ function chatPage() {
531531
is_error: !!t.is_error
532532
};
533533
});
534-
return { id: ++msgId, role: role, text: text, meta: '', tools: tools };
534+
var images = (m.images || []).map(function(img) {
535+
return { file_id: img.file_id, filename: img.filename || 'image' };
536+
});
537+
return { id: ++msgId, role: role, text: text, meta: '', tools: tools, images: images };
535538
});
536539
self.$nextTick(function() { self.scrollToBottom(); });
537540
}

crates/openfang-api/static/js/pages/settings.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ function settingsPage() {
2626
providerTesting: {},
2727
providerTestResults: {},
2828
copilotOAuth: { polling: false, userCode: '', verificationUri: '', pollId: '', interval: 5 },
29+
customProviderName: '',
30+
customProviderUrl: '',
31+
customProviderKey: '',
32+
customProviderStatus: '',
33+
addingCustomProvider: false,
2934
loading: true,
3035
loadError: '',
3136

@@ -499,6 +504,34 @@ function settingsPage() {
499504
this.providerUrlSaving[provider.id] = false;
500505
},
501506

507+
async addCustomProvider() {
508+
var name = this.customProviderName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
509+
if (!name) { OpenFangToast.error('Please enter a provider name'); return; }
510+
var url = this.customProviderUrl.trim();
511+
if (!url) { OpenFangToast.error('Please enter a base URL'); return; }
512+
if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) {
513+
OpenFangToast.error('URL must start with http:// or https://'); return;
514+
}
515+
this.addingCustomProvider = true;
516+
this.customProviderStatus = '';
517+
try {
518+
var result = await OpenFangAPI.put('/api/providers/' + encodeURIComponent(name) + '/url', { base_url: url });
519+
if (this.customProviderKey.trim()) {
520+
await OpenFangAPI.post('/api/providers/' + encodeURIComponent(name) + '/key', { key: this.customProviderKey.trim() });
521+
}
522+
this.customProviderName = '';
523+
this.customProviderUrl = '';
524+
this.customProviderKey = '';
525+
this.customProviderStatus = '';
526+
OpenFangToast.success('Provider "' + name + '" added' + (result.reachable ? ' (reachable)' : ' (not reachable yet)'));
527+
await this.loadProviders();
528+
} catch(e) {
529+
this.customProviderStatus = 'Error: ' + (e.message || 'Failed');
530+
OpenFangToast.error('Failed to add provider: ' + e.message);
531+
}
532+
this.addingCustomProvider = false;
533+
},
534+
502535
// -- Security methods --
503536
async loadSecurity() {
504537
this.secLoading = true;

crates/openfang-api/static/js/pages/wizard.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ function wizardPage() {
320320
},
321321

322322
get popularProviders() {
323-
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter'];
323+
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter', 'claude-code'];
324324
return this.providers.filter(function(p) {
325325
return popular.indexOf(p.id) >= 0;
326326
}).sort(function(a, b) {
@@ -329,7 +329,7 @@ function wizardPage() {
329329
},
330330

331331
get otherProviders() {
332-
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter'];
332+
var popular = ['anthropic', 'openai', 'gemini', 'groq', 'deepseek', 'openrouter', 'claude-code'];
333333
return this.providers.filter(function(p) {
334334
return popular.indexOf(p.id) < 0;
335335
});
@@ -355,7 +355,8 @@ function wizardPage() {
355355
fireworks: { url: 'https://fireworks.ai/account/api-keys', text: 'Get your key from Fireworks AI' },
356356
perplexity: { url: 'https://www.perplexity.ai/settings/api', text: 'Get your key from Perplexity Settings' },
357357
cohere: { url: 'https://dashboard.cohere.com/api-keys', text: 'Get your key from the Cohere Dashboard' },
358-
xai: { url: 'https://console.x.ai/', text: 'Get your key from the xAI Console' }
358+
xai: { url: 'https://console.x.ai/', text: 'Get your key from the xAI Console' },
359+
'claude-code': { url: 'https://docs.anthropic.com/en/docs/claude-code', text: 'Install: npm install -g @anthropic-ai/claude-code && claude auth (no API key needed)' }
359360
};
360361
return help[id] || null;
361362
},
@@ -474,7 +475,8 @@ function wizardPage() {
474475
fireworks: 'accounts/fireworks/models/llama-v3p1-70b-instruct',
475476
perplexity: 'llama-3.1-sonar-large-128k-online',
476477
cohere: 'command-r-plus',
477-
xai: 'grok-2'
478+
xai: 'grok-2',
479+
'claude-code': 'claude-code/sonnet'
478480
};
479481
return defaults[providerId] || '';
480482
},

crates/openfang-kernel/src/kernel.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,8 +567,34 @@ impl OpenFangKernel {
567567
warn!(
568568
provider = %config.default_model.provider,
569569
error = %e,
570-
"Primary LLM driver init failed — dashboard will still be accessible"
570+
"Primary LLM driver init failed — trying auto-detect"
571571
);
572+
// Auto-detect: scan env for any configured provider key
573+
if let Some((provider, model, env_var)) = drivers::detect_available_provider() {
574+
let auto_config = DriverConfig {
575+
provider: provider.to_string(),
576+
api_key: std::env::var(env_var).ok(),
577+
base_url: config.provider_urls.get(provider).cloned(),
578+
};
579+
match drivers::create_driver(&auto_config) {
580+
Ok(d) => {
581+
info!(
582+
provider = %provider,
583+
model = %model,
584+
"Auto-detected provider from {} — using as default",
585+
env_var
586+
);
587+
driver_chain.push(d);
588+
// Update the running config so agents get the right model
589+
config.default_model.provider = provider.to_string();
590+
config.default_model.model = model.to_string();
591+
config.default_model.api_key_env = env_var.to_string();
592+
}
593+
Err(e2) => {
594+
warn!(provider = %provider, error = %e2, "Auto-detected provider also failed");
595+
}
596+
}
597+
}
572598
}
573599
}
574600

0 commit comments

Comments
 (0)