replicas-engine 0.1.519 → 0.1.522

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 (2) hide show
  1. package/dist/src/index.js +272 -166
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -195,20 +195,22 @@ function detectLanguageByPath(filePath) {
195
195
  return EXT_TO_LANGUAGE[ext] ?? null;
196
196
  }
197
197
 
198
- // ../shared/src/credentials/opencode-go.ts
199
- import { z } from "zod";
200
- var OPENCODE_GO_PROVIDER = "opencode-go";
201
- var opencodeGoModelsDevSchema = z.object({
202
- "opencode-go": z.object({
203
- models: z.record(z.string(), z.object({
204
- id: z.string().optional(),
205
- name: z.string(),
206
- description: z.string().optional(),
207
- status: z.enum(["alpha", "beta", "deprecated"]).optional()
208
- }))
209
- }).optional()
210
- });
211
- var OPENCODE_GO_CATALOG_CACHE_MS = 5 * 6e4;
198
+ // ../shared/src/aster.ts
199
+ var ASTER_PROVIDER = "aster";
200
+ var ASTER_BASE_URL = "https://api.asterlab.ai/v1";
201
+ var ASTER_MODELS = [
202
+ "kimi-k3",
203
+ "glm-5.2",
204
+ "gpt-oss-120b-fast",
205
+ "gpt-oss-120b"
206
+ ];
207
+ var DEFAULT_ASTER_MODEL = ASTER_MODELS[0];
208
+ var ASTER_MODEL_LABELS = {
209
+ "kimi-k3": "Kimi K3",
210
+ "glm-5.2": "GLM 5.2",
211
+ "gpt-oss-120b-fast": "GPT-OSS 120B Fast",
212
+ "gpt-oss-120b": "GPT-OSS 120B"
213
+ };
212
214
 
213
215
  // ../shared/src/engine/types.ts
214
216
  var DEFAULT_CHAT_TITLES = {
@@ -320,8 +322,8 @@ var AGENT_MODELS = {
320
322
  CLAUDE_HAIKU_4_5_MODEL,
321
323
  "kimi-k2.5"
322
324
  ],
323
- opencode: OPENROUTER_MODELS,
324
- pi: OPENROUTER_MODELS,
325
+ opencode: [...OPENROUTER_MODELS, ...ASTER_MODELS],
326
+ pi: [...OPENROUTER_MODELS, ...ASTER_MODELS],
325
327
  relay: [CLAUDE_FABLE_5_MODEL, DEFAULT_CLAUDE_MODEL, CLAUDE_SONNET_5_MODEL]
326
328
  };
