arcane-os 0.4.1 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -2053,6 +2053,21 @@
2053
2053
  }
2054
2054
 
2055
2055
  function determinateProgress(){
2056
+ const totalBytes=role?.progress?.totalBytes;
2057
+ const loadedBytes=role?.progress?.loadedBytes;
2058
+ if(
2059
+ Number.isFinite(totalBytes)
2060
+ &&totalBytes>0
2061
+ &&Number.isFinite(loadedBytes)
2062
+ &&loadedBytes>=0
2063
+ ){
2064
+ return {
2065
+ completed:Math.min(loadedBytes,totalBytes),
2066
+ total:totalBytes,
2067
+ unit:'bytes',
2068
+ byteTelemetry:true
2069
+ };
2070
+ }
2056
2071
  const total=Number(role?.progress?.total);
2057
2072
  const completed=Number(role?.progress?.completed);
2058
2073
  const unit=typeof role?.progress?.unit==='string'&&role.progress.unit
@@ -2068,23 +2083,162 @@
2068
2083
  return {
2069
2084
  completed,
2070
2085
  total,
2071
- unit
2086
+ unit,
2087
+ byteTelemetry:false
2072
2088
  };
2073
2089
  }
2074
2090
 
2091
+ function formatBytes(value){
2092
+ if(!Number.isFinite(value)||value<0){
2093
+ return '';
2094
+ }
2095
+ const bytes=value;
2096
+ const units=['B','KB','MB','GB'];
2097
+ let unitIndex=0;
2098
+ let amount=bytes;
2099
+ while(amount>=1000&&unitIndex<units.length-1){
2100
+ amount/=1000;
2101
+ unitIndex+=1;
2102
+ }
2103
+ const decimals=unitIndex===0||amount>=100
2104
+ ?0
2105
+ :amount>=10
2106
+ ?1
2107
+ :2;
2108
+ return `${Number(amount.toFixed(decimals))} ${units[unitIndex]}`;
2109
+ }
2110
+
2111
+ function formatPercentage(loadedBytes,totalBytes){
2112
+ const percentage=Math.min(100,Math.max(0,loadedBytes/totalBytes*100));
2113
+ if(loadedBytes<=0){
2114
+ return '0%';
2115
+ }
2116
+ if(loadedBytes>=totalBytes){
2117
+ return '100%';
2118
+ }
2119
+ return `${Math.min(99.9,percentage).toFixed(1)}%`;
2120
+ }
2121
+
2122
+ function approximateDuration(value){
2123
+ if(!Number.isFinite(value)||value<0){
2124
+ return '';
2125
+ }
2126
+ const suppliedSeconds=value;
2127
+ const seconds=Math.ceil(suppliedSeconds);
2128
+ if(seconds<1){
2129
+ return '<1s';
2130
+ }
2131
+ if(seconds<60){
2132
+ return `${seconds}s`;
2133
+ }
2134
+ const minutes=Math.floor(seconds/60);
2135
+ const remainingSeconds=seconds%60;
2136
+ if(minutes<60){
2137
+ return `${minutes}m ${String(remainingSeconds).padStart(2,'0')}s`;
2138
+ }
2139
+ const hours=Math.floor(minutes/60);
2140
+ const remainingMinutes=minutes%60;
2141
+ return `${hours}h ${String(remainingMinutes).padStart(2,'0')}m`;
2142
+ }
2143
+
2144
+ function activeTransferText(){
2145
+ const active=role?.progress?.activeTransfers;
2146
+ const limit=role?.progress?.transferLimit;
2147
+ const mode=typeof role?.progress?.transferMode==='string'
2148
+ ?role.progress.transferMode.trim().toLowerCase()
2149
+ :'';
2150
+ if(
2151
+ !Number.isSafeInteger(active)
2152
+ ||active<0
2153
+ ||!Number.isSafeInteger(limit)
2154
+ ||limit<1
2155
+ ||!mode
2156
+ ){
2157
+ return '';
2158
+ }
2159
+ if(mode==='single'){
2160
+ return active>0?'single transfer active':'single transfer idle';
2161
+ }
2162
+ if(mode==='probing'){
2163
+ return 'checking parallel Range support';
2164
+ }
2165
+ const noun=['range','ranges'].includes(mode)
2166
+ ?'range transfer worker'
2167
+ :['shard','shards'].includes(mode)
2168
+ ?'shard transfer worker'
2169
+ :['file','files'].includes(mode)
2170
+ ?'file transfer worker'
2171
+ :['single','single-file','monolith'].includes(mode)
2172
+ ?'single-file transfer worker'
2173
+ :`${mode.replaceAll('-',' ')} transfer worker`;
2174
+ return `${active} of ${limit} ${noun}${limit===1?'':'s'} active`;
2175
+ }
2176
+
2177
+ function progressElapsedText(){
2178
+ const elapsedMilliseconds=Number(role?.progress?.elapsedMs);
2179
+ if(!Number.isFinite(elapsedMilliseconds)||elapsedMilliseconds<0){
2180
+ return '';
2181
+ }
2182
+ const elapsedSeconds=Math.floor(elapsedMilliseconds/1000);
2183
+ const hours=Math.floor(elapsedSeconds/3600);
2184
+ const minutes=Math.floor(elapsedSeconds%3600/60);
2185
+ const seconds=elapsedSeconds%60;
2186
+ if(hours>0){
2187
+ return `${hours}h ${minutes}m elapsed`;
2188
+ }
2189
+ if(minutes>0){
2190
+ return `${minutes}m ${String(seconds).padStart(2,'0')}s elapsed`;
2191
+ }
2192
+ return `${seconds}s elapsed`;
2193
+ }
2194
+
2075
2195
  function progressMessage(){
2076
2196
  const measured=determinateProgress();
2077
- const phase=typeof role?.progress?.phase==='string'&&role.progress.phase
2197
+ const phaseValue=typeof role?.progress?.phase==='string'&&role.progress.phase
2078
2198
  ?role.progress.phase
2079
2199
  :'loading';
2200
+ const phase=phaseValue.replaceAll('-',' ');
2080
2201
  const parts=[
2081
2202
  `Loading ${modelDisplayName()} through the Arcane SDK`,
2082
2203
  phase
2083
2204
  ];
2084
- if(measured){
2085
- parts.push(`${measured.completed} of ${measured.total} ${measured.unit}`);
2205
+ if(measured?.byteTelemetry){
2206
+ const remainingBytes=Math.max(0,measured.total-measured.completed);
2207
+ parts.push(formatPercentage(measured.completed,measured.total));
2208
+ parts.push(
2209
+ `${formatBytes(measured.completed)} of ${formatBytes(measured.total)} downloaded`
2210
+ );
2211
+ parts.push(`${formatBytes(remainingBytes)} remaining`);
2212
+ const bytesPerSecond=role?.progress?.bytesPerSecond;
2213
+ if(Number.isFinite(bytesPerSecond)&&bytesPerSecond>=0){
2214
+ parts.push(`${formatBytes(bytesPerSecond)}/s`);
2215
+ }else{
2216
+ parts.push('Calculating speed');
2217
+ }
2218
+ if(remainingBytes>0){
2219
+ const eta=approximateDuration(role?.progress?.etaSeconds);
2220
+ parts.push(eta?`About ${eta} remaining`:'Estimating time remaining');
2221
+ }
2222
+ }else if(measured){
2223
+ const suffix=phaseValue==='download'&&measured.unit==='files'
2224
+ ?'files complete'
2225
+ :measured.unit;
2226
+ parts.push(`${measured.completed} of ${measured.total} ${suffix}`);
2227
+ }
2228
+ const transfers=activeTransferText();
2229
+ if(transfers){
2230
+ parts.push(transfers);
2231
+ }
2232
+ const elapsed=progressElapsedText();
2233
+ if(elapsed){
2234
+ parts.push(elapsed);
2235
+ if(role?.progress?.heartbeat===true){
2236
+ parts.push('Still working');
2237
+ }
2238
+ }
2239
+ if(!measured?.byteTelemetry){
2240
+ parts.push('The first activation can take several minutes; keep this tab open');
2086
2241
  }
2087
- parts.push('The first activation can take several minutes; keep this tab open');
2088
2242
  return parts.join(' · ');
2089
2243
  }
2090
2244
 
@@ -2094,16 +2248,19 @@
2094
2248
  if(!visible){
2095
2249
  progress.removeAttribute('value');
2096
2250
  progress.removeAttribute('max');
2251
+ progress.removeAttribute('aria-valuetext');
2097
2252
  return;
2098
2253
  }
2099
2254
  const measured=determinateProgress();
2100
2255
  if(!measured){
2101
2256
  progress.removeAttribute('value');
2102
2257
  progress.removeAttribute('max');
2258
+ progress.setAttribute('aria-valuetext',progressMessage());
2103
2259
  return;
2104
2260
  }
2105
2261
  progress.max=measured.total;
2106
2262
  progress.value=measured.completed;
2263
+ progress.setAttribute('aria-valuetext',progressMessage());
2107
2264
  }
