arcane-os 0.4.2 → 0.5.1

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.2",
3
+ "version": "0.5.1",
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,10 +2083,97 @@
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
+
2075
2177
  function progressElapsedText(){
2076
2178
  const elapsedMilliseconds=Number(role?.progress?.elapsedMs);
2077
2179
  if(!Number.isFinite(elapsedMilliseconds)||elapsedMilliseconds<0){
@@ -2100,16 +2202,32 @@
2100
2202
  `Loading ${modelDisplayName()} through the Arcane SDK`,
2101
2203
  phase
2102
2204
  ];
2103
- if(measured){
2104
- if(
2105
- phaseValue==='download'
2106
- &&measured.unit==='files'
2107
- &&measured.completed<measured.total
2108
- ){
2109
- parts.push(`part ${measured.completed+1} of ${measured.total}`);
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`);
2110
2215
  }else{
2111
- parts.push(`${measured.completed} of ${measured.total} ${measured.unit}`);
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');
2112
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);
2113
2231
  }
2114
2232
  const elapsed=progressElapsedText();
2115
2233
  if(elapsed){
@@ -2118,7 +2236,9 @@
2118
2236
  parts.push('Still working');
2119
2237
  }
2120
2238
  }
2121
- parts.push('The first activation can take several minutes; keep this tab open');
2239
+ if(!measured?.byteTelemetry){
2240
+ parts.push('The first activation can take several minutes; keep this tab open');
2241
+ }
2122
2242
  return parts.join(' · ');
2123
2243
  }
2124
2244
 
@@ -2128,16 +2248,19 @@
2128
2248
  if(!visible){
2129
2249
  progress.removeAttribute('value');
2130
2250
  progress.removeAttribute('max');
2251
+ progress.removeAttribute('aria-valuetext');
2131
2252
  return;
2132
2253
  }
2133
2254
  const measured=determinateProgress();
2134
2255
  if(!measured){
2135
2256
  progress.removeAttribute('value');
2136
2257
  progress.removeAttribute('max');
2258
+ progress.setAttribute('aria-valuetext',progressMessage());
2137
2259
  return;
2138
2260
  }
2139
2261
  progress.max=measured.total;
2140
2262
  progress.value=measured.completed;
2263
+ progress.setAttribute('aria-valuetext',progressMessage());
2141
2264
  }
2142
2265
 
2143
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 = {
@@ -967,16 +969,21 @@ class AI {
967
969
  return {
968
970
  OPENAI: {
969
971
  'Content-Type': 'application/json',
970
- 'Authorization': `Bearer ${this.license}`
972
+ 'Authorization': `Bearer ${this.twinKey}`
971
973
  }
972
974
  };
973
975
  }
974
976
 
977
+ get #legacyOpenAISpeechKey(){
978
+ const value=globalThis.arcane?.config?.openAI?.apiKey;
979
+ return typeof value==='string'?value:'';
980
+ }
981
+
975
982
  get #ttsHeaders(){
976
983
  return {
977
984
  OPENAI: {
978
985
  'Content-Type': 'application/json',
979
- 'Authorization': `Bearer ${this.license}`
986
+ 'Authorization': `Bearer ${this.#legacyOpenAISpeechKey}`
980
987
  },
981
988
  LOCAL_SPEACH: {
982
989
  'Content-Type': 'application/json',
@@ -987,7 +994,7 @@ class AI {
987
994
  get #sttHeaders(){
988
995
  return {
989
996
  OPENAI: {
990
- 'Authorization': `Bearer ${this.license}`,
997
+ 'Authorization': `Bearer ${this.#legacyOpenAISpeechKey}`,
991
998
  },
992
999
  LOCAL_SPEACH: {}
993
1000
  };
@@ -1155,11 +1162,13 @@ class AI {
1155
1162
 
1156
1163
  // Browser-delivered framework code must not contain provider credentials.
1157
1164
  // The selected host, application, or user profile supplies one at runtime.
1158
- get license(){
1159
- return this.#license || globalThis.arcane?.config?.openAI?.apiKey || '';
1165
+ get twinKey(){
1166
+ return this.#license
1167
+ ||globalThis.arcane?.config?.twinCloud?.accessKey
1168
+ ||'';
1160
1169
  }
1161
-
1162
- set license(value){
1170
+
1171
+ set twinKey(value){
1163
1172
  this.#license=typeof value==='string' ? value.trim():'';
1164
1173
  this.#retainLegacyLLMReadiness(
1165
1174
  this.#reconcileLegacyLLMReadiness()
@@ -1170,6 +1179,17 @@ class AI {
1170
1179
  return this.#license;
1171
1180
  }
1172
1181
 
1182
+ // Retain the established credential property while consumers move their
1183
+ // user-facing profile field to the TWiN key name.
1184
+ get license(){
1185
+ return this.twinKey;
1186
+ }
1187
+
1188
+ set license(value){
1189
+ this.twinKey=value;
1190
+ return this.#license;
1191
+ }
1192
+
1173
1193
  #legacyLLMCapability(providerId){
1174
1194
  if(providerId==='OPENAI'){
1175
1195
  return this.llmService==='OPENAI'
@@ -1426,7 +1446,8 @@ class AI {
1426
1446
  return false;
1427
1447
  }
1428
1448
  if(providerId==='OPENAI'){
1429
- return Boolean(this.license)&&typeof globalThis.fetch==='function';
1449
+ return Boolean(this.#legacyOpenAISpeechKey)
1450
+ &&typeof globalThis.fetch==='function';
1430
1451
  }
1431
1452
  if(providerId==='LOCAL_SPEACH'){
1432
1453
  return Boolean(this.#nativeSpeech(service,role));
@@ -1854,7 +1875,11 @@ class AI {
1854
1875
  throw error;
1855
1876
  }
1856
1877
 
1857
- if(service==='OPENAI'&&this.license){
1878
+ if(service==='OPENAI'&&(
1879
+ role==='llm'
1880
+ ?Boolean(this.twinKey)
1881
+ :Boolean(this.#legacyOpenAISpeechKey)
1882
+ )){
1858
1883
  return true;
1859
1884
  }
1860
1885
 
@@ -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({