Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f38fd53e4 | ||
|
|
c612120b7e | ||
|
|
e874ce9c4c |
@@ -2,13 +2,22 @@ import assert from 'node:assert/strict'
|
||||
|
||||
import { test } from 'vitest'
|
||||
|
||||
import { classifyActiveRuntime, hasValidBootstrapMarker } from './active-runtime-state'
|
||||
import { classifyActiveRuntime, hasValidBootstrapMarker, needsPackagedRuntimeUpgrade } from './active-runtime-state'
|
||||
|
||||
const VALID_MARKER = {
|
||||
pinnedCommit: '1234567890abcdef1234567890abcdef12345678',
|
||||
schemaVersion: 1
|
||||
}
|
||||
|
||||
test('a package upgrade refreshes only a proven managed runtime, once per package', () => {
|
||||
const stamp = {commit: 'b'.repeat(40), source: 'git'}
|
||||
assert.equal(needsPackagedRuntimeUpgrade(true, stamp, VALID_MARKER), true)
|
||||
assert.equal(needsPackagedRuntimeUpgrade(true, stamp, {...VALID_MARKER, pinnedCommit: stamp.commit}), false)
|
||||
assert.equal(needsPackagedRuntimeUpgrade(false, stamp, VALID_MARKER), false)
|
||||
assert.equal(needsPackagedRuntimeUpgrade(true, stamp, null), false)
|
||||
assert.equal(needsPackagedRuntimeUpgrade(true, {commit: '0'.repeat(40), source: 'fallback'}, VALID_MARKER), false)
|
||||
})
|
||||
|
||||
test('hasValidBootstrapMarker accepts the current schema with a real-looking commit', () => {
|
||||
assert.equal(hasValidBootstrapMarker(VALID_MARKER, 1), true)
|
||||
})
|
||||
|
||||
@@ -3,6 +3,18 @@ export interface BootstrapMarkerLike {
|
||||
schemaVersion?: unknown
|
||||
}
|
||||
|
||||
// An AITURK package upgrade must bring its managed agent along with the UI.
|
||||
// Developer checkouts and installs without proven Desktop ownership keep their
|
||||
// existing launch behavior. The installer preserves edits and refuses rollback.
|
||||
export function needsPackagedRuntimeUpgrade(
|
||||
packaged: boolean, stamp: { commit?: string; source?: string } | null,
|
||||
marker: BootstrapMarkerLike | null
|
||||
): boolean {
|
||||
return Boolean(packaged && stamp?.source !== 'fallback' &&
|
||||
/^[0-9a-f]{40}$/i.test(stamp?.commit || '') && !/^0+$/.test(stamp?.commit || '') &&
|
||||
hasValidBootstrapMarker(marker, 1) && marker?.pinnedCommit !== stamp?.commit)
|
||||
}
|
||||
|
||||
export interface ActiveRuntimeState {
|
||||
hasValidMarker: boolean
|
||||
shouldUseActiveRuntime: boolean
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
systemPreferences
|
||||
} from 'electron'
|
||||
|
||||
import { classifyActiveRuntime } from './active-runtime-state'
|
||||
import { classifyActiveRuntime, needsPackagedRuntimeUpgrade } from './active-runtime-state'
|
||||
import { destroyKeepaliveAgents, downloadAgentFor, jsonAgentFor, withRetry } from './api-transport'
|
||||
import { appIconCandidates, resolveAppIcon } from './app-icon'
|
||||
import { stopBackendChild as stopBackendChildImpl, stopBackendTreesForUpdate } from './backend-child'
|
||||
@@ -4905,6 +4905,11 @@ function resolveHermesBackend(backendArgs) {
|
||||
// bootstrap when the runtime itself is unusable.
|
||||
const activeRuntime = activeRuntimeState()
|
||||
|
||||
if (needsPackagedRuntimeUpgrade(IS_PACKAGED, INSTALL_STAMP, readBootstrapMarker())) {
|
||||
rememberLog('[bootstrap] AITURK package changed; updating its managed agent before launch.')
|
||||
return createBootstrapBackend(backendArgs)
|
||||
}
|
||||
|
||||
if (activeRuntime.shouldUseActiveRuntime && !bootstrapRepairRequested) {
|
||||
if (!activeRuntime.hasValidMarker) {
|
||||
rememberLog(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "aiturk-ide",
|
||||
"productName": "AITURK IDE",
|
||||
"private": true,
|
||||
"version": "1.0.0-beta.1",
|
||||
"version": "1.0.0-beta.2",
|
||||
"description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.",
|
||||
"author": "AITURK / TurkServis",
|
||||
"repository": {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function aiturkInstallStage(name: string): string | undefined {
|
||||
|
||||
const en = {
|
||||
intro: 'Bring your code, question, or idea. Build with AITURK.',
|
||||
pitch: 'Connect your models with your TurkServis account.',
|
||||
pitch: 'Connect your models and available image and audio tools with your TurkServis account.',
|
||||
key: 'TurkServis API key',
|
||||
getKey: 'My account and API keys',
|
||||
noModels: 'No models are available for this key.',
|
||||
@@ -74,7 +74,7 @@ const en = {
|
||||
|
||||
const tr: typeof en = {
|
||||
intro: 'Kodunuzu, sorunuzu veya fikrinizi paylaşın. AITURK ile birlikte geliştirin.',
|
||||
pitch: 'TurkServis hesabınızla modellerinize bağlanın.',
|
||||
pitch: 'TurkServis hesabınızla modellerinize, kullanılabilir görsel ve ses araçlarına bağlanın.',
|
||||
key: 'TurkServis API anahtarı',
|
||||
getKey: 'Hesabım ve API anahtarlarım',
|
||||
noModels: 'Bu anahtarla kullanılabilecek model bulunamadı.',
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Connect the AITURK distribution's media tools to its existing member key."""
|
||||
|
||||
|
||||
def configure_existing_media(cfg: dict) -> bool:
|
||||
"""Migrate an existing member endpoint when the upgraded backend starts."""
|
||||
providers = cfg.get("providers") or {}
|
||||
entries = providers.values() if isinstance(providers, dict) else []
|
||||
for entry in entries:
|
||||
if isinstance(entry, dict) and configure_media(cfg, entry):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def configure_media(cfg: dict, entry: dict) -> bool:
|
||||
if entry.get("base_url", "").rstrip("/") != "https://ai.turkservis.online/v1":
|
||||
return False
|
||||
key_env = entry.get("key_env")
|
||||
if not isinstance(key_env, str) or not key_env:
|
||||
return False
|
||||
servers = cfg.get("mcp_servers")
|
||||
if servers is None:
|
||||
servers = cfg["mcp_servers"] = {}
|
||||
if not isinstance(servers, dict):
|
||||
return False
|
||||
name = "turkservis-media"
|
||||
existing = servers.get(name, {})
|
||||
if not isinstance(existing, dict):
|
||||
return False
|
||||
if existing and existing.get("url") != "https://ai.turkservis.online/mcp":
|
||||
return False
|
||||
updated = {
|
||||
**existing,
|
||||
"url": "https://ai.turkservis.online/mcp",
|
||||
"headers": {"Authorization": "Bearer ${" + key_env + "}"},
|
||||
"timeout": 300,
|
||||
"tools": {"include": ["list_capabilities", "generate_media"]},
|
||||
}
|
||||
if updated == existing:
|
||||
return False
|
||||
servers[name] = updated
|
||||
return True
|
||||
@@ -405,6 +405,12 @@ def _eager_reconcile_own_session_db() -> None:
|
||||
|
||||
@asynccontextmanager
|
||||
async def _lifespan(app: "FastAPI"):
|
||||
from hermes_cli.aiturk_media import configure_existing_media
|
||||
from hermes_cli.config import read_raw_config, save_config
|
||||
with _CONFIG_MUTATION_LOCK:
|
||||
media_cfg = read_raw_config()
|
||||
if configure_existing_media(media_cfg):
|
||||
save_config(media_cfg)
|
||||
app.state.event_channels = {} # dict[str, set]
|
||||
app.state.event_lock = asyncio.Lock()
|
||||
app.state.pty_active_session_files = {} # dict[str, Path]
|
||||
@@ -8823,6 +8829,9 @@ def _write_custom_endpoint(cfg: Dict[str, Any], body: CustomEndpointUpdate) -> T
|
||||
providers[endpoint_id] = entry
|
||||
cfg["providers"] = providers
|
||||
|
||||
from hermes_cli.aiturk_media import configure_media
|
||||
configure_media(cfg, entry)
|
||||
|
||||
if body.make_default:
|
||||
cfg["model"] = _apply_main_model_assignment(
|
||||
cfg.get("model", {}), endpoint_id, model, base_url
|
||||
|
||||
Generated
+7
-7
@@ -65,7 +65,8 @@
|
||||
},
|
||||
"apps/desktop": {
|
||||
"name": "aiturk-ide",
|
||||
"version": "1.0.0-beta.1",
|
||||
"version": "1.0.0-beta.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@assistant-ui/core": "0.2.23",
|
||||
"@assistant-ui/react": "0.14.24",
|
||||
@@ -175,8 +176,7 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"get-windows": "9.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
},
|
||||
"apps/desktop/node_modules/@babel/code-frame": {
|
||||
"version": "8.0.0",
|
||||
@@ -7465,6 +7465,10 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aiturk-ide": {
|
||||
"resolved": "apps/desktop",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
||||
@@ -19873,10 +19877,6 @@
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/aiturk-ide": {
|
||||
"resolved": "apps/desktop",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from hermes_cli.aiturk_media import configure_media, configure_existing_media
|
||||
|
||||
|
||||
def test_existing_member_profile_migrates_once_without_changing_provider():
|
||||
entry = {"base_url": "https://ai.turkservis.online/v1", "key_env": "MEMBER_KEY"}
|
||||
cfg = {"providers": {"turkservis": dict(entry)}, "model": {"default": "kept"}}
|
||||
assert configure_existing_media(cfg)
|
||||
assert cfg["providers"]["turkservis"] == entry
|
||||
assert cfg["model"] == {"default": "kept"}
|
||||
assert not configure_existing_media(cfg)
|
||||
|
||||
|
||||
def test_member_key_is_referenced_without_copying_or_overwriting_other_servers():
|
||||
cfg = {"mcp_servers": {"other": {"url": "https://example.org/mcp"}}}
|
||||
entry = {"base_url": "https://ai.turkservis.online/v1", "key_env": "HERMES_CUSTOM_TURKSERVIS_API_KEY"}
|
||||
assert configure_media(cfg, entry)
|
||||
assert cfg["mcp_servers"]["other"] == {"url": "https://example.org/mcp"}
|
||||
assert cfg["mcp_servers"]["turkservis-media"]["headers"]["Authorization"] == "Bearer ${HERMES_CUSTOM_TURKSERVIS_API_KEY}"
|
||||
assert not configure_media(cfg, entry)
|
||||
entry["key_env"] = "ROTATED_MEMBER_KEY"
|
||||
assert configure_media(cfg, entry)
|
||||
assert "ROTATED_MEMBER_KEY" in cfg["mcp_servers"]["turkservis-media"]["headers"]["Authorization"]
|
||||
|
||||
|
||||
def test_other_provider_and_missing_key_do_not_enable_media():
|
||||
for entry in ({"base_url": "https://example.com/v1", "key_env": "SECRET"}, {"base_url": "https://ai.turkservis.online/v1"}):
|
||||
cfg = {}
|
||||
assert not configure_media(cfg, entry)
|
||||
assert cfg == {}
|
||||
|
||||
|
||||
def test_explicitly_disabled_connection_stays_disabled():
|
||||
cfg = {"mcp_servers": {"turkservis-media": {"url": "https://ai.turkservis.online/mcp", "enabled": False}}}
|
||||
configure_media(cfg, {"base_url": "https://ai.turkservis.online/v1", "key_env": "KEY"})
|
||||
assert cfg["mcp_servers"]["turkservis-media"]["enabled"] is False
|
||||
Reference in New Issue
Block a user