@fxz26284407/yuanma-gateway 0.1.13 → 0.1.15

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.
@@ -1,3 +1,4 @@
1
+ import { imageReferences } from './media.js';
1
2
  import { createHash } from 'node:crypto';
2
3
  import { basename } from 'node:path';
3
4
  import { requireValue } from './domain.js';
@@ -107,7 +108,7 @@ export function nativeRecords(steps, fallbackTurn = 'agy-context') {
107
108
  }
108
109
  return records;
109
110
  }
110
- export function conversationPage(records, nextCursor, fullReplies = new Map()) {
111
+ export function conversationPage(records, nextCursor, fullReplies = new Map(), threadId = '') {
111
112
  const turns = new Map();
112
113
  for (const { step, turnId } of records)
113
114
  for (const part of stepParts(step)) {
@@ -116,6 +117,8 @@ export function conversationPage(records, nextCursor, fullReplies = new Map()) {
116
117
  const text = fullReplies.get(id) ?? part.text;
117
118
  const truncated = (!reply && text.length > 1000) || (fields.includes(part.part === 'tools' ? 'tool_calls' : part.part) && !fullReplies.has(id));
118
119
  const item = { id, type: part.role === 'user' ? 'userMessage' : reply ? 'agentMessage' : 'commandExecution', role: part.role, text: reply ? text : text.slice(0, 1000), totalChars: text.length, truncated };
120
+ if (reply)
121
+ item.images = imageReferences({ type: 'agentMessage', id, text }, turnId, threadId);
119
122
  item.readId = id + ':' + fingerprint(step);
120
123
  item.groupKey = turnId + ':' + step.step_index;
121
124
  if (part.role === 'tool')
@@ -62,10 +62,22 @@ export class AntigravityNative {
62
62
  await this.connect();
63
63
  const authority = this.#authority;
64
64
  try {
65
- if (method === 'GetAllCascadeTrajectories' && authority.version === '2.31.0') {
66
- const snapshot = await this.readState('JetboxSubscribeToSummaries', {});
67
- requireValue(snapshot.updates && typeof snapshot.updates === 'object' && !Array.isArray(snapshot.updates), 'PROTOCOL_INCOMPATIBLE', '桌面会话订阅格式已变化', 503);
68
- return { trajectorySummaries: snapshot.updates };
65
+ if (method === 'GetAllCascadeTrajectories') {
66
+ try {
67
+ const snapshot = await this.readState('JetboxSubscribeToSummaries', {});
68
+ requireValue(snapshot && typeof snapshot === 'object' && !Array.isArray(snapshot) && (snapshot.updates !== undefined || Object.keys(snapshot).length === 0), 'PROTOCOL_INCOMPATIBLE', '桌面会话订阅格式已变化', 503);
69
+ return { trajectorySummaries: cascadeSummaries(snapshot.updates === undefined ? {} : { trajectorySummaries: snapshot.updates }) };
70
+ }
71
+ catch (error) {
72
+ if (error.code !== 'UNSUPPORTED_METHOD')
73
+ throw error;
74
+ // Older servers may expose only the unary catalog.
75
+ const result = await this.rpc(authority, method, params, timeout, signal);
76
+ cascadeSummaries(result);
77
+ this.ready = true;
78
+ this.#checked = Date.now();
79
+ return result;
80
+ }
69
81
  }
70
82
  const result = await this.rpc(authority, method, params, timeout, signal);
71
83
  if (method === 'GetCascadeTrajectorySteps') {
@@ -114,7 +126,7 @@ export class AntigravityNative {
114
126
  } onEnd(error); };
115
127
  const req = request({ hostname: '127.0.0.1', port: authority.port, path: '/exa.language_server_pb.LanguageServerService/' + method, method: 'POST', rejectUnauthorized: false, headers: { 'content-type': 'application/connect+json', 'connect-protocol-version': '1', 'x-codeium-csrf-token': authority.token } }, res => {
116
128
  if (res.statusCode !== 200) {
117
- finish(new Error('Antigravity 状态订阅不可用(HTTP ' + res.statusCode + ')'));
129
+ finish(Object.assign(new Error('Antigravity 状态订阅不可用(HTTP ' + res.statusCode + ')'), { code: res.statusCode === 404 || res.statusCode === 501 ? 'UNSUPPORTED_METHOD' : 'STREAM_UNAVAILABLE' }));
118
130
  return;
119
131
  }
120
132
  if (!res.headers['content-type']?.startsWith('application/connect+json')) {
@@ -136,7 +148,7 @@ export class AntigravityNative {
136
148
  const value = JSON.parse(buffer.subarray(5, 5 + length).toString('utf8'));
137
149
  buffer = buffer.subarray(5 + length);
138
150
  if (flags === 2) {
139
- finish(value.error ? new Error('Antigravity 状态订阅中断') : undefined);
151
+ finish(value.error ? Object.assign(new Error('Antigravity 状态订阅中断'), { code: value.error.code === 'unimplemented' ? 'UNSUPPORTED_METHOD' : 'STREAM_UNAVAILABLE' }) : undefined);
140
152
  return;
141
153
  }
142
154
  if (flags !== 0)
@@ -5,7 +5,7 @@ import { join, relative, isAbsolute, sep, basename, dirname, resolve } from 'nod
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import { randomUUID } from 'node:crypto';
7
7
  import { platform } from './platform.mjs';
8
- import { imageChunk } from './media.js';
8
+ import { imageChunk, imageSources } from './media.js';
9
9
  import { AntigravityNative, cascadeSummaries } from './antigravity-native.js';
10
10
  import { messageInput, resolveExtensions } from './extensions.js';
11
11
  import { iconSource, iconFields, extensionIconChunk } from './extension-icons.js';
@@ -1028,7 +1028,15 @@ export class AntigravityBackend {
1028
1028
  async mediaChunk(threadId, turnId, itemId, index, offset, version) {
1029
1029
  await this.session(threadId);
1030
1030
  requireValue(Number.isSafeInteger(index) && index >= 0 && index < 8, 'INVALID_REQUEST', '图片索引无效', 400);
1031
- const log = await this.log(threadId), record = log.records.find(r => r.turnId === turnId && partId(r.step, 'content') === itemId);
1031
+ const log = await this.log(threadId), live = this.follows.get(threadId);
1032
+ const matches = (r) => r.turnId === turnId && partId(r.step, 'content') === itemId;
1033
+ const record = nativeRecords(live?.steps ?? new Map(), live?.fallbackTurn).find(matches) ?? log.records.find(matches);
1034
+ if (record?.step.type === 'PLANNER_RESPONSE' && record.step.source === 'MODEL') {
1035
+ const text = await this.historyText(threadId, turnId, itemId);
1036
+ const source = imageSources({ type: 'agentMessage', text })[index];
1037
+ requireValue(source, 'MEDIA_NOT_FOUND', '输出图片不可用', 404);
1038
+ return imageChunk(source, offset, version);
1039
+ }
1032
1040
  requireValue(record && record.step.type === 'USER_INPUT', 'MEDIA_NOT_FOUND', '图片消息已变化,请刷新', 404);
1033
1041
  const raw = await this.desktop.call('GetCascadeTrajectorySteps', { cascadeId: threadId, stepOffset: record.step.step_index, trajectoryVerbosity: 'CLIENT_TRAJECTORY_VERBOSITY_FULL' });
1034
1042
  const media = raw.steps?.[0]?.userInput?.media?.[index];
@@ -1074,7 +1082,7 @@ export class AntigravityBackend {
1074
1082
  fullReplies.set(partId(step, 'content'), reply.text);
1075
1083
  }
1076
1084
  }
1077
- const page = await this.enrichUserContent(conversationPage(records, start ? Buffer.from(JSON.stringify({ threadId, version: log.version, end: start })).toString('base64url') : null, fullReplies), records, threadId);
1085
+ const page = await this.enrichUserContent(conversationPage(records, start ? Buffer.from(JSON.stringify({ threadId, version: log.version, end: start })).toString('base64url') : null, fullReplies, threadId), records, threadId);
1078
1086
  if (this.desktop.ready)
1079
1087
  for (const project of this.projects)
1080
1088
  if (await this.belongs(project, row)) {
@@ -1099,6 +1107,12 @@ export class AntigravityBackend {
1099
1107
  async history(threadId) { return this.mobileHistory(threadId); }
1100
1108
  async historyItem(threadId, turnId, itemId, offset) {
1101
1109
  requireValue(Number.isSafeInteger(offset) && offset >= 0, 'INVALID_REQUEST', 'Invalid text offset', 400);
1110
+ const text = await this.historyText(threadId, turnId, itemId);
1111
+ requireValue(offset <= text.length, 'INVALID_REQUEST', 'Text offset exceeds message', 400);
1112
+ const end = Math.min(text.length, offset + 12000);
1113
+ return { text: text.slice(offset, end), totalChars: text.length, ...(end < text.length ? { nextOffset: end } : {}) };
1114
+ }
1115
+ async historyText(threadId, turnId, itemId) {
1102
1116
  await this.session(threadId);
1103
1117
  const log = await this.log(threadId);
1104
1118
  const versioned = /^(agy:\d+:[a-f0-9]{16}:(?:content|thinking|tools)):([a-f0-9]{16})$/.exec(itemId), stableId = versioned?.[1] ?? itemId;
@@ -1117,9 +1131,7 @@ export class AntigravityBackend {
1117
1131
  requireValue(value, 'ITEM_NOT_FOUND', '没有可读取的全文', 404);
1118
1132
  text = value.text;
1119
1133
  }
1120
- requireValue(offset <= text.length, 'INVALID_REQUEST', 'Text offset exceeds message', 400);
1121
- const end = Math.min(text.length, offset + 12000);
1122
- return { text: text.slice(offset, end), totalChars: text.length, ...(end < text.length ? { nextOffset: end } : {}) };
1134
+ return text;
1123
1135
  }
1124
1136
  close() { for (const id of this.follows.keys())
1125
1137
  this.unfollow(id); this.interactions.clear(); this.diffs.clear(); this.desktop.close(); this.runtimeCache.clear(); this.extensionCache.clear(); this.ready = false; this.logs.clear(); }
@@ -1,5 +1,9 @@
1
+ import { imageSources } from './media.js';
1
2
  import { requireValue } from './domain.js';
2
3
  const blocks = (content) => Array.isArray(content) ? content.filter(b => b.type === 'text').map(b => b.text ?? '').join('\n') : '';
4
+ export function deepseekOutputImages(event) {
5
+ return event.type === 'assistant/message' ? imageSources({ type: 'agentMessage', text: blocks(event.data.message?.content) }) : [];
6
+ }
3
7
  function userText(event) {
4
8
  if (event.data.source?.kind === 'user')
5
9
  return blocks(event.data.content);
@@ -95,10 +99,14 @@ export const deepseekConversation = {
95
99
  const media = e.type === 'tool/call' ? results.get(e.data.callId) : e;
96
100
  const content = media?.type === 'user/message' ? media.data.content : media?.data.message?.content;
97
101
  if (content && view)
98
- view.images = content.filter((b) => b.type === 'image' && b.attachment).map((_b, index) => ({ id: JSON.stringify([threadId, media.seq, index]), threadId, turnId: turn, itemId: 'dsh:' + media.seq, index, label: '图片 ' + (index + 1) }));
102
+ view.images = [...content.filter((b) => b.type === 'image' && b.attachment), ...deepseekOutputImages(media)].slice(0, 8).map((_b, index) => ({ id: JSON.stringify([threadId, media.seq, index]), threadId, turnId: turn, itemId: 'dsh:' + media.seq, index, label: '图片 ' + (index + 1) }));
99
103
  if (!turns.has(turn))
100
104
  turns.set(turn, { id: turn, status: 'recorded', items: [] });
101
105
  turns.get(turn).items.push(...thoughts, ...(view ? [view] : []));
106
+ if (view?.role === 'tool' && view.images?.length) {
107
+ turns.get(turn).items.push({ ...item(media, 'assistant', ''), id: view.id + ':images', images: view.images });
108
+ view.images = [];
109
+ }
102
110
  }
103
111
  return { data: [...turns.values()].reverse(), nextCursor };
104
112
  }
package/dist/deepseek.js CHANGED
@@ -5,11 +5,11 @@ import { homedir } from 'node:os';
5
5
  import { join, relative, isAbsolute, sep } from 'node:path';
6
6
  import { realpath, mkdir } from 'node:fs/promises';
7
7
  import { createHash, randomUUID } from 'node:crypto';
8
- import { deepseekConversation, deepseekToolText } from './deepseek-conversation.js';
8
+ import { deepseekConversation, deepseekToolText, deepseekOutputImages } from './deepseek-conversation.js';
9
9
  import { permissionKey } from './permissions.js';
10
10
  import { agentCommands, checkGoal } from './agent-commands.js';
11
11
  import { digest } from './domain.js';
12
- import { imageMime } from './media.js';
12
+ import { imageMime, imageChunk } from './media.js';
13
13
  import { messageInput, resolveExtensions } from './extensions.js';
14
14
  import { Fault, requireValue } from './domain.js';
15
15
  /** Conversation work uses the desktop Host; auxiliary suggestions use only its installed model SDK. */
@@ -386,12 +386,18 @@ export class DeepSeekBackend {
386
386
  const event = page.records.map((r) => r.event).find((e) => e.seq === seq);
387
387
  requireValue(event && (event.data.turn === undefined || String(event.data.turn) === turnId), 'MEDIA_NOT_FOUND', 'Image event not found', 404);
388
388
  const blocks = event.type === 'user/message' ? event.data.content : event.data.message?.content;
389
- const ref = blocks?.filter((b) => b.type === 'image' && b.attachment)[index]?.attachment;
389
+ const attachments = blocks?.filter((b) => b.type === 'image' && b.attachment) ?? [];
390
+ if (index >= attachments.length) {
391
+ const source = deepseekOutputImages(event)[index - attachments.length];
392
+ requireValue(source, 'MEDIA_NOT_FOUND', 'Image reference not found', 404);
393
+ return imageChunk(source, offset, version);
394
+ }
395
+ const ref = attachments[index]?.attachment;
390
396
  requireValue(ref, 'MEDIA_NOT_FOUND', 'Image reference not found', 404);
391
397
  const key = id + '\0' + ref.attachmentId;
392
398
  let cached = this.mediaCache.get(key);
393
399
  if (!cached) {
394
- const image = await this.call('session/attachment', { request: { sessionId: id, attachmentId: ref.attachmentId } });
400
+ const image = await this.call('session/attachment', { request: { sessionId: id, attachment: ref } });
395
401
  requireValue(typeof image.data === 'string' && image.data.length <= Math.ceil(8 * 1024 * 1024 / 3) * 4, 'MEDIA_TOO_LARGE', '图片超过手机端 8 MiB 限制', 413);
396
402
  const bytes = Buffer.from(image.data, 'base64'), mime = imageMime(bytes);
397
403
  requireValue(bytes.length <= 8 * 1024 * 1024 && mime === image.attachment.mediaType, 'MEDIA_UNSUPPORTED', 'Invalid image attachment', 415);
@@ -615,7 +621,7 @@ export class DeepSeekBackend {
615
621
  requireValue(actual.type === want.type && (want.type !== 'text' || actual.text === want.text), 'AMBIGUOUS_DELIVERY', 'Request ID has different content');
616
622
  if (want.type === 'image') {
617
623
  requireValue(actual.attachment?.mediaType === want.mediaType, 'AMBIGUOUS_DELIVERY', 'Image receipt differs');
618
- const image = await this.call('session/attachment', { request: { sessionId: m.thread_id, attachmentId: actual.attachment.attachmentId } });
624
+ const image = await this.call('session/attachment', { request: { sessionId: m.thread_id, attachment: actual.attachment } });
619
625
  // Harness normalizes admitted images. The exact rpcId above identifies
620
626
  // the submission; verify its immutable stored attachment, not raw-byte equality.
621
627
  const bytes = Buffer.from(image.data, 'base64');
@@ -19,6 +19,8 @@ export function itemText(item) {
19
19
  return (item.content ?? []).map((c) => c.type === 'text' ? userText(c.text ?? '') : ['skill', 'mention'].includes(c.type) ? '@' + c.name : '').filter(Boolean).join('\n');
20
20
  if (item.type === 'imageView' || item.type === 'contextCompaction')
21
21
  return '';
22
+ if (item.type === 'imageGeneration')
23
+ return item.failure?.message ?? '';
22
24
  if (item.type === 'agentMessage')
23
25
  return item.text ?? '';
24
26
  if (item.type === 'commandExecution')
@@ -32,11 +34,12 @@ export function reasoningText(item) {
32
34
  }
33
35
  export function mobileHistoryPage(page, threadId = '') {
34
36
  return { nextCursor: page.nextCursor ?? null, data: page.data.filter((e) => e.item.type !== 'reasoning' || itemText(e.item).trim()).map((e) => {
37
+ const assistant = e.item.type === 'agentMessage' || e.item.type === 'imageGeneration';
35
38
  const text = itemText(e.item), truncated = text.length > 800;
36
39
  const preview = text.slice(0, 800);
37
40
  const question = asyncQuestions(e.turnId, page.data.map((x) => x.item)).find(q => q.sourceItemId === e.item.id);
38
- const base = { role: e.item.type === 'userMessage' ? 'user' : e.item.type === 'agentMessage' ? 'assistant' : 'tool', ...(question ? { asyncQuestion: question } : {}), images: imageReferences(e.item, e.turnId, threadId), id: e.item.id, clientId: e.item.clientId, truncated, totalChars: text.length, ...(e.item.type !== 'userMessage' && e.item.type !== 'agentMessage' ? { execution: executionSummary(e.item) } : {}) };
39
- const item = e.item.type === 'userMessage' ? { ...base, type: 'userMessage', content: [{ type: 'text', text: preview }] } : e.item.type === 'agentMessage' ? { ...base, type: 'agentMessage', text: preview, ...(typeof e.item.phase === 'string' ? { phase: e.item.phase } : {}), ...(typeof e.item.delivery === 'string' ? { delivery: e.item.delivery } : {}) } : { ...base, type: 'commandExecution', text: preview, command: e.item.type === 'commandExecution' ? '工具执行' : e.item.type, aggregatedOutput: preview };
41
+ const base = { role: e.item.type === 'userMessage' ? 'user' : assistant ? 'assistant' : 'tool', ...(question ? { asyncQuestion: question } : {}), images: imageReferences(e.item, e.turnId, threadId), id: e.item.id, clientId: e.item.clientId, truncated, totalChars: text.length, ...(e.item.type !== 'userMessage' && !assistant ? { execution: executionSummary(e.item) } : {}) };
42
+ const item = e.item.type === 'userMessage' ? { ...base, type: 'userMessage', content: [{ type: 'text', text: preview }] } : assistant ? { ...base, type: 'agentMessage', text: preview, ...(typeof e.item.phase === 'string' ? { phase: e.item.phase } : {}), ...(typeof e.item.delivery === 'string' ? { delivery: e.item.delivery } : {}) } : { ...base, type: 'commandExecution', text: preview, command: e.item.type === 'commandExecution' ? '工具执行' : e.item.type, aggregatedOutput: preview };
40
43
  return { id: e.turnId, status: 'recorded', items: [item] };
41
44
  }) };
42
45
  }
@@ -153,7 +153,6 @@ export class MacOsAdapter extends HostAdapter {
153
153
  discover: async () => {
154
154
  const binary = '/Applications/Antigravity.app/Contents/Resources/bin/language_server';
155
155
  const { stdout: version } = await exec('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', '/Applications/Antigravity.app/Contents/Info.plist']);
156
- requireValue(['2.29.0', '2.31.0'].includes(version.trim()), 'PROTOCOL_INCOMPATIBLE', '当前适配 Antigravity App 2.29.0、2.31.0,请核对桌面版本', 503);
157
156
  const { stdout } = await exec('/bin/ps', ['-axo', 'uid,pid,ppid,command'], { maxBuffer: 4 * 1024 * 1024 });
158
157
  const rows = stdout.split('\n').flatMap(line => {
159
158
  const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/.exec(line);
@@ -259,8 +259,7 @@ export class WindowsAdapter extends HostAdapter {
259
259
  requireValue(resolve(parent.path).toLowerCase() === resolve(application).toLowerCase(), 'BACKEND_UNAVAILABLE', '无法核实 Antigravity LS 的桌面父进程', 503);
260
260
  await trustedWindowsExecutable(parent.path);
261
261
  const version = await windowsCommand(`(Get-Item -LiteralPath $d.path).VersionInfo.FileVersion|ConvertTo-Json -Compress`, { path: parent.path });
262
- requireValue(version === '2.29.0', 'PROTOCOL_INCOMPATIBLE', '当前仅适配 Antigravity App 2.29.0,请核对 Windows 桌面版本', 503);
263
- return { pid: desktop.pid, ports: desktop.ports, token: desktop.token };
262
+ return { pid: desktop.pid, ports: desktop.ports, token: desktop.token, version: typeof version === 'string' ? version : undefined };
264
263
  },
265
264
  diff: async (path, original, modified) => {
266
265
  const root = join(this.gatewayHome, 'diff');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fxz26284407/yuanma-gateway",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": "22.x || 26.x"