@blade-hq/agent-client 2610.0.0-beta.27 → 2610.0.0-beta.29
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/README.md +34 -0
- package/dist/blade-client.d.ts +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +108 -7
- package/dist/index.js.map +1 -1
- package/dist/resources/computers.d.ts +63 -0
- package/dist/resources/sessions.d.ts +3 -1
- package/dist/schemas/session.d.ts +3 -1
- package/dist/session/agent-session.d.ts +3 -0
- package/dist/types/socket-events.d.ts +4 -0
- package/package.json +1 -1
- package/public-api.md +65 -2
package/README.md
CHANGED
|
@@ -289,6 +289,40 @@ const body = { model: defaultServiceModel, messages, stream: true }
|
|
|
289
289
|
没问题,直接交给浏览器就指向用户自己的机器了。默认形态是后端透传(密钥本来也不该进浏览器),
|
|
290
290
|
浏览器直连只适合这个地址对浏览器同样可达的场景。
|
|
291
291
|
|
|
292
|
+
### 远程电脑:client.computers
|
|
293
|
+
|
|
294
|
+
一个会话除了自己的运行时,还可以操作用户接入的其他电脑(`blade daemon connect` 接进来的机器)。
|
|
295
|
+
**默认一台都不能用**,要先为这次会话启动它。
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const { computers } = await client.computers.list(sessionId)
|
|
299
|
+
// SessionComputerList → { computers: SessionComputer[] }
|
|
300
|
+
// SessionComputer: { id, label, os, arch, home, workspace, allowed_paths,
|
|
301
|
+
// online, enabled, is_primary, last_seen_at, ... }
|
|
302
|
+
|
|
303
|
+
await client.computers.setEnabled(sessionId, computer.id, true) // 启动
|
|
304
|
+
await client.computers.setEnabled(sessionId, computer.id, false) // 停用
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`online` 和 `enabled` 是**两件独立的事**:前者指那台电脑此刻连着后端,后者指这次会话已经启动了它。
|
|
308
|
+
离线的电脑也能先启动,等它连上就直接可用。`online` 由服务端读心跳时间判定,不做实时探测,
|
|
309
|
+
所以电脑离线时列表照样返回它——你拿得到「它离线了」这个结论。
|
|
310
|
+
|
|
311
|
+
`is_primary` 表示这次会话本身就跑在这台电脑上。这种电脑恒为可用,也不能停用——
|
|
312
|
+
那是会话自己的工作目录所在。
|
|
313
|
+
|
|
314
|
+
做电脑选择器时用这几个纯函数,别自己重算状态:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { canToggleComputer, computerState, sortComputers } from "@blade-hq/agent-client"
|
|
318
|
+
|
|
319
|
+
sortComputers(computers) // 可用的在前,其次在线的,最后按名字
|
|
320
|
+
computerState(computer) // ComputerState: "primary" | "enabled" | "offline" | "idle"
|
|
321
|
+
canToggleComputer(computer) // 主运行时返回 false
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`ComputersResource` 是 `client.computers` 的类型。
|
|
325
|
+
|
|
292
326
|
## AgentSession
|
|
293
327
|
|
|
294
328
|
一个会话的实时状态机。**状态归属实例**:同一页面建多个会话互不干扰。
|
package/dist/blade-client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type LoginOptions, type LoginResult, type TokenStorageMode } from "./au
|
|
|
2
2
|
import { type BladeFetchInit, type HttpMethod } from "./rest";
|
|
3
3
|
import { AuthResource } from "./resources/auth";
|
|
4
4
|
import { HeadlessResource } from "./resources/headless";
|
|
5
|
+
import { ComputersResource } from "./resources/computers";
|
|
5
6
|
import { ModelsResource } from "./resources/models";
|
|
6
7
|
import { SessionsResource } from "./resources/sessions";
|
|
7
8
|
import { SessionHub } from "./session/hub";
|
|
@@ -33,6 +34,7 @@ export declare class BladeClient {
|
|
|
33
34
|
readonly options: BladeClientOptions;
|
|
34
35
|
readonly auth: AuthResource;
|
|
35
36
|
readonly headless: HeadlessResource;
|
|
37
|
+
readonly computers: ComputersResource;
|
|
36
38
|
readonly models: ModelsResource;
|
|
37
39
|
readonly sessions: SessionsResource;
|
|
38
40
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ export type { CommandEnvelope, InboundAction, InboundEnvelope } from "./commands
|
|
|
20
20
|
export { isCommandEnvelope, isInboundEnvelope } from "./commands/protocol";
|
|
21
21
|
export type { AuthResource, ExchangeCodeParams, ExchangeCodeResult, ProvidersResponse, UserInfo, } from "./resources/auth";
|
|
22
22
|
export type { HeadlessResource } from "./resources/headless";
|
|
23
|
+
export { ComputersResource } from "./resources/computers";
|
|
24
|
+
export { canToggleComputer, computerState, sortComputers, } from "./resources/computers";
|
|
25
|
+
export type { ComputerState, SessionComputer, SessionComputerList, } from "./resources/computers";
|
|
23
26
|
export { ModelsResource } from "./resources/models";
|
|
24
27
|
export type { ModelCatalog, ModelOption } from "./resources/models";
|
|
25
28
|
export type { SessionsResource } from "./resources/sessions";
|
package/dist/index.js
CHANGED
|
@@ -424,6 +424,50 @@ function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
|
424
424
|
});
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
// src/resources/computers.ts
|
|
428
|
+
function computerState(computer) {
|
|
429
|
+
if (computer.is_primary) return "primary";
|
|
430
|
+
if (computer.enabled) return "enabled";
|
|
431
|
+
return computer.online ? "idle" : "offline";
|
|
432
|
+
}
|
|
433
|
+
function canToggleComputer(computer) {
|
|
434
|
+
return !computer.is_primary;
|
|
435
|
+
}
|
|
436
|
+
function sortComputers(computers) {
|
|
437
|
+
return [...computers].sort((a, b) => {
|
|
438
|
+
if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
|
|
439
|
+
if (a.online !== b.online) return a.online ? -1 : 1;
|
|
440
|
+
return a.label.localeCompare(b.label);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
var ComputersResource = class {
|
|
444
|
+
constructor(client) {
|
|
445
|
+
this.client = client;
|
|
446
|
+
}
|
|
447
|
+
client;
|
|
448
|
+
/**
|
|
449
|
+
* 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
|
|
450
|
+
* 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
|
|
451
|
+
*/
|
|
452
|
+
list(sessionId) {
|
|
453
|
+
return this.client.json(
|
|
454
|
+
"GET",
|
|
455
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
|
|
460
|
+
* 电脑离线时同样能登记,等它上线就直接可用。
|
|
461
|
+
*/
|
|
462
|
+
setEnabled(sessionId, computer, enabled) {
|
|
463
|
+
return this.client.json(
|
|
464
|
+
"PUT",
|
|
465
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
|
|
466
|
+
{ enabled }
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
427
471
|
// src/resources/models.ts
|
|
428
472
|
var ModelsResource = class {
|
|
429
473
|
constructor(client) {
|
|
@@ -472,6 +516,7 @@ var SessionInfo = type({
|
|
|
472
516
|
"disable_tools?": "string[]",
|
|
473
517
|
"runtime_type?": "string | null",
|
|
474
518
|
"daemon_id?": "string | null",
|
|
519
|
+
"agent_runtime_id?": "string | null",
|
|
475
520
|
"workspace_path?": "string | null",
|
|
476
521
|
"match?": "unknown"
|
|
477
522
|
});
|
|
@@ -1566,7 +1611,10 @@ var ClientProjectionBuilder = class {
|
|
|
1566
1611
|
);
|
|
1567
1612
|
}
|
|
1568
1613
|
}
|
|
1569
|
-
|
|
1614
|
+
const isNativeProviderResponse = typeof payload.provider === "string" && payload.provider !== "";
|
|
1615
|
+
if (state.toolCalls.length === 0 || isNativeProviderResponse && state.toolCalls.every(
|
|
1616
|
+
(toolCall) => toolCall.status !== "pending" && toolCall.status !== "awaiting_answer"
|
|
1617
|
+
)) {
|
|
1570
1618
|
return this.finalize(loopId, "completed");
|
|
1571
1619
|
}
|
|
1572
1620
|
return this.syncPatch(state);
|
|
@@ -1580,7 +1628,8 @@ var ClientProjectionBuilder = class {
|
|
|
1580
1628
|
const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
|
|
1581
1629
|
const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
|
|
1582
1630
|
applyToolResult(state, toolCallId, content, status, durationMs, sl);
|
|
1583
|
-
|
|
1631
|
+
const isNativeProviderResult = typeof payload.provider === "string" && payload.provider !== "";
|
|
1632
|
+
if (!isNativeProviderResult && this.shouldFinalizeAfterToolResults(state)) {
|
|
1584
1633
|
return this.finalize(loopId, "completed");
|
|
1585
1634
|
}
|
|
1586
1635
|
return this.syncPatch(state);
|
|
@@ -1621,6 +1670,11 @@ var ClientProjectionBuilder = class {
|
|
|
1621
1670
|
const toolName = String(payload.tool_name ?? "");
|
|
1622
1671
|
const displayName = this.displayNameResolver(toolName);
|
|
1623
1672
|
upsertToolCall(state, toolCallId, toolName, displayName, "");
|
|
1673
|
+
const relatedToolCallId = String(payload.related_tool_call_id ?? "");
|
|
1674
|
+
if (relatedToolCallId && relatedToolCallId !== toolCallId) {
|
|
1675
|
+
const related = state.toolCalls.find((toolCall) => toolCall.id === relatedToolCallId);
|
|
1676
|
+
if (related) related.status = "awaiting_answer";
|
|
1677
|
+
}
|
|
1624
1678
|
return this.syncPatch(state);
|
|
1625
1679
|
}
|
|
1626
1680
|
onChildPause(state, loopId, payload) {
|
|
@@ -1877,6 +1931,7 @@ function projectHistory(entries) {
|
|
|
1877
1931
|
const latestAssistantByLoop = /* @__PURE__ */ new Map();
|
|
1878
1932
|
const pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1879
1933
|
const answeredToolCallIds = /* @__PURE__ */ new Set();
|
|
1934
|
+
const nativeToolTurns = /* @__PURE__ */ new Set();
|
|
1880
1935
|
let latestRootMessage = null;
|
|
1881
1936
|
let latestRootAssistant = null;
|
|
1882
1937
|
let sequence = 0;
|
|
@@ -1936,10 +1991,27 @@ function projectHistory(entries) {
|
|
|
1936
1991
|
duration_ms: numberOrZero(message._duration_ms),
|
|
1937
1992
|
started_at: typeof entry.timestamp === "string" ? entry.timestamp : void 0
|
|
1938
1993
|
};
|
|
1994
|
+
const isNativeToolEntry = role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.some(
|
|
1995
|
+
(toolCall) => isRecord4(toolCall) && typeof toolCall.provider === "string"
|
|
1996
|
+
);
|
|
1997
|
+
const previousTurn = turns[turns.length - 1];
|
|
1998
|
+
if (role === "assistant" && previousTurn?.role === "assistant" && previousTurn.loop_id === loopId && nativeToolTurns.has(previousTurn)) {
|
|
1999
|
+
previousTurn.blocks.push(...turn.blocks);
|
|
2000
|
+
previousTurn.tool_calls.push(...turn.tool_calls);
|
|
2001
|
+
previousTurn.status = turn.status;
|
|
2002
|
+
previousTurn.model = turn.model ?? previousTurn.model;
|
|
2003
|
+
previousTurn.usage = turn.usage ?? previousTurn.usage;
|
|
2004
|
+
previousTurn.duration_ms = turn.duration_ms || previousTurn.duration_ms;
|
|
2005
|
+
if (turn.started_at) previousTurn.started_at = turn.started_at;
|
|
2006
|
+
if (loopId === "root") latestRootMessage = previousTurn;
|
|
2007
|
+
latestAssistantByLoop.set(loopId, previousTurn);
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
1939
2010
|
if (loopId === "root" && (role === "user" || role === "assistant")) {
|
|
1940
2011
|
latestRootMessage = turn;
|
|
1941
2012
|
}
|
|
1942
2013
|
turns.push(turn);
|
|
2014
|
+
if (isNativeToolEntry) nativeToolTurns.add(turn);
|
|
1943
2015
|
if (role === "assistant") latestAssistantByLoop.set(loopId, turn);
|
|
1944
2016
|
if (loopId === "root") {
|
|
1945
2017
|
if (role === "assistant") latestRootAssistant = turn;
|
|
@@ -1948,6 +2020,21 @@ function projectHistory(entries) {
|
|
|
1948
2020
|
for (const toolCall of toolCalls) toolOwners.set(toolCall.id, turn);
|
|
1949
2021
|
continue;
|
|
1950
2022
|
}
|
|
2023
|
+
if (kind === "tool_result") {
|
|
2024
|
+
const data = entry.data ?? {};
|
|
2025
|
+
const toolCallId = String(data.tool_call_id ?? "");
|
|
2026
|
+
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
2027
|
+
if (owner && toolCallId) {
|
|
2028
|
+
const rawContent = data.content;
|
|
2029
|
+
const content = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent : String(rawContent ?? "");
|
|
2030
|
+
const status = data.is_error === true ? "error" : inferToolStatus(typeof content === "string" ? content : JSON.stringify(content));
|
|
2031
|
+
setToolCallStatus(owner, toolCallId, status, content, null);
|
|
2032
|
+
if (!owner.blocks.some((block) => block.type === "tool_result" && block.tool_call_id === toolCallId)) {
|
|
2033
|
+
owner.blocks.push({ type: "tool_result", content, tool_call_id: toolCallId });
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
1951
2038
|
if (kind === "mode_change") {
|
|
1952
2039
|
turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
|
|
1953
2040
|
} else if (kind === "workspace_change") {
|
|
@@ -2196,7 +2283,8 @@ function toCreateSessionPayload(request) {
|
|
|
2196
2283
|
solution_snapshot_session_id: request.solution_snapshot_session_id ?? null,
|
|
2197
2284
|
compaction_ratio: request.compaction_ratio ?? null,
|
|
2198
2285
|
...request.runtime_type ? { runtime_type: request.runtime_type } : {},
|
|
2199
|
-
...request.daemon_id ? { daemon_id: request.daemon_id } : {}
|
|
2286
|
+
...request.daemon_id ? { daemon_id: request.daemon_id } : {},
|
|
2287
|
+
...request.agent_runtime_id ? { agent_runtime_id: request.agent_runtime_id } : {}
|
|
2200
2288
|
};
|
|
2201
2289
|
}
|
|
2202
2290
|
var builtinSolutionIds = /* @__PURE__ */ new Set([
|
|
@@ -3519,6 +3607,7 @@ var AgentSession = class _AgentSession {
|
|
|
3519
3607
|
replayQueue = Promise.resolve();
|
|
3520
3608
|
pendingReplayMessage = null;
|
|
3521
3609
|
pendingReplayMode;
|
|
3610
|
+
pendingReplayKbIds;
|
|
3522
3611
|
recentChatEndAt = 0;
|
|
3523
3612
|
disposed = false;
|
|
3524
3613
|
getAppContext = null;
|
|
@@ -3687,6 +3776,7 @@ var AgentSession = class _AgentSession {
|
|
|
3687
3776
|
if (!options.replayDecision) {
|
|
3688
3777
|
this.pendingReplayMessage = content;
|
|
3689
3778
|
this.pendingReplayMode = options.mode;
|
|
3779
|
+
this.pendingReplayKbIds = options.kbIds;
|
|
3690
3780
|
}
|
|
3691
3781
|
const payload = {
|
|
3692
3782
|
session_id: this.sessionId,
|
|
@@ -3697,7 +3787,8 @@ var AgentSession = class _AgentSession {
|
|
|
3697
3787
|
thinking_override: options.thinkingOverride,
|
|
3698
3788
|
whatif: options.whatif,
|
|
3699
3789
|
replay_decision: options.replayDecision,
|
|
3700
|
-
app_context: appContext
|
|
3790
|
+
app_context: appContext,
|
|
3791
|
+
kb_ids: options.kbIds && options.kbIds.length > 0 ? options.kbIds : void 0
|
|
3701
3792
|
};
|
|
3702
3793
|
try {
|
|
3703
3794
|
await this.ensureJoined();
|
|
@@ -3733,6 +3824,7 @@ var AgentSession = class _AgentSession {
|
|
|
3733
3824
|
rollbackOptimisticSend(optimisticTurnId, askUserAnswer, optimisticAskAnswer, previousAskAnswer, message) {
|
|
3734
3825
|
this.pendingReplayMessage = null;
|
|
3735
3826
|
this.pendingReplayMode = void 0;
|
|
3827
|
+
this.pendingReplayKbIds = void 0;
|
|
3736
3828
|
this.update((current) => {
|
|
3737
3829
|
let next = optimisticTurnId ? withTurns(
|
|
3738
3830
|
current,
|
|
@@ -3914,7 +4006,7 @@ var AgentSession = class _AgentSession {
|
|
|
3914
4006
|
_handleTurnEvents(events) {
|
|
3915
4007
|
if (!events.length) return;
|
|
3916
4008
|
const terminalChatEndStatus = extractTerminalChatEndStatus(events);
|
|
3917
|
-
if (!terminalChatEndStatus && hasChatRunEvent2(events)) {
|
|
4009
|
+
if (!terminalChatEndStatus && hasChatRunEvent2(events) && this.state.status !== "waiting_for_input") {
|
|
3918
4010
|
this.markRunningIfIdle();
|
|
3919
4011
|
}
|
|
3920
4012
|
if (this.state.turns.length) {
|
|
@@ -3986,8 +4078,10 @@ var AgentSession = class _AgentSession {
|
|
|
3986
4078
|
_handleReplayMismatch(data) {
|
|
3987
4079
|
const message = this.pendingReplayMessage ?? data.actual_message ?? "";
|
|
3988
4080
|
const mode = this.pendingReplayMode;
|
|
4081
|
+
const kbIds = this.pendingReplayKbIds;
|
|
3989
4082
|
this.pendingReplayMessage = null;
|
|
3990
4083
|
this.pendingReplayMode = void 0;
|
|
4084
|
+
this.pendingReplayKbIds = void 0;
|
|
3991
4085
|
const respond = (decision) => {
|
|
3992
4086
|
void this.runtime.emitWithAck(
|
|
3993
4087
|
"chat:send",
|
|
@@ -3995,7 +4089,8 @@ var AgentSession = class _AgentSession {
|
|
|
3995
4089
|
session_id: this.sessionId,
|
|
3996
4090
|
message,
|
|
3997
4091
|
mode,
|
|
3998
|
-
replay_decision: decision
|
|
4092
|
+
replay_decision: decision,
|
|
4093
|
+
kb_ids: kbIds && kbIds.length > 0 ? kbIds : void 0
|
|
3999
4094
|
},
|
|
4000
4095
|
3e4
|
|
4001
4096
|
).then((response) => {
|
|
@@ -4650,7 +4745,7 @@ function resolveAuthToken(options) {
|
|
|
4650
4745
|
|
|
4651
4746
|
// src/version.ts
|
|
4652
4747
|
var SDK_NAME = "agent-client";
|
|
4653
|
-
var SDK_VERSION = true ? "2610.0.0-beta.
|
|
4748
|
+
var SDK_VERSION = true ? "2610.0.0-beta.29" : "1.1.1";
|
|
4654
4749
|
|
|
4655
4750
|
// src/socket.ts
|
|
4656
4751
|
function withSdkIdentity(auth) {
|
|
@@ -4678,6 +4773,7 @@ var BladeClient = class {
|
|
|
4678
4773
|
options;
|
|
4679
4774
|
auth;
|
|
4680
4775
|
headless;
|
|
4776
|
+
computers;
|
|
4681
4777
|
models;
|
|
4682
4778
|
sessions;
|
|
4683
4779
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
|
@@ -4689,6 +4785,7 @@ var BladeClient = class {
|
|
|
4689
4785
|
};
|
|
4690
4786
|
this.auth = new AuthResource(this);
|
|
4691
4787
|
this.headless = new HeadlessResource(this);
|
|
4788
|
+
this.computers = new ComputersResource(this);
|
|
4692
4789
|
this.models = new ModelsResource(this);
|
|
4693
4790
|
this.sessions = new SessionsResource(this);
|
|
4694
4791
|
this.hub = new SessionHub(this);
|
|
@@ -5229,6 +5326,7 @@ export {
|
|
|
5229
5326
|
BladeApiError,
|
|
5230
5327
|
BladeClient,
|
|
5231
5328
|
ClientProjectionBuilder,
|
|
5329
|
+
ComputersResource,
|
|
5232
5330
|
DEFAULT_REPLAY_SPEED,
|
|
5233
5331
|
EMPTY_PLATFORM_ENDPOINTS,
|
|
5234
5332
|
LayoutType,
|
|
@@ -5243,6 +5341,8 @@ export {
|
|
|
5243
5341
|
TaskStatus,
|
|
5244
5342
|
acceptedPostChatFollowupCompletesLatestRun,
|
|
5245
5343
|
buildMessageContent,
|
|
5344
|
+
canToggleComputer,
|
|
5345
|
+
computerState,
|
|
5246
5346
|
connectEmbedded,
|
|
5247
5347
|
contentPreview,
|
|
5248
5348
|
contextProjectionData,
|
|
@@ -5262,6 +5362,7 @@ export {
|
|
|
5262
5362
|
normalizeMessageContent,
|
|
5263
5363
|
reconcileOptimisticUserTurns,
|
|
5264
5364
|
resolveServiceUrl,
|
|
5365
|
+
sortComputers,
|
|
5265
5366
|
toReplaySnapshot,
|
|
5266
5367
|
transformSlashCommand
|
|
5267
5368
|
};
|