2108
2265
 
2109
2266
  function render(){
@@ -278,9 +278,9 @@
278
278
  summary.setAttribute('role','alert');
279
279
  summary.textContent=userManaged
280
280
  ?guidanceMessage
281
- ||'Review the unavailable user-managed Android service, then Retry or open Profile and switch the affected option to OpenAI.'
281
+ ||'Review the unavailable user-managed Android service, then Retry or open Profile and switch the language-model option to TWiN Cloud.'
282
282
  :browser
283
- ?'Start the unavailable local service yourself and Retry, or open Profile, switch each affected option to OpenAI, and enter your OpenAI API key.'
283
+ ?'Start the unavailable local service yourself and Retry, or open Profile, switch the language-model option to TWiN Cloud, and enter your TWiN access key.'
284
284
  :'Arcane could not recover every selected managed local service. Retry or open Arcane Settings > Local AI.';
285
285
  return report;
286
286
  }
@@ -917,7 +917,9 @@ class AI {
917
917
  // This is the enum section for inference configuration
918
918
  #service = {
919
919
  baseURL: {
920
- OPENAI: 'https://api.openai.com/v1'
920
+ // OPENAI remains the legacy route identifier for compatibility;
921
+ // remote LLM chat is provided by TWiN Cloud.
922
+ OPENAI: 'https://inference.do-ai.run/v1'
921
923
  },
922
924
  sttURL: {
923
925
  LOCAL_SPEACH: 'http://127.0.0.1:8011/v1',
@@ -944,7 +946,7 @@ class AI {
944
946
  }
945
947
 
946
948
  #models = {
947
- OPENAI:'gpt-4o'
949
+ OPENAI:'openai-gpt-oss-120b'
948
950
  }
949
951
 
950
952
  #sttModels = {
@@ -1155,11 +1157,14 @@ class AI {
1155
1157
 
1156
1158
  // Browser-delivered framework code must not contain provider credentials.
1157
1159
  // The selected host, application, or user profile supplies one at runtime.
1158
- get license(){
1159
- return this.#license || globalThis.arcane?.config?.openAI?.apiKey || '';
1160
+ get twinKey(){
1161
+ return this.#license
1162
+ ||globalThis.arcane?.config?.twinCloud?.accessKey
1163
+ ||globalThis.arcane?.config?.openAI?.apiKey
1164
+ ||'';
1160
1165
  }
1161
-
1162
- set license(value){
1166
+
1167
+ set twinKey(value){
1163
1168
  this.#license=typeof value==='string' ? value.trim():'';
1164
1169
  this.#retainLegacyLLMReadiness(
1165
1170
  this.#reconcileLegacyLLMReadiness()
@@ -1170,6 +1175,17 @@ class AI {
1170
1175
  return this.#license;
1171
1176
  }
1172
1177
 
1178
+ // Retain the established credential property while consumers move their
1179
+ // user-facing profile field to the TWiN key name.
1180
+ get license(){
1181
+ return this.twinKey;
1182
+ }
1183
+
1184
+ set license(value){
1185
+ this.twinKey=value;
1186
+ return this.#license;
1187
+ }
1188
+
1173
1189
  #legacyLLMCapability(providerId){
1174
1190
  if(providerId==='OPENAI'){
1175
1191
  return this.llmService==='OPENAI'
@@ -718,12 +718,18 @@ function guidance(mode,slots){
718
718
 
719
719
  const services=recoveryServices(slots);
720
720
  const profileSettings=affectedSlots.map(slot=>slot+'Provider');
721
+ const twinCloudOption=affectedSlots.includes('llm')
722
+ ?' Alternatively, switch the Profile language-model setting to TWiN Cloud and enter a TWiN access key.'
723
+ :'';
721
724
  const message=mode==='browser'
722
725
  ?browserManualInstruction(services)
723
- +' Alternatively, switch the affected Profile setting to OpenAI and enter an OpenAI API/license key. Arcane will not switch providers automatically.'
726
+ +twinCloudOption
727
+ +' Arcane will not switch providers automatically.'
724
728
  :mode===USER_MANAGED_LOOPBACK_PROVIDER_MODE
725
729
  ?userManagedLoopbackInstruction(services)
726
- +' Arcane does not install, start, repair, pull, or otherwise manage this service or its models. Alternatively, switch the affected Profile setting to OpenAI. Arcane will not switch providers automatically.'
730
+ +' Arcane does not install, start, repair, pull, or otherwise manage this service or its models.'
731
+ +twinCloudOption
732
+ +' Arcane will not switch providers automatically.'
727
733
  :'Arcane could not recover the selected local AI service. Retry or review Local AI in Arcane Settings. Arcane will not switch providers automatically.';
728
734
 
729
735
  return completeResult({