@bash0816/copilot-termux 1.0.63

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.
@@ -0,0 +1,69 @@
1
+ Copyright (c) 2012-2015, Christopher Jeffrey (https://github.com/chjj/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
21
+
22
+
23
+ The MIT License (MIT)
24
+
25
+ Copyright (c) 2016, Daniel Imms (http://www.growingwiththeweb.com)
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining a copy
28
+ of this software and associated documentation files (the "Software"), to deal
29
+ in the Software without restriction, including without limitation the rights
30
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31
+ copies of the Software, and to permit persons to whom the Software is
32
+ furnished to do so, subject to the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43
+ SOFTWARE.
44
+
45
+
46
+
47
+ MIT License
48
+
49
+ Copyright (c) 2018 - present Microsoft Corporation
50
+
51
+ All rights reserved.
52
+
53
+ Permission is hereby granted, free of charge, to any person obtaining a copy
54
+ of this software and associated documentation files (the "Software"), to deal
55
+ in the Software without restriction, including without limitation the rights
56
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
57
+ copies of the Software, and to permit persons to whom the Software is
58
+ furnished to do so, subject to the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be included in all
61
+ copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
64
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
65
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
66
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
67
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
68
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
69
+ SOFTWARE.
@@ -0,0 +1,66 @@
1
+ # Third-Party Notices
2
+
3
+ This package includes third-party software with the following licenses.
4
+
5
+ ---
6
+
7
+ ## GitHub Copilot CLI
8
+
9
+ - Source: https://github.com/github/copilot-cli (private)
10
+ - Version: 1.0.63
11
+ - License: GitHub Copilot CLI License
12
+ - License text: THIRD-PARTY-LICENSES/COPILOT-LICENSE.md
13
+
14
+ Redistributed in unmodified form as part of this package, in accordance with
15
+ Section 2 of the GitHub Copilot CLI License.
16
+
17
+ Files: lib/copilot/
18
+
19
+ ---
20
+
21
+ ## Node.js
22
+
23
+ - Source: https://nodejs.org/
24
+ - License: MIT (main), with bundled components under various licenses
25
+ (see THIRD-PARTY-LICENSES/NODE-LICENSE.txt for full attribution)
26
+ - License text: THIRD-PARTY-LICENSES/NODE-LICENSE.txt
27
+
28
+ Files: lib/node
29
+
30
+ ---
31
+
32
+ ## GNU C Library (glibc)
33
+
34
+ - Source: https://www.gnu.org/software/libc/
35
+ - License: LGPL-2.1+
36
+ - License text: THIRD-PARTY-LICENSES/LGPL-2.1.txt
37
+
38
+ Files:
39
+ lib/glibc/ld-linux-aarch64.so.1
40
+ lib/glibc/libc.so.6
41
+ lib/glibc/libm.so.6
42
+
43
+ ---
44
+
45
+ ## GCC Runtime Libraries
46
+
47
+ - Source: https://gcc.gnu.org/
48
+ - License: GPL-3.0 WITH GCC Runtime Library Exception
49
+ - Exception text: THIRD-PARTY-LICENSES/GCC-RUNTIME-EXCEPTION.txt
50
+ - Base license text: THIRD-PARTY-LICENSES/GPL-3.0.txt
51
+
52
+ Files:
53
+ lib/glibc/libgcc_s.so.1
54
+ lib/glibc/libstdc++.so.6
55
+
56
+ ---
57
+
58
+ ## node-pty
59
+
60
+ - Source: https://github.com/microsoft/node-pty
61
+ - Version: 1.1.0
62
+ - License: MIT
63
+ - License text: THIRD-PARTY-LICENSES/NODE-PTY-LICENSE.txt
64
+
65
+ Compiled natively for Android aarch64 (bionic) and bundled as `lib/native/pty.node`.
66
+ Used to provide pseudo-terminal support for the interactive TUI mode of @github/copilot.
package/bin/copilot ADDED
@@ -0,0 +1,70 @@
1
+ #!/bin/sh
2
+ set -eu
3
+ SELF="$0"
4
+ while [ -L "${SELF}" ]; do
5
+ DIR=$(CDPATH= cd -- "$(dirname -- "${SELF}")" && pwd)
6
+ SELF="$(readlink "${SELF}")"
7
+ case "${SELF}" in /*) ;; *) SELF="${DIR}/${SELF}" ;; esac
8
+ done
9
+ PACKAGE_DIR=$(CDPATH= cd -- "$(dirname -- "${SELF}")/.." && pwd)
10
+ _COMPAT="${PACKAGE_DIR}/lib/bionic-compat.so"
11
+ [ -f "${_COMPAT}" ] || { echo '[copilot-termux] ERROR: lib/bionic-compat.so missing. Reinstall package.' >&2; exit 1; }
12
+ PRELOAD="${_COMPAT}${LD_PRELOAD:+:${LD_PRELOAD}}"
13
+
14
+ CACHE_DIR="${HOME}/.copilot-termux"
15
+ CURRENT="${CACHE_DIR}/current"
16
+
17
+ if [ ! -d "${CURRENT}" ]; then
18
+ echo "[copilot-termux] Not set up. Run: copilot-termux setup" >&2
19
+ exit 1
20
+ fi
21
+
22
+ COPILOT_INDEX="${CURRENT}/index.js"
23
+ if [ ! -f "${COPILOT_INDEX}" ]; then
24
+ echo "[copilot-termux] Installation broken. Run: copilot-termux setup" >&2
25
+ exit 1
26
+ fi
27
+
28
+ MXC_WRAP="${CACHE_DIR}/mxc-wrap"
29
+ if [ -d "${MXC_WRAP}/arm64" ] && [ -x "${MXC_WRAP}/arm64/lxc-exec" ]; then
30
+ export MXC_BIN_DIR="${MXC_WRAP}"
31
+ fi
32
+
33
+ if [ -n "${MAGI_NODE:-}" ]; then
34
+ MAGI_NODE_VERSION="$("${MAGI_NODE}" --version 2>/dev/null || true)"
35
+ MAGI_NODE_MAJOR=""
36
+ if [ -n "${MAGI_NODE_VERSION}" ]; then
37
+ MAGI_NODE_VERSION="${MAGI_NODE_VERSION#v}"
38
+ MAGI_NODE_MAJOR="${MAGI_NODE_VERSION%%.*}"
39
+ fi
40
+
41
+ case "${MAGI_NODE_MAJOR}" in
42
+ ''|*[!0-9]*)
43
+ echo "[copilot-termux] MAGI_NODE version check failed; falling back to node" >&2
44
+ LD_PRELOAD="${PRELOAD}" exec node \
45
+ --require "${PACKAGE_DIR}/lib/platform-patch.js" \
46
+ "${COPILOT_INDEX}" \
47
+ "$@"
48
+ ;;
49
+ esac
50
+
51
+ if [ "${MAGI_NODE_MAJOR}" -lt 24 ]; then
52
+ echo "[copilot-termux] MAGI_NODE ${MAGI_NODE_VERSION} is below v24; falling back to node" >&2
53
+ LD_PRELOAD="${PRELOAD}" exec node \
54
+ --require "${PACKAGE_DIR}/lib/platform-patch.js" \
55
+ "${COPILOT_INDEX}" \
56
+ "$@"
57
+ fi
58
+
59
+ exec env -u LD_LIBRARY_PATH \
60
+ LD_PRELOAD="${PRELOAD}" \
61
+ "${MAGI_NODE}" \
62
+ --require "${PACKAGE_DIR}/lib/platform-patch.js" \
63
+ "${COPILOT_INDEX}" \
64
+ "$@"
65
+ fi
66
+
67
+ LD_PRELOAD="${PRELOAD}" exec node \
68
+ --require "${PACKAGE_DIR}/lib/platform-patch.js" \
69
+ "${COPILOT_INDEX}" \
70
+ "$@"
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const { setup } = require('../lib/setup');
4
+ const cmd = process.argv[2];
5
+ if (cmd === 'setup' || cmd === 'update') {
6
+ setup().then(() => process.exit(0)).catch(e => { console.error(e.message); process.exit(1); });
7
+ } else {
8
+ console.log('Usage: copilot-termux <setup|update>');
9
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "wrapperVersion": "2.0.0",
3
+ "copilot": {
4
+ "version": "1.0.63",
5
+ "integrity": "sha512-e8DRYiWJQc4kepVXsXjC8vpDU2FXS/TfR+Z6p/KAojfcwIUZzKMAfCV5D1lD25hV4CryVH1Z9t7mHqChickj0Q=="
6
+ }
7
+ }
Binary file
Binary file
@@ -0,0 +1,451 @@
1
+ 'use strict';
2
+ // Override process.platform so @github/copilot treats Android as Linux.
3
+ // @github/copilot is distributed unmodified (LICENSE Section 2).
4
+ // This file is our code and is NOT part of the @github/copilot software.
5
+ Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
6
+
7
+ if (process.report) {
8
+ const orig = process.report.getReport.bind(process.report);
9
+ process.report.getReport = (...args) => {
10
+ const r = orig(...args);
11
+ if (r && r.header) r.header.platform = 'linux';
12
+ return r;
13
+ };
14
+ }
15
+
16
+ // Redirect linux-arm64 native addons to bionic-compatible variants.
17
+ // bionic Node.js cannot load glibc addons; glibc fallback causes segfault.
18
+ const Module = require('module');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ // Bundled Termux-native pty.node (built against bionic, not glibc).
23
+ const NATIVE_PTY = path.join(__dirname, 'native', 'pty.node');
24
+
25
+ const origResolve = Module._resolveFilename.bind(Module);
26
+ Module._resolveFilename = function (request, parent, isMain, options) {
27
+ if (typeof request === 'string' && request.endsWith('.node')) {
28
+ // Redirect pty.node to our bundled Termux-native build.
29
+ if (path.basename(request) === 'pty.node' &&
30
+ (request.includes('linux-arm64') || request.includes('linuxmusl-arm64'))) {
31
+ if (fs.existsSync(NATIVE_PTY)) return NATIVE_PTY;
32
+ }
33
+
34
+ // Redirect other linux-arm64 addons to linuxmusl-arm64 variants.
35
+ if (request.includes('linux-arm64')) {
36
+ const muslReq = request.replace(/linux-arm64/g, 'linuxmusl-arm64');
37
+ try {
38
+ const resolved = origResolve(muslReq, parent, isMain, options);
39
+ if (fs.existsSync(resolved)) return resolved;
40
+ } catch (e) {
41
+ if (e.code !== 'MODULE_NOT_FOUND') throw e;
42
+ }
43
+ // Block glibc fallback — loading linux-arm64 (glibc) on bionic causes segfault.
44
+ throw new Error(
45
+ `[copilot-termux] Cannot load glibc addon on bionic: ${request}\n` +
46
+ 'No linuxmusl-arm64 variant available. This addon is not supported on Android.'
47
+ );
48
+ }
49
+ }
50
+ return origResolve(request, parent, isMain, options);
51
+ };
52
+
53
+ // [Android bionic 対応] linuxmusl-arm64/runtime.node は Rust tokio を使う。
54
+ // bionic 上で musl pthread ABI でスレッドを生成すると TUI 起動時に SIGSEGV。
55
+ // runtime.node ロード後に Rust async 初期化関数を no-op に差し替えて阻止する。
56
+ const origLoad = Module._load.bind(Module);
57
+ Module._load = function (request, parent, isMain) {
58
+ const result = origLoad(request, parent, isMain);
59
+ if (typeof request === 'string' &&
60
+ path.basename(request) === 'runtime.node') {
61
+ // Rust tokio を使う関数群を no-op に差し替え。
62
+ // sessionStore*/sessionSqlite* は非同期 SQLite (tokio)、
63
+ // modelHttp*/networkFetch*/ahpRelay*/websocketResponses* は Rust HTTP (tokio)。
64
+ // featureFlagService* は同期 Rust のため除外(no-op にすると .handle クラッシュ)。
65
+ const TOKIO_PATTERN = /^(modelHttp|networkFetch|ahpRelay|websocketResponses|sessionStore|sessionSqlite)/;
66
+ for (const key of Object.keys(result)) {
67
+ if (TOKIO_PATTERN.test(key) && typeof result[key] === 'function') {
68
+ result[key] = () => undefined;
69
+ }
70
+ }
71
+ if (typeof result.networkFetchGetExtraCaPems === 'function') {
72
+ result.networkFetchGetExtraCaPems = () => ({ errors: [], pems: [] });
73
+ }
74
+ // capiClientListModels を Node.js fetch で実装(Rust tokio SIGSEGV 回避)
75
+ if (typeof result.capiClientListModels === 'function') {
76
+ result.capiClientListModels = async function(handle, _includeHidden, _skipCache, _applyModelLimitCaps, _networkingConfigId) {
77
+ let authHeaders;
78
+ try {
79
+ const prepared = result.capiClientPrepareRequestHeaders(handle, '', []);
80
+ authHeaders = {};
81
+ for (const {name, value} of prepared.headers) {
82
+ authHeaders[name] = value;
83
+ }
84
+ } catch (e) {
85
+ throw new Error(JSON.stringify({kind: 'network', message: `prepareHeaders failed: ${e.message}`}));
86
+ }
87
+ const baseUrl = 'https://api.githubcopilot.com';
88
+ let res;
89
+ try {
90
+ res = await globalThis.fetch(`${baseUrl}/models`, {method: 'GET', headers: authHeaders});
91
+ } catch (e) {
92
+ throw new Error(JSON.stringify({kind: 'network', message: e.message}));
93
+ }
94
+ if (!res.ok) {
95
+ const body = await res.text().catch(() => '');
96
+ const hdrs = [...res.headers.entries()].map(([name, value]) => ({name, value}));
97
+ throw new Error(JSON.stringify({kind: 'http', status: res.status, statusText: res.statusText, body, headers: hdrs, hasRequestId: res.headers.has('x-request-id')}));
98
+ }
99
+ const data = await res.json();
100
+ const models = Array.isArray(data) ? data : (data.data ?? data.models ?? []);
101
+ const rateHeaders = [...res.headers.entries()].map(([name, value]) => ({name, value}));
102
+ return {modelsJson: JSON.stringify(models), copilotUrl: baseUrl, usageRatelimitHeaders: rateHeaders, capturedAssignmentContext: undefined};
103
+ };
104
+ }
105
+ if (typeof result.sessionSqliteOpen === 'function') {
106
+ result.sessionSqliteOpen = () => 1;
107
+ }
108
+ if (typeof result.sessionSqliteQuery === 'function') {
109
+ result.sessionSqliteQuery = () => ({ rows: '[]' });
110
+ }
111
+ if (typeof result.sessionSqliteRun === 'function') {
112
+ result.sessionSqliteRun = () => ({ rowsAffected: 0, lastInsertRowid: null });
113
+ }
114
+ if (typeof result.sessionSqliteFileExists === 'function') {
115
+ result.sessionSqliteFileExists = () => false;
116
+ }
117
+ // --- JS networkFetch* implementation (bionic: tokio networkFetch* are no-op'd) ---
118
+ // B7 が QXe() から直接呼ばれる MCP 専用パスでクラッシュするため JS で代替。
119
+ // networkFetchStreamStart → { requestId, response: Promise<{handle,url,status,statusText,headers}> }
120
+ // networkFetchStreamRead → Promise<{ done, body?: Uint8Array }>
121
+ // networkFetchStreamClose → void
122
+ // networkFetchRequestCancel → void
123
+ const _nfMap = new Map();
124
+ let _nfIdSeq = 3e6;
125
+
126
+ result.networkFetchStreamStart = function(req) {
127
+ const requestId = 'nf-' + (++_nfIdSeq);
128
+ const abortCtrl = new AbortController();
129
+ const entry = { abort: () => abortCtrl.abort(), reader: null };
130
+ _nfMap.set(requestId, entry);
131
+
132
+ const headers = {};
133
+ if (Array.isArray(req.headers)) {
134
+ for (const { name, value } of req.headers) headers[name] = value;
135
+ } else if (req.headers && typeof req.headers === 'object') {
136
+ Object.assign(headers, req.headers);
137
+ }
138
+
139
+ const response = (async () => {
140
+ let res;
141
+ try {
142
+ res = await globalThis.fetch(req.url, {
143
+ method: req.method || 'GET',
144
+ headers,
145
+ body: req.body ?? undefined,
146
+ signal: abortCtrl.signal,
147
+ redirect: req.redirect || 'follow',
148
+ });
149
+ } catch (e) {
150
+ _nfMap.delete(requestId);
151
+ throw e;
152
+ }
153
+ const handle = requestId;
154
+ const reader = res.body ? res.body.getReader() : null;
155
+ entry.reader = reader;
156
+ const respHeaders = [...res.headers.entries()].map(([name, value]) => ({ name, value }));
157
+ return { handle, url: res.url, status: res.status, statusText: res.statusText, headers: respHeaders };
158
+ })();
159
+
160
+ return { requestId, response };
161
+ };
162
+
163
+ result.networkFetchStreamRead = function(handle) {
164
+ return (async () => {
165
+ const entry = _nfMap.get(handle);
166
+ if (!entry || !entry.reader) return { done: true };
167
+ try {
168
+ const { done, value } = await entry.reader.read();
169
+ if (done) { _nfMap.delete(handle); return { done: true }; }
170
+ return { done: false, body: value };
171
+ } catch (e) {
172
+ _nfMap.delete(handle);
173
+ throw e;
174
+ }
175
+ })();
176
+ };
177
+
178
+ result.networkFetchStreamClose = function(handle) {
179
+ const entry = _nfMap.get(handle);
180
+ if (entry) {
181
+ try { entry.reader?.cancel(); } catch (_) {}
182
+ try { entry.abort(); } catch (_) {}
183
+ _nfMap.delete(handle);
184
+ }
185
+ };
186
+
187
+ result.networkFetchRequestCancel = function(requestId) {
188
+ const entry = _nfMap.get(requestId);
189
+ if (entry) {
190
+ try { entry.abort(); } catch (_) {}
191
+ _nfMap.delete(requestId);
192
+ }
193
+ };
194
+
195
+ result.networkFetchResetClients = function() {
196
+ for (const entry of _nfMap.values()) {
197
+ try { entry.abort(); } catch (_) {}
198
+ }
199
+ _nfMap.clear();
200
+ };
201
+ // --- end JS networkFetch* implementation ---
202
+ // --- JS model HTTP implementation (bionic: tokio modelHttp* are no-op'd) ---
203
+ // Stream store: streamId → {events, index, finalMessage}
204
+ const _jsStreams = new Map();
205
+ let _accIdSeq = 1e6;
206
+ // Accumulator store: accId → {message}
207
+ const _jsAccs = new Map();
208
+
209
+ // Helper: parse Anthropic SSE body into events array
210
+ function _parseAnthropicSSE(bodyText) {
211
+ const events = [];
212
+ const lines = bodyText.split(/\r\n|\r|\n/);
213
+ let dataLines = [];
214
+ for (const line of lines) {
215
+ if (line === '' || line === '\r') {
216
+ if (dataLines.length > 0) {
217
+ const dataStr = dataLines.join('\n');
218
+ dataLines = [];
219
+ if (dataStr.trim() === '[DONE]') continue;
220
+ try { events.push(JSON.parse(dataStr)); } catch(_) {}
221
+ }
222
+ } else if (line.startsWith('data: ')) {
223
+ dataLines.push(line.slice(6));
224
+ } else if (line.startsWith('data:')) {
225
+ dataLines.push(line.slice(5));
226
+ }
227
+ }
228
+ if (dataLines.length > 0) {
229
+ const dataStr = dataLines.join('\n');
230
+ if (dataStr.trim() !== '[DONE]') {
231
+ try { events.push(JSON.parse(dataStr)); } catch(_) {}
232
+ }
233
+ }
234
+ return events;
235
+ }
236
+
237
+ // Helper: reconstruct final Anthropic message from SSE events
238
+ function _reconstructFinalMessage(events) {
239
+ let message = null;
240
+ const contentBlocks = new Map(); // index → block
241
+ for (const e of events) {
242
+ if (e.type === 'message_start') {
243
+ message = {
244
+ id: e.message && e.message.id,
245
+ type: (e.message && e.message.type) || 'message',
246
+ role: (e.message && e.message.role) || 'assistant',
247
+ model: e.message && e.message.model,
248
+ stop_reason: (e.message && e.message.stop_reason) || null,
249
+ stop_sequence: (e.message && e.message.stop_sequence) || null,
250
+ usage: (e.message && e.message.usage) || { input_tokens: 0, output_tokens: 0 },
251
+ content: []
252
+ };
253
+ } else if (e.type === 'content_block_start') {
254
+ const blk = e.content_block;
255
+ if (!blk) continue;
256
+ if (blk.type === 'text') {
257
+ contentBlocks.set(e.index, { type: 'text', text: blk.text || '' });
258
+ } else if (blk.type === 'tool_use') {
259
+ contentBlocks.set(e.index, { type: 'tool_use', id: blk.id, name: blk.name, _inputBuf: '' });
260
+ } else if (blk.type === 'thinking') {
261
+ contentBlocks.set(e.index, { type: 'thinking', thinking: blk.thinking || '', signature: blk.signature || '' });
262
+ } else if (blk.type === 'redacted_thinking') {
263
+ contentBlocks.set(e.index, { type: 'redacted_thinking', data: blk.data });
264
+ }
265
+ } else if (e.type === 'content_block_delta') {
266
+ const blk = contentBlocks.get(e.index);
267
+ if (!blk || !e.delta) continue;
268
+ if (e.delta.type === 'text_delta') {
269
+ blk.text = (blk.text || '') + (e.delta.text || '');
270
+ } else if (e.delta.type === 'input_json_delta') {
271
+ blk._inputBuf = (blk._inputBuf || '') + (e.delta.partial_json || '');
272
+ } else if (e.delta.type === 'thinking_delta') {
273
+ blk.thinking = (blk.thinking || '') + (e.delta.thinking || '');
274
+ } else if (e.delta.type === 'signature_delta') {
275
+ blk.signature = (blk.signature || '') + (e.delta.signature || '');
276
+ }
277
+ } else if (e.type === 'content_block_stop') {
278
+ const blk = contentBlocks.get(e.index);
279
+ if (blk && blk.type === 'tool_use') {
280
+ try { blk.input = JSON.parse(blk._inputBuf || '{}'); } catch(_) { blk.input = {}; }
281
+ delete blk._inputBuf;
282
+ }
283
+ } else if (e.type === 'message_delta') {
284
+ if (message) {
285
+ if (e.delta && e.delta.stop_reason != null) message.stop_reason = e.delta.stop_reason;
286
+ if (e.delta && e.delta.stop_sequence != null) message.stop_sequence = e.delta.stop_sequence;
287
+ if (e.usage && e.usage.output_tokens != null && message.usage) {
288
+ message.usage.output_tokens = e.usage.output_tokens;
289
+ }
290
+ }
291
+ }
292
+ }
293
+ if (message) {
294
+ const blocks = Array.from(contentBlocks.entries())
295
+ .sort(([a], [b]) => a - b)
296
+ .map(([, v]) => v);
297
+ message.content = blocks;
298
+ }
299
+ if (!message) {
300
+ message = { role: 'assistant', content: [], stop_reason: 'error', stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } };
301
+ }
302
+ return message;
303
+ }
304
+
305
+ // 1. anthropicMessageStreamAccumulatorCreate → JS mock
306
+ if (typeof result.anthropicMessageStreamAccumulatorCreate === 'function') {
307
+ result.anthropicMessageStreamAccumulatorCreate = function(_handleJson) {
308
+ const id = _accIdSeq++;
309
+ _jsAccs.set(id, { message: null });
310
+ return { json: JSON.stringify({ accumulatorId: id }) };
311
+ };
312
+ }
313
+
314
+ // 2. anthropicMessageStreamAccumulatorFinish → return accumulated message
315
+ if (typeof result.anthropicMessageStreamAccumulatorFinish === 'function') {
316
+ result.anthropicMessageStreamAccumulatorFinish = function(id) {
317
+ const acc = _jsAccs.get(id) || { message: null };
318
+ _jsAccs.delete(id);
319
+ return { json: JSON.stringify({ message: acc.message }) };
320
+ };
321
+ }
322
+
323
+ // 3. anthropicMessageStreamAccumulatorDrop → cleanup
324
+ if (typeof result.anthropicMessageStreamAccumulatorDrop === 'function') {
325
+ result.anthropicMessageStreamAccumulatorDrop = function(id) {
326
+ _jsAccs.delete(id);
327
+ };
328
+ }
329
+
330
+ // 4. modelHttpStreamStart → fetch full SSE body, parse events
331
+ result.modelHttpStreamStart = async function(jsonArg) {
332
+ const req = JSON.parse(jsonArg);
333
+ let body = req.body;
334
+ if (body !== null && body !== undefined && typeof body === 'object') {
335
+ if (body.type === 'Buffer' && Array.isArray(body.data)) {
336
+ body = Buffer.from(body.data);
337
+ } else {
338
+ body = JSON.stringify(body);
339
+ }
340
+ }
341
+ let res;
342
+ try {
343
+ res = await globalThis.fetch(req.url, {
344
+ method: req.method || 'POST',
345
+ headers: req.headers || {},
346
+ body: body,
347
+ });
348
+ } catch(e) {
349
+ throw new Error(JSON.stringify({ kind: 'network', message: e.message }));
350
+ }
351
+ const headers = {};
352
+ for (const [k, v] of res.headers.entries()) headers[k] = v;
353
+ const bodyText = await res.text().catch(() => '');
354
+ if (res.status < 200 || res.status >= 300) {
355
+ return { json: JSON.stringify({ bodyText, status: res.status, statusText: res.statusText, headers, streamId: null }) };
356
+ }
357
+ const events = _parseAnthropicSSE(bodyText);
358
+ const finalMessage = _reconstructFinalMessage(events);
359
+ const streamId = 'js-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);
360
+ _jsStreams.set(streamId, { events, index: 0, finalMessage });
361
+ return { json: JSON.stringify({ bodyText: null, status: res.status, statusText: res.statusText, headers, streamId }) };
362
+ };
363
+
364
+ // 5. modelHttpStreamNextAnthropicMessageEvent → yield events one by one
365
+ result.modelHttpStreamNextAnthropicMessageEvent = async function(streamId, accId) {
366
+ const st = _jsStreams.get(streamId);
367
+ if (!st || st.index >= st.events.length) {
368
+ if (st) {
369
+ const acc = _jsAccs.get(accId);
370
+ if (acc) acc.message = st.finalMessage;
371
+ _jsStreams.delete(streamId);
372
+ }
373
+ return null;
374
+ }
375
+ return { json: JSON.stringify({ kind: 'ok', processResult: { event: st.events[st.index++] } }) };
376
+ };
377
+
378
+ // 6. modelHttpStreamCancel → cleanup
379
+ result.modelHttpStreamCancel = function(streamId) {
380
+ _jsStreams.delete(streamId);
381
+ };
382
+
383
+ // 7. modelHttpRequest → non-streaming fetch
384
+ result.modelHttpRequest = async function(jsonArg) {
385
+ const req = JSON.parse(jsonArg);
386
+ let body = req.body;
387
+ if (body !== null && body !== undefined && typeof body === 'object') {
388
+ if (body.type === 'Buffer' && Array.isArray(body.data)) {
389
+ body = Buffer.from(body.data);
390
+ } else {
391
+ body = JSON.stringify(body);
392
+ }
393
+ }
394
+ let res;
395
+ try {
396
+ res = await globalThis.fetch(req.url, {
397
+ method: req.method || 'POST',
398
+ headers: req.headers || {},
399
+ body: body,
400
+ });
401
+ } catch(e) {
402
+ throw new Error(JSON.stringify({ kind: 'network', message: e.message }));
403
+ }
404
+ const headers = {};
405
+ for (const [k, v] of res.headers.entries()) headers[k] = v;
406
+ const bodyText = await res.text().catch(() => '');
407
+ return { json: JSON.stringify({ bodyText, status: res.status, statusText: res.statusText, headers }) };
408
+ };
409
+ // 8. responsesStreamDrive → JS streamをネイティブリデューサーに流す
410
+ // native responsesStreamDrive は Rust tokio でストリームを読む。
411
+ // JS streamId("js-"で始まる)はRust側にないためクラッシュ → JS実装で代替。
412
+ const _nativeReducerProcessEvent = typeof result.openaiResponsesStreamReducerProcessEvent === 'function'
413
+ ? result.openaiResponsesStreamReducerProcessEvent
414
+ : null;
415
+ result.responsesStreamDrive = async function(streamId, reducerId, hasProcessors, onChunkCallback) {
416
+ const st = _jsStreams.get(streamId);
417
+ if (!st) {
418
+ throw new Error(`Native mod HTTP stream was not found: ${streamId}`);
419
+ }
420
+ _jsStreams.delete(streamId);
421
+ let copilotUsage = null;
422
+ for (const event of st.events) {
423
+ if (!_nativeReducerProcessEvent) continue;
424
+ let parsed;
425
+ try { parsed = JSON.parse(_nativeReducerProcessEvent(reducerId, JSON.stringify(event)).json); }
426
+ catch (_) { continue; }
427
+ if (parsed && parsed.copilotUsage !== undefined && parsed.copilotUsage !== null)
428
+ copilotUsage = parsed.copilotUsage;
429
+ const cc = parsed && parsed.chunkContext;
430
+ if (hasProcessors && typeof onChunkCallback === 'function' && cc &&
431
+ (cc.content || cc.messageStart || cc.reportIntentArguments || cc.chunkBoundary || cc.size > 0)) {
432
+ try { onChunkCallback(JSON.stringify(cc)); } catch (_) {}
433
+ }
434
+ }
435
+ return { json: JSON.stringify({ kind: 'ok', copilotUsage, ttftMs: null, interTokenLatencyMs: null }) };
436
+ };
437
+ // --- end JS model HTTP implementation ---
438
+ }
439
+ return result;
440
+ };
441
+
442
+ // [Android bionic 対応] app.js の oPt() が globalThis.fetch を Rust ベースの B7 に
443
+ // 差し替えるのを阻止する。linuxmusl-arm64/runtime.node の Rust ネットワークスタックは
444
+ // bionic 上の実 I/O で動作しないため、Node.js ビルトイン fetch(動作確認済み)に固定する。
445
+ const _nativeFetch = globalThis.fetch;
446
+ Object.defineProperty(globalThis, 'fetch', {
447
+ configurable: true,
448
+ enumerable: true,
449
+ get() { return _nativeFetch; },
450
+ set(_) { /* B7 代入を無視 */ },
451
+ });