327
329
  var MODEL_LABELS = {
@@ -342,6 +344,7 @@ var MODEL_LABELS = {
342
344
  "minimax/minimax-m3": "MiniMax M3 via OpenRouter",
343
345
  "xiaomi/mimo-v2.5-pro": "MiMo V2.5 Pro via OpenRouter",
344
346
  "moonshotai/kimi-k2.6": "Kimi K2.6 via OpenRouter",
347
+ ...ASTER_MODEL_LABELS,
345
348
  "gpt-5.4": "GPT-5.4",
346
349
  "gpt-5.4-mini": "GPT-5.4 Mini",
347
350
  "gpt-5.4-nano": "GPT-5.4 Nano",
@@ -609,7 +612,7 @@ var WORKSPACE_SIZES = ["small", "large"];
609
612
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
610
613
 
611
614
  // ../shared/src/e2b.ts
612
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-29-v1";
615
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-29-v4";
613
616
 
614
617
  // ../shared/src/runtime-env.ts
615
618
  function shellQuotePosix(value) {
@@ -2943,55 +2946,55 @@ function hasChatStarted(chat) {
2943
2946
  var DESKTOP_NOVNC_PORT = 6080;
2944
2947
 
2945
2948
  // ../shared/src/engine/v1.ts
2946
- import { z as z2 } from "zod";
2949
+ import { z } from "zod";
2947
2950
  var MERGED_MESSAGE_SEPARATOR = "\n\n<!-- replicas:merged -->\n\n";
2948
2951
  var ENGINE_HEALTH_WAIT_HEADER = "X-Replicas-Health-Wait";
2949
2952
  var ENGINE_HEALTH_WAIT_QUERY_PARAM = "wait_ms";
2950
2953
  var ENGINE_HEALTH_MAX_WAIT_MS = 2e3;
2951
- var createChatRequestSchema = z2.object({
2952
- id: z2.string().uuid().optional(),
2953
- createdAt: z2.string().datetime().optional(),
2954
- provider: z2.enum(VALID_AGENT_PROVIDERS),
2955
- title: z2.string().min(1).optional(),
2956
- parentChatId: z2.string().uuid().optional(),
2957
- clientRequestId: z2.string().min(1).max(128).optional()
2954
+ var createChatRequestSchema = z.object({
2955
+ id: z.string().uuid().optional(),
2956
+ createdAt: z.string().datetime().optional(),
2957
+ provider: z.enum(VALID_AGENT_PROVIDERS),
2958
+ title: z.string().min(1).optional(),
2959
+ parentChatId: z.string().uuid().optional(),
2960
+ clientRequestId: z.string().min(1).max(128).optional()
2958
2961
  });
2959
- var sendChatMessageRequestSchema = z2.object({
2960
- messageId: z2.string().optional(),
2961
- submittedAt: z2.string().optional(),
2962
- message: z2.string(),
2963
- model: z2.string().optional(),
2964
- customInstructions: z2.string().optional(),
2965
- planMode: z2.boolean().optional(),
2966
- images: z2.array(z2.object({
2967
- type: z2.literal("image"),
2968
- source: z2.discriminatedUnion("type", [
2969
- z2.object({ type: z2.literal("base64"), media_type: z2.enum(IMAGE_MEDIA_TYPES), data: z2.string() }),
2970
- z2.object({ type: z2.literal("url"), url: z2.string() })
2962
+ var sendChatMessageRequestSchema = z.object({
2963
+ messageId: z.string().optional(),
2964
+ submittedAt: z.string().optional(),
2965
+ message: z.string(),
2966
+ model: z.string().optional(),
2967
+ customInstructions: z.string().optional(),
2968
+ planMode: z.boolean().optional(),
2969
+ images: z.array(z.object({
2970
+ type: z.literal("image"),
2971
+ source: z.discriminatedUnion("type", [
2972
+ z.object({ type: z.literal("base64"), media_type: z.enum(IMAGE_MEDIA_TYPES), data: z.string() }),
2973
+ z.object({ type: z.literal("url"), url: z.string() })
2971
2974
  ])
2972
2975
  })).optional(),
2973
- thinkingLevel: z2.enum(VALID_THINKING_LEVELS).optional(),
2974
- goalMode: z2.boolean().optional(),
2975
- fastMode: z2.boolean().optional(),
2976
- enableInteractiveTools: z2.boolean().optional(),
2977
- type: z2.string().optional(),
2978
- merge: z2.boolean().optional(),
2979
- idempotencyKey: z2.string().optional(),
2980
- senderUserId: z2.string().optional(),
2981
- senderEmail: z2.string().optional(),
2982
- senderDisplayName: z2.string().optional(),
2983
- senderAvatarUrl: z2.string().optional(),
2984
- errorNotificationTarget: z2.discriminatedUnion("type", [
2985
- z2.object({ type: z2.literal("slack") }),
2986
- z2.object({ type: z2.literal("linear"), sessionId: z2.string() }),
2987
- z2.object({
2988
- type: z2.literal("code_host"),
2989
- provider: z2.enum(["github", "gitlab"]),
2990
- resource: z2.enum(["issue", "pull_request"]),
2991
- repositoryId: z2.string(),
2992
- resourceNumber: z2.number()
2976
+ thinkingLevel: z.enum(VALID_THINKING_LEVELS).optional(),
2977
+ goalMode: z.boolean().optional(),
2978
+ fastMode: z.boolean().optional(),
2979
+ enableInteractiveTools: z.boolean().optional(),
2980
+ type: z.string().optional(),
2981
+ merge: z.boolean().optional(),
2982
+ idempotencyKey: z.string().optional(),
2983
+ senderUserId: z.string().optional(),
2984
+ senderEmail: z.string().optional(),
2985
+ senderDisplayName: z.string().optional(),
2986
+ senderAvatarUrl: z.string().optional(),
2987
+ errorNotificationTarget: z.discriminatedUnion("type", [
2988
+ z.object({ type: z.literal("slack") }),
2989
+ z.object({ type: z.literal("linear"), sessionId: z.string() }),
2990
+ z.object({
2991
+ type: z.literal("code_host"),
2992
+ provider: z.enum(["github", "gitlab"]),
2993
+ resource: z.enum(["issue", "pull_request"]),
2994
+ repositoryId: z.string(),
2995
+ resourceNumber: z.number()
2993
2996
  }),
2994
- z2.object({ type: z2.literal("automation"), executionId: z2.string() })
2997
+ z.object({ type: z.literal("automation"), executionId: z.string() })
2995
2998
  ]).optional()
2996
2999
  }).passthrough();
2997
3000
  function isChatMessageSender(value) {
@@ -3332,32 +3335,32 @@ var DEFAULT_WORKSPACE_FILTERS = {
3332
3335
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
3333
3336
 
3334
3337
  // ../shared/src/routes/workspace-events.ts
3335
- import { z as z3 } from "zod";
3336
- var workspaceChangedEventSchema = z3.discriminatedUnion("type", [
3337
- z3.object({ type: z3.literal("workspace.changed"), workspaceId: z3.string(), ts: z3.string() }),
3338
- z3.object({
3339
- type: z3.literal("workspace.chat.changed"),
3340
- workspaceId: z3.string(),
3341
- chatId: z3.string(),
3342
- ts: z3.string()
3338
+ import { z as z2 } from "zod";
3339
+ var workspaceChangedEventSchema = z2.discriminatedUnion("type", [
3340
+ z2.object({ type: z2.literal("workspace.changed"), workspaceId: z2.string(), ts: z2.string() }),
3341
+ z2.object({
3342
+ type: z2.literal("workspace.chat.changed"),
3343
+ workspaceId: z2.string(),
3344
+ chatId: z2.string(),
3345
+ ts: z2.string()
3343
3346
  }),
3344
- z3.object({ type: z3.literal("presence.changed"), ts: z3.string() })
3347
+ z2.object({ type: z2.literal("presence.changed"), ts: z2.string() })
3345
3348
  ]);
3346
3349
 
3347
3350
  // ../shared/src/routes/presence.ts
3348
- import { z as z4 } from "zod";
3349
- var presenceStatusSchema = z4.enum(["online", "typing"]);
3350
- var presenceLocationSchema = z4.object({
3351
- environmentId: z4.string().optional(),
3352
- workspaceId: z4.string().optional()
3351
+ import { z as z3 } from "zod";
3352
+ var presenceStatusSchema = z3.enum(["online", "typing"]);
3353
+ var presenceLocationSchema = z3.object({
3354
+ environmentId: z3.string().optional(),
3355
+ workspaceId: z3.string().optional()
3353
3356
  });
3354
- var presenceEntrySchema = z4.object({
3355
- userId: z4.string(),
3357
+ var presenceEntrySchema = z3.object({
3358
+ userId: z3.string(),
3356
3359
  status: presenceStatusSchema,
3357
3360
  location: presenceLocationSchema,
3358
- ts: z4.string()
3361
+ ts: z3.string()
3359
3362
  });
3360
- var updatePresenceRequestSchema = z4.object({
3363
+ var updatePresenceRequestSchema = z3.object({
3361
3364
  status: presenceStatusSchema,
3362
3365
  location: presenceLocationSchema.optional()
3363
3366
  });
@@ -10069,7 +10072,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
10069
10072
  var MIN_CODEX_CLI_VERSION = "0.144.6";
10070
10073
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
10071
10074
  var codexCliVersionEnsured = null;
10072
- var ENGINE_PACKAGE_VERSION = "0.1.519";
10075
+ var ENGINE_PACKAGE_VERSION = "0.1.522";
10073
10076
  var INITIALIZE_METHOD = "initialize";
10074
10077
  var INITIALIZED_NOTIFICATION = "initialized";
10075
10078
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -12671,6 +12674,24 @@ import { randomBytes as randomBytes2 } from "crypto";
12671
12674
  import { fileURLToPath } from "url";
12672
12675
  import { Agent } from "undici";
12673
12676
  import { z as z5 } from "zod";
12677
+
12678
+ // ../shared/src/credentials/opencode-go.ts
12679
+ import { z as z4 } from "zod";
12680
+ var OPENCODE_GO_PROVIDER = "opencode-go";
12681
+ var DEFAULT_OPENCODE_GO_MODEL = "glm-5.2";
12682
+ var opencodeGoModelsDevSchema = z4.object({
12683
+ "opencode-go": z4.object({
12684
+ models: z4.record(z4.string(), z4.object({
12685
+ id: z4.string().optional(),
12686
+ name: z4.string(),
12687
+ description: z4.string().optional(),
12688
+ status: z4.enum(["alpha", "beta", "deprecated"]).optional()
12689
+ }))
12690
+ }).optional()
12691
+ });
12692
+ var OPENCODE_GO_CATALOG_CACHE_MS = 5 * 6e4;
12693
+
12694
+ // src/managers/opencode-manager.ts
12674
12695
  import {
12675
12696
  createOpencodeClient,
12676
12697
  createOpencodeServer
@@ -12719,20 +12740,46 @@ async function hasOpencodeCredentials(provider) {
12719
12740
  return false;
12720
12741
  }
12721
12742
  }
12743
+ async function getOpencodeProvider() {
12744
+ if (await hasOpencodeCredentials(OPENCODE_GO_PROVIDER)) return OPENCODE_GO_PROVIDER;
12745
+ if (await hasOpencodeCredentials(ASTER_PROVIDER)) return ASTER_PROVIDER;
12746
+ return "openrouter";
12747
+ }
12748
+ function getDefaultOpencodeModel(provider) {
12749
+ if (provider === OPENCODE_GO_PROVIDER) return DEFAULT_OPENCODE_GO_MODEL;
12750
+ if (provider === ASTER_PROVIDER) return DEFAULT_ASTER_MODEL;
12751
+ return DEFAULT_OPENCODE_MODEL;
12752
+ }
12722
12753
  function getOpenCodeGoModel(model) {
12723
12754
  return model.replace(/^[^/]+\//, "");
12724
12755
  }
12725
- async function opencodeConfig(model) {
12756
+ function opencodeConfig(provider, model) {
12726
12757
  let config;
12727
- if (await hasOpencodeCredentials(OPENCODE_GO_PROVIDER)) {
12758
+ if (provider === OPENCODE_GO_PROVIDER) {
12728
12759
  config = {
12729
12760
  enabled_providers: [OPENCODE_GO_PROVIDER],
12730
12761
  model: `${OPENCODE_GO_PROVIDER}/${model}`,
12731
12762
  permission: OPENCODE_WORKSPACE_PERMISSION,
12732
12763
  share: "disabled"
12733
12764
  };
12765
+ } else if (provider === ASTER_PROVIDER) {
12766
+ const models = getConfiguredOpencodeModels(model, provider);
12767
+ config = {
12768
+ enabled_providers: [ASTER_PROVIDER],
12769
+ model: `${ASTER_PROVIDER}/${model}`,
12770
+ provider: {
12771
+ [ASTER_PROVIDER]: {
12772
+ npm: "@ai-sdk/openai-compatible",
12773
+ name: "Aster",
12774
+ options: { baseURL: ASTER_BASE_URL },
12775
+ models: Object.fromEntries(models.map((candidate) => [candidate, {}]))
12776
+ }
12777
+ },
12778
+ permission: OPENCODE_WORKSPACE_PERMISSION,
12779
+ share: "disabled"
12780
+ };
12734
12781
  } else {
12735
- const models = getConfiguredOpencodeModels(model);
12782
+ const models = getConfiguredOpencodeModels(model, provider);
12736
12783
  config = {
12737
12784
  enabled_providers: ["openrouter"],
12738
12785
  model: `openrouter/${model}`,
@@ -12745,14 +12792,10 @@ async function opencodeConfig(model) {
12745
12792
  share: "disabled"
12746
12793
  };
12747
12794
  }
12748
- const mcp = await readProvisionedOpencodeMcpConfig();
12749
- if (mcp && Object.keys(mcp).length > 0) {
12750
- config.mcp = mcp;
12751
- }
12752
12795
  return config;
12753
12796
  }
12754
- function getConfiguredOpencodeModels(model) {
12755
- return [.../* @__PURE__ */ new Set([model, ...OPENROUTER_MODELS])];
12797
+ function getConfiguredOpencodeModels(model, provider) {
12798
+ return [.../* @__PURE__ */ new Set([model, ...provider === ASTER_PROVIDER ? ASTER_MODELS : OPENROUTER_MODELS])];
12756
12799
  }
12757
12800
  function opencodeCommandToSlashCommand(command) {
12758
12801
  return createProviderSlashCommand("opencode", command.name, command.description);
@@ -12942,7 +12985,8 @@ var OpencodeManager = class extends CodingAgentManager {
12942
12985
  }
12943
12986
  this.slashCommandsRequest ??= (async () => {
12944
12987
  try {
12945
- const client = await this.ensureClient(DEFAULT_OPENCODE_MODEL);
12988
+ const provider = await getOpencodeProvider();
12989
+ const client = await this.ensureClient(getDefaultOpencodeModel(provider), provider);
12946
12990
  const directories = [this.workingDirectory, ...await getAgentAdditionalDirectories()];
12947
12991
  const perDirectory = await Promise.all(directories.map(async (directory) => {
12948
12992
  try {
@@ -12972,8 +13016,7 @@ var OpencodeManager = class extends CodingAgentManager {
12972
13016
  })();
12973
13017
  return this.slashCommandsRequest;
12974
13018
  }
12975
- async ensureClient(model) {
12976
- const providerId = await hasOpencodeCredentials(OPENCODE_GO_PROVIDER) ? OPENCODE_GO_PROVIDER : "openrouter";
13019
+ async ensureClient(model, providerId) {
12977
13020
  if (this.client && this.providerId !== providerId) {
12978
13021
  this.eventAbortController?.abort();
12979
13022
  this.server?.close();
@@ -12983,11 +13026,15 @@ var OpencodeManager = class extends CodingAgentManager {
12983
13026
  }
12984
13027
  this.providerId = providerId;
12985
13028
  const configuredModel = providerId === OPENCODE_GO_PROVIDER ? getOpenCodeGoModel(model) : model;
13029
+ const providerModels = providerId === ASTER_PROVIDER ? ASTER_MODELS : providerId === "openrouter" ? OPENROUTER_MODELS : null;
13030
+ if (providerModels && !providerModels.includes(configuredModel)) {
13031
+ throw new Error(`Model ${configuredModel} is not available through ${providerId}.`);
13032
+ }
12986
13033
  if (this.client && (providerId === OPENCODE_GO_PROVIDER || this.configuredModels.has(configuredModel))) {
12987
13034
  return this.client;
12988
13035
  }
12989
- if (this.providerId !== OPENCODE_GO_PROVIDER && !await hasOpencodeCredentials("openrouter")) {
12990
- throw new Error("OpenCode Go or OpenRouter credentials are not configured for Opencode in this workspace.");
13036
+ if (!await hasOpencodeCredentials(providerId)) {
13037
+ throw new Error("OpenCode Go, Aster, or OpenRouter credentials are not configured for Opencode in this workspace.");
12991
13038
  }
12992
13039
  this.eventAbortController?.abort();
12993
13040
  this.server?.close();
@@ -12997,10 +13044,13 @@ var OpencodeManager = class extends CodingAgentManager {
12997
13044
  }
12998
13045
  const password = process.env.OPENCODE_SERVER_PASSWORD || randomBytes2(24).toString("base64url");
12999
13046
  process.env.OPENCODE_SERVER_PASSWORD = password;
13047
+ const config = opencodeConfig(providerId, configuredModel);
13048
+ const mcp = await readProvisionedOpencodeMcpConfig();
13049
+ if (mcp && Object.keys(mcp).length > 0) config.mcp = mcp;
13000
13050
  const server = await createOpencodeServer({
13001
13051
  port: 0,
13002
13052
  timeout: OPENCODE_SERVER_STARTUP_TIMEOUT_MS,
13003
- config: await opencodeConfig(configuredModel)
13053
+ config
13004
13054
  });
13005
13055
  const client = createOpencodeClient({
13006
13056
  baseUrl: server.url,
@@ -13011,7 +13061,7 @@ var OpencodeManager = class extends CodingAgentManager {
13011
13061
  });
13012
13062
  this.client = client;
13013
13063
  this.server = server;
13014
- this.configuredModels = this.providerId === OPENCODE_GO_PROVIDER ? /* @__PURE__ */ new Set() : new Set(getConfiguredOpencodeModels(model));
13064
+ this.configuredModels = this.providerId === OPENCODE_GO_PROVIDER ? /* @__PURE__ */ new Set() : new Set(getConfiguredOpencodeModels(model, providerId));
13015
13065
  const eventController = new AbortController();
13016
13066
  this.eventAbortController = eventController;
13017
13067
  this.eventSubscriptionReady = new Promise((resolve4) => {
@@ -13075,7 +13125,8 @@ var OpencodeManager = class extends CodingAgentManager {
13075
13125
  return OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL[thinkingLevel].find((variant) => variants.has(variant));
13076
13126
  }
13077
13127
  async processMessageInternal(request) {
13078
- const model = request.model ?? DEFAULT_OPENCODE_MODEL;
13128
+ const provider = await getOpencodeProvider();
13129
+ const model = request.model ?? getDefaultOpencodeModel(provider);
13079
13130
  const controller = new AbortController();
13080
13131
  const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
13081
13132
  const linearForwarder = new LinearEventForwarder(linearSessionId);
@@ -13083,7 +13134,7 @@ var OpencodeManager = class extends CodingAgentManager {
13083
13134
  this.activeLinearForwarder = linearForwarder;
13084
13135
  this.forwardedLinearPartKeys.clear();
13085
13136
  try {
13086
- const client = await this.ensureClient(model);
13137
+ const client = await this.ensureClient(model, provider);
13087
13138
  const providerModel = this.providerId === OPENCODE_GO_PROVIDER ? getOpenCodeGoModel(model) : model;
13088
13139
  const agent = request.planMode ? "plan" : "build";
13089
13140
  const variant = await this.getThinkingVariant(client, providerModel, request.thinkingLevel);
@@ -13431,6 +13482,7 @@ var PiManager = class extends CodingAgentManager {
13431
13482
  activeSessionFile = null;
13432
13483
  historyFilePath;
13433
13484
  historyFile;
13485
+ providerId = "openrouter";
13434
13486
  constructor(options) {
13435
13487
  super(options);
13436
13488
  this.historyFilePath = options.historyFilePath ?? join20(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
@@ -13462,8 +13514,8 @@ var PiManager = class extends CodingAgentManager {
13462
13514
  }
13463
13515
  async enqueueMessage(request) {
13464
13516
  await this.initialized;
13465
- if (!this.session && !await this.getOpenRouterApiKey()) {
13466
- throw new Error("OpenRouter authentication is missing for Pi. Add an OpenRouter API key in Settings \u2192 Coding agents.");
13517
+ if (!this.session && !this.getProviderCredentials()) {
13518
+ throw new Error("Aster or OpenRouter authentication is missing for Pi. Add an API key in Settings \u2192 Coding agents.");
13467
13519
  }
13468
13520
  return this.messageQueue.enqueue(request);
13469
13521
  }
@@ -13476,8 +13528,9 @@ var PiManager = class extends CodingAgentManager {
13476
13528
  async ensureSession(request) {
13477
13529
  if (this.session) {
13478
13530
  if (request.model && request.model !== this.session.model?.id) {
13479
- const model2 = this.session.modelRegistry.find("openrouter", request.model);
13480
- if (model2) await this.session.setModel(model2);
13531
+ const model2 = this.session.modelRegistry.find(this.providerId, request.model);
13532
+ if (!model2) throw new Error(`Pi model is not available through ${this.providerId}: ${request.model}`);
13533
+ await this.session.setModel(model2);
13481
13534
  }
13482
13535
  if (request.thinkingLevel) {
13483
13536
  this.session.setThinkingLevel(request.thinkingLevel === "ultra" || request.thinkingLevel === "ultracode" ? "xhigh" : request.thinkingLevel);
@@ -13485,34 +13538,56 @@ var PiManager = class extends CodingAgentManager {
13485
13538
  return this.session;
13486
13539
  }
13487
13540
  const authStorage = AuthStorage.create(PI_AUTH_PATH);
13488
- const apiKey = await this.getOpenRouterApiKey(authStorage);
13489
- if (!apiKey) throw new Error("OpenRouter API key is not configured for Pi.");
13541
+ const credentials = this.getProviderCredentials(authStorage);
13542
+ if (!credentials) throw new Error("Aster or OpenRouter API key is not configured for Pi.");
13543
+ this.providerId = credentials.provider;
13490
13544
  const modelRegistry = ModelRegistry.create(authStorage);
13491
13545
  const openRouterModels = modelRegistry.getAll().filter((candidate) => candidate.provider === "openrouter");
13492
13546
  const modelTemplate = openRouterModels[0];
13493
- if (!modelTemplate) throw new Error("Pi OpenRouter model catalog is empty.");
13494
- modelRegistry.registerProvider("openrouter", {
13495
- api: modelTemplate.api,
13496
- apiKey,
13497
- baseUrl: modelTemplate.baseUrl,
13498
- models: [
13499
- ...openRouterModels,
13500
- ...OPENROUTER_MODELS.filter((id) => !openRouterModels.some((candidate) => candidate.id === id)).map((id) => ({
13547
+ if (!modelTemplate) throw new Error("Pi OpenAI-compatible model template is unavailable.");
13548
+ if (this.providerId === ASTER_PROVIDER) {
13549
+ modelRegistry.registerProvider(ASTER_PROVIDER, {
13550
+ name: "Aster",
13551
+ api: "openai-completions",
13552
+ apiKey: credentials.apiKey,
13553
+ baseUrl: ASTER_BASE_URL,
13554
+ authHeader: true,
13555
+ models: ASTER_MODELS.map((id) => ({
13501
13556
  id,
13502
- name: id,
13503
- api: modelTemplate.api,
13504
- baseUrl: modelTemplate.baseUrl,
13505
- reasoning: modelTemplate.reasoning,
13506
- input: modelTemplate.input,
13507
- cost: modelTemplate.cost,
13508
- contextWindow: modelTemplate.contextWindow,
13509
- maxTokens: modelTemplate.maxTokens
13557
+ name: ASTER_MODEL_LABELS[id] ?? id,
13558
+ api: "openai-completions",
13559
+ reasoning: true,
13560
+ input: ["text"],
13561
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
13562
+ contextWindow: id === "kimi-k3" ? 1e6 : 131072,
13563
+ maxTokens: 65536
13510
13564
  }))
13511
- ]
13512
- });
13513
- const modelId = request.model ?? DEFAULT_PI_MODEL;
13514
- const model = modelRegistry.find("openrouter", modelId) ?? modelRegistry.find("openrouter", DEFAULT_PI_MODEL);
13515
- if (!model) throw new Error(`Pi model is not available through OpenRouter: ${modelId}`);
13565
+ });
13566
+ } else {
13567
+ modelRegistry.registerProvider("openrouter", {
13568
+ api: modelTemplate.api,
13569
+ apiKey: credentials.apiKey,
13570
+ baseUrl: modelTemplate.baseUrl,
13571
+ models: [
13572
+ ...openRouterModels,
13573
+ ...OPENROUTER_MODELS.filter((id) => !openRouterModels.some((candidate) => candidate.id === id)).map((id) => ({
13574
+ id,
13575
+ name: id,
13576
+ api: modelTemplate.api,
13577
+ baseUrl: modelTemplate.baseUrl,
13578
+ reasoning: modelTemplate.reasoning,
13579
+ input: modelTemplate.input,
13580
+ cost: modelTemplate.cost,
13581
+ contextWindow: modelTemplate.contextWindow,
13582
+ maxTokens: modelTemplate.maxTokens
13583
+ }))
13584
+ ]
13585
+ });
13586
+ }
13587
+ const defaultModel = this.providerId === ASTER_PROVIDER ? DEFAULT_ASTER_MODEL : DEFAULT_PI_MODEL;
13588
+ const modelId = request.model ?? defaultModel;
13589
+ const model = modelRegistry.find(this.providerId, modelId);
13590
+ if (!model) throw new Error(`Pi model is not available through ${this.providerId}: ${modelId}`);
13516
13591
  const sessionManager = this.initialSessionId ? SessionManager.open(this.initialSessionId, PI_HISTORY_DIR, this.workingDirectory) : SessionManager.create(this.workingDirectory, PI_HISTORY_DIR);
13517
13592
  const resourceLoader = new DefaultResourceLoader({
13518
13593
  cwd: this.workingDirectory,
@@ -13538,9 +13613,12 @@ var PiManager = class extends CodingAgentManager {
13538
13613
  this.unsubscribe = this.session.subscribe((event) => this.handleEvent(event));
13539
13614
  return this.session;
13540
13615
  }
13541
- async getOpenRouterApiKey(authStorage = AuthStorage.create(PI_AUTH_PATH)) {
13542
- const credential = authStorage.get("openrouter");
13543
- return credential?.type === "api_key" ? credential.key : void 0;
13616
+ getProviderCredentials(authStorage = AuthStorage.create(PI_AUTH_PATH)) {
13617
+ for (const provider of [ASTER_PROVIDER, "openrouter"]) {
13618
+ const credential = authStorage.get(provider);
13619
+ if (credential?.type === "api_key") return { provider, apiKey: credential.key };
13620
+ }
13621
+ return null;
13544
13622
  }
13545
13623
  handleEvent(event) {
13546
13624
  const payload = eventPayload(event);
@@ -14407,7 +14485,10 @@ async function reconcileCanvasItems(filenames) {
14407
14485
  }
14408
14486
 
14409
14487
  // src/services/upload-chat-transcripts.ts
14410
- import { readdir as readdir7, readFile as readFile13 } from "fs/promises";
14488
+ import { createReadStream } from "fs";
14489
+ import { readdir as readdir7, readFile as readFile13, stat as stat4 } from "fs/promises";
14490
+ import { request as httpRequest } from "http";
14491
+ import { request as httpsRequest } from "https";
14411
14492
  import { basename as basename2, join as join23 } from "path";
14412
14493
 
14413
14494
  // src/services/chat/chat-senders.ts
@@ -14434,6 +14515,34 @@ var HISTORY_DIRS = [
14434
14515
  join23(ENGINE_DIR2, "relay-histories"),
14435
14516
  join23(ENGINE_DIR2, "codex-histories")
14436
14517
  ];
14518
+ async function putTranscript(uploadUrl, filePath, size) {
14519
+ await new Promise((resolve4, reject) => {
14520
+ const url = new URL(uploadUrl);
14521
+ const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
14522
+ method: "PUT",
14523
+ headers: {
14524
+ "content-length": String(size),
14525
+ "content-type": "application/x-ndjson"
14526
+ }
14527
+ }, (response) => {
14528
+ response.setEncoding("utf8");
14529
+ let body = "";
14530
+ response.on("data", (chunk) => {
14531
+ body += chunk;
14532
+ });
14533
+ response.on("end", () => {
14534
+ const status = response.statusCode ?? 0;
14535
+ if (status >= 200 && status < 300) resolve4();
14536
+ else reject(new Error(`upload failed: ${status} ${body}`));
14537
+ });
14538
+ response.on("error", reject);
14539
+ });
14540
+ request.on("error", reject);
14541
+ const file = createReadStream(filePath, { start: 0, end: size - 1 });
14542
+ file.on("error", (error) => request.destroy(error));
14543
+ file.pipe(request);
14544
+ });
14545
+ }
14437
14546
  async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
14438
14547
  let flushed = 0;
14439
14548
  let failed = 0;
@@ -14462,44 +14571,41 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
14462
14571
  return { flushed, failed };
14463
14572
  }
14464
14573
  async function uploadChatTranscript(chatId, filePath, chat) {
14465
- const bytes = await readFile13(filePath);
14466
- if (bytes.byteLength === 0) return;
14467
- let senders;
14574
+ const { size } = await stat4(filePath);
14575
+ if (size === 0) return;
14576
+ const metadata = chat ? {
14577
+ provider: chat.provider,
14578
+ title: chat.title,
14579
+ createdAt: chat.createdAt,
14580
+ updatedAt: chat.updatedAt,
14581
+ parentChatId: chat.parentChatId,
14582
+ deletedAt: chat.deletedAt ?? null
14583
+ } : {};
14468
14584
  try {
14469
- senders = parseChatMessageSendersJsonl(await readFile13(chatMessageSendersFilePath(chatId), "utf-8"));
14585
+ metadata.senders = parseChatMessageSendersJsonl(
14586
+ await readFile13(chatMessageSendersFilePath(chatId), "utf-8")
14587
+ );
14470
14588
  } catch (error) {
14471
14589
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
14472
14590
  }
14473
- const form = new FormData();
14474
- form.append("chat_id", chatId);
14475
- if (chat) {
14476
- const metadata = {
14477
- provider: chat.provider,
14478
- title: chat.title,
14479
- createdAt: chat.createdAt,
14480
- updatedAt: chat.updatedAt,
14481
- parentChatId: chat.parentChatId,
14482
- deletedAt: chat.deletedAt ?? null
14483
- };
14484
- form.append("provider", metadata.provider);
14485
- form.append("title", metadata.title);
14486
- form.append("created_at", metadata.createdAt);
14487
- form.append("updated_at", metadata.updatedAt);
14488
- if (metadata.parentChatId) form.append("parent_chat_id", metadata.parentChatId);
14489
- form.append("deleted_at", metadata.deletedAt ?? "");
14591
+ const uploadRequest = { chatId, size, metadata };
14592
+ const prepareResponse = await monolithRequest("/v1/engine/chat-transcripts/upload-url", {
14593
+ body: uploadRequest
14594
+ });
14595
+ if (!prepareResponse.ok) {
14596
+ throw new Error(`prepare failed: ${prepareResponse.status} ${await prepareResponse.text()}`);
14490
14597
  }
14491
- if (senders !== void 0) {
14492
- form.append("senders", JSON.stringify(senders));
14598
+ const prepareBody = await prepareResponse.json();
14599
+ if (!isRecord(prepareBody) || typeof prepareBody.uploadUrl !== "string" && prepareBody.uploadUrl !== null) {
14600
+ throw new Error("prepare failed: invalid response");
14493
14601
  }
14494
- form.append(
14495
- "file",
14496
- new Blob([new Uint8Array(bytes)], { type: "application/x-ndjson" }),
14497
- "transcript.jsonl"
14498
- );
14499
- const response = await monolithRequest("/v1/engine/chat-transcripts", { body: form });
14500
- if (!response.ok) {
14501
- const errorText = await response.text();
14502
- throw new Error(`upload failed: ${response.status} ${errorText}`);
14602
+ if (prepareBody.uploadUrl === null) return;
14603
+ await putTranscript(prepareBody.uploadUrl, filePath, size);
14604
+ const finalizeResponse = await monolithRequest("/v1/engine/chat-transcripts/finalize", {
14605
+ body: uploadRequest
14606
+ });
14607
+ if (!finalizeResponse.ok) {
14608
+ throw new Error(`finalize failed: ${finalizeResponse.status} ${await finalizeResponse.text()}`);
14503
14609
  }
14504
14610
  }
14505
14611
 
@@ -14537,8 +14643,8 @@ async function flushRepoState() {
14537
14643
  }
14538
14644
 
14539
14645
  // src/services/upload-engine-logs.ts
14540
- import { createReadStream } from "fs";
14541
- import { readdir as readdir8, stat as stat4 } from "fs/promises";
14646
+ import { createReadStream as createReadStream2 } from "fs";
14647
+ import { readdir as readdir8, stat as stat5 } from "fs/promises";
14542
14648
  import { join as join24 } from "path";
14543
14649
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
14544
14650
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
@@ -14569,7 +14675,7 @@ async function flushAllEngineLogs() {
14569
14675
  try {
14570
14676
  const sessionId = filename.slice(0, -".log".length);
14571
14677
  const filePath = join24(LOG_DIR, filename);
14572
- const fileStat = await runBeforeDeadline(() => stat4(filePath), deadline);
14678
+ const fileStat = await runBeforeDeadline(() => stat5(filePath), deadline);
14573
14679
  if (!fileStat.isFile()) {
14574
14680
  skipped++;
14575
14681
  return null;
@@ -14601,7 +14707,7 @@ async function flushAllEngineLogs() {
14601
14707
  const content = await runBeforeDeadline(async (signal) => {
14602
14708
  if (candidate.length === 0) return Buffer.alloc(0);
14603
14709
  const chunks = [];
14604
- for await (const chunk of createReadStream(candidate.filePath, {
14710
+ for await (const chunk of createReadStream2(candidate.filePath, {
14605
14711
  start: candidate.offset,
14606
14712
  end: candidate.offset + candidate.length - 1,
14607
14713
  signal
@@ -15542,7 +15648,7 @@ var ChatService = class {
15542
15648
 
15543
15649
  // src/services/repo-file-service.ts
15544
15650
  import { execFile as execFile2 } from "child_process";
15545
- import { readFile as readFile15, realpath, stat as stat5 } from "fs/promises";
15651
+ import { readFile as readFile15, realpath, stat as stat6 } from "fs/promises";
15546
15652
  import { join as join26, resolve as resolve2, extname as extname2 } from "path";
15547
15653
  var CACHE_TTL_MS = 3e4;
15548
15654
  var SEARCH_TIMEOUT_MS = 15e3;
@@ -15707,7 +15813,7 @@ var RepoFileService = class {
15707
15813
  const repoRoot = await realpath(repo.path);
15708
15814
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
15709
15815
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
15710
- const fileStat = await stat5(fullPath);
15816
+ const fileStat = await stat6(fullPath);
15711
15817
  if (!fileStat.isFile()) return null;
15712
15818
  const sizeBytes = fileStat.size;
15713
15819
  if (isBinaryExtension(filePath)) {
@@ -15810,7 +15916,7 @@ var RepoFileService = class {
15810
15916
  // src/v1-routes.ts
15811
15917
  import { Hono } from "hono";
15812
15918
  import { z as z7 } from "zod";
15813
- import { readdir as readdir10, stat as stat6, readFile as readFile18 } from "fs/promises";
15919
+ import { readdir as readdir10, stat as stat7, readFile as readFile18 } from "fs/promises";
15814
15920
  import { join as join29, resolve as resolve3 } from "path";
15815
15921
 
15816
15922
  // src/services/warm-hooks-service.ts
@@ -17200,7 +17306,7 @@ data: ${JSON.stringify("Terminal session not found")}
17200
17306
  const sessions = await Promise.all(
17201
17307
  logFiles.map(async (filename) => {
17202
17308
  const filePath = join29(LOG_DIR, filename);
17203
- const fileStat = await stat6(filePath);
17309
+ const fileStat = await stat7(filePath);
17204
17310
  const sessionId = filename.replace(/\.log$/, "");
17205
17311
  return {
17206
17312
  sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.519",
3
+ "version": "0.1.522",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",