@buyong/pi-devin 0.0.0-stage → 0.1.0

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -2
  3. package/index.ts +16 -0
  4. package/package.json +62 -3
  5. package/proto/README.md +78 -0
  6. package/proto/buf/validate/validate.proto +453 -0
  7. package/proto/cel/expr/checked.proto +109 -0
  8. package/proto/cel/expr/eval.proto +44 -0
  9. package/proto/cel/expr/explain.proto +22 -0
  10. package/proto/cel/expr/syntax.proto +119 -0
  11. package/proto/cel/expr/value.proto +47 -0
  12. package/proto/connectext/grpc/status/v1/status.proto +21 -0
  13. package/proto/errorspb/errors.proto +58 -0
  14. package/proto/errorspb/hintdetail.proto +9 -0
  15. package/proto/errorspb/markers.proto +12 -0
  16. package/proto/errorspb/tags.proto +14 -0
  17. package/proto/errorspb/testing.proto +8 -0
  18. package/proto/exa/analytics_pb/analytics.proto +190 -0
  19. package/proto/exa/api_server_pb/api_server.proto +2590 -0
  20. package/proto/exa/auth_pb/auth.proto +21 -0
  21. package/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +81 -0
  22. package/proto/exa/browser_preview_pb/browser_preview.proto +26 -0
  23. package/proto/exa/bug_checker_pb/bug_checker.proto +24 -0
  24. package/proto/exa/cascade_plugins_pb/cascade_plugins.proto +264 -0
  25. package/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
  26. package/proto/exa/chat_pb/chat.proto +452 -0
  27. package/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
  28. package/proto/exa/codeium_common_pb/browser_context.proto +31 -0
  29. package/proto/exa/codeium_common_pb/codeium_common.proto +4183 -0
  30. package/proto/exa/context_module_pb/context_module.proto +174 -0
  31. package/proto/exa/cortex_pb/cortex.proto +3288 -0
  32. package/proto/exa/dev_pb/dev.proto +28 -0
  33. package/proto/exa/diff_action_pb/diff_action.proto +76 -0
  34. package/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +106 -0
  35. package/proto/exa/eval_pb/eval.proto +1318 -0
  36. package/proto/exa/extension_server_pb/extension_server.proto +555 -0
  37. package/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
  38. package/proto/exa/index_pb/index.proto +469 -0
  39. package/proto/exa/knowledge_base_pb/knowledge_base.proto +146 -0
  40. package/proto/exa/language_server_pb/language_server.proto +2435 -0
  41. package/proto/exa/model_management_pb/model_management.proto +184 -0
  42. package/proto/exa/opensearch_clients_pb/opensearch_clients.proto +495 -0
  43. package/proto/exa/product_analytics_pb/product_analytics.proto +39 -0
  44. package/proto/exa/prompt_pb/prompt.proto +97 -0
  45. package/proto/exa/reactive_component_pb/reactive_component.proto +98 -0
  46. package/proto/exa/seat_management_pb/seat_management.proto +2910 -0
  47. package/proto/exa/tokenizer_pb/tokenizer.proto +39 -0
  48. package/proto/exa/trainer_pb/config.proto +643 -0
  49. package/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
  50. package/proto/exa/trust_pb/trust.proto +192 -0
  51. package/proto/exa/user_analytics_pb/user_analytics.proto +752 -0
  52. package/proto/google/api/expr/v1alpha1/checked.proto +108 -0
  53. package/proto/google/api/expr/v1alpha1/eval.proto +38 -0
  54. package/proto/google/api/expr/v1alpha1/explain.proto +22 -0
  55. package/proto/google/api/expr/v1alpha1/syntax.proto +126 -0
  56. package/proto/google/api/expr/v1alpha1/value.proto +47 -0
  57. package/proto/google/protobuf/any.proto +15 -0
  58. package/proto/google/protobuf/cpp_features.proto +22 -0
  59. package/proto/google/protobuf/descriptor.proto +473 -0
  60. package/proto/google/protobuf/duration.proto +16 -0
  61. package/proto/google/protobuf/empty.proto +14 -0
  62. package/proto/google/protobuf/go_features.proto +31 -0
  63. package/proto/google/protobuf/struct.proto +34 -0
  64. package/proto/google/protobuf/timestamp.proto +16 -0
  65. package/proto/google/protobuf/wrappers.proto +47 -0
  66. package/proto/google/rpc/status.proto +18 -0
  67. package/proto/grpc/binlog/v1/binarylog.proto +89 -0
  68. package/proto/io/prometheus/client/metrics.proto +103 -0
  69. package/src/core/auth/access.ts +6 -0
  70. package/src/core/auth/index.ts +2 -0
  71. package/src/core/auth/login.ts +177 -0
  72. package/src/core/auth/rest-exchange.ts +35 -0
  73. package/src/core/client.ts +153 -0
  74. package/src/core/index.ts +12 -0
  75. package/src/core/inference/chat.ts +229 -0
  76. package/src/core/inference/images.ts +24 -0
  77. package/src/core/inference/messages.ts +34 -0
  78. package/src/core/inference/retry.ts +68 -0
  79. package/src/core/inference/state.ts +8 -0
  80. package/src/core/models/access.ts +7 -0
  81. package/src/core/models/catalog.ts +35 -0
  82. package/src/core/models/index.ts +4 -0
  83. package/src/core/models/select.ts +43 -0
  84. package/src/core/models/variants.ts +163 -0
  85. package/src/core/protocol/device.ts +73 -0
  86. package/src/core/protocol/fingerprint.ts +59 -0
  87. package/src/core/protocol/http.ts +109 -0
  88. package/src/core/protocol/protobuf.ts +112 -0
  89. package/src/core/protocol/transport.ts +501 -0
  90. package/src/core/types.ts +115 -0
  91. package/src/pi/auth.ts +134 -0
  92. package/src/pi/devin-command.ts +76 -0
  93. package/src/pi/devin-panel.ts +77 -0
  94. package/src/pi/devin-settings.ts +50 -0
  95. package/src/pi/extension.ts +80 -0
  96. package/src/pi/fusion-command.ts +83 -0
  97. package/src/pi/fusion-picker.ts +171 -0
  98. package/src/pi/fusion-settings.ts +44 -0
  99. package/src/pi/lifecycle.ts +217 -0
  100. package/src/pi/messages.ts +107 -0
  101. package/src/pi/models.ts +378 -0
  102. package/src/pi/provider.ts +241 -0
  103. package/src/pi/sidekick-report.ts +42 -0
  104. package/src/pi/sidekick-runtime.ts +287 -0
  105. package/src/pi/sidekick.ts +190 -0
  106. package/src/pi/stream.ts +124 -0
  107. package/src/pi/usage.ts +153 -0
@@ -0,0 +1,73 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { networkInterfaces, userInfo, type NetworkInterfaceInfo } from "node:os";
3
+
4
+ export interface DeviceIdentity {
5
+ serial: string;
6
+ macs: string[];
7
+ username: string;
8
+ }
9
+
10
+ /** OS inputs read by device collection. Tests control only this boundary; collection rules and the fingerprint computation run unchanged. */
11
+ export interface DeviceSystem {
12
+ platform: NodeJS.Platform;
13
+ run(file: string, args: string[]): string;
14
+ networkInterfaces(): NodeJS.Dict<NetworkInterfaceInfo[]>;
15
+ username(): string;
16
+ }
17
+
18
+ // The MAC collection shell input is a fixed string and never includes user input.
19
+ const MAC_COMMAND = `ifconfig 2>/dev/null | awk '/^[a-z]/{iface=$1} /inet6? /{has_ip[iface]=1} /ether /{mac[iface]=$2} END{for(i in has_ip) if(mac[i] && mac[i]!="00:00:00:00:00:00") print mac[i]}' | tr '[:upper:]' '[:lower:]' | sort -u`;
20
+ const COMMAND_OPTIONS = {
21
+ encoding: "utf8" as const, timeout: 3000, maxBuffer: 1024 * 1024,
22
+ env: { PATH: "/usr/bin:/bin:/usr/sbin:/sbin", LC_ALL: "C" },
23
+ stdio: ["ignore", "pipe", "ignore"] as ["ignore", "pipe", "ignore"],
24
+ };
25
+
26
+ function nodeDeviceSystem(): DeviceSystem {
27
+ return {
28
+ platform: process.platform,
29
+ run: (file, args) => execFileSync(file, args, COMMAND_OPTIONS),
30
+ networkInterfaces,
31
+ username: () => userInfo().username,
32
+ };
33
+ }
34
+
35
+ /** MAC collection conditions: non-zero MACs of interfaces that have an address, lowercased, deduplicated and sorted. */
36
+ function interfaceMACs(interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>): string[] {
37
+ const macs = new Set<string>();
38
+ for (const addresses of Object.values(interfaces)) {
39
+ for (const address of addresses ?? []) {
40
+ const mac = address.mac.toLowerCase();
41
+ if (mac && mac !== "00:00:00:00:00:00") macs.add(mac);
42
+ }
43
+ }
44
+ return [...macs].sort();
45
+ }
46
+
47
+ /**
48
+ * Device values are used only for the fingerprint. Credentials and settings of an installed CLI are never read.
49
+ * macOS reads ioreg/ifconfig. Serial collection on other OSes is unverified, so the
50
+ * serial is left empty instead of being invented; MACs come from the common Node API under the same conditions.
51
+ */
52
+ export function collectDeviceIdentity(system: DeviceSystem = nodeDeviceSystem()): DeviceIdentity {
53
+ const isDarwin = system.platform === "darwin";
54
+ let serial = "";
55
+ if (isDarwin) {
56
+ try {
57
+ const result = system.run("/usr/sbin/ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
58
+ const match = result.match(/"IOPlatformSerialNumber"\s*=\s*("(?:\\.|[^"\\])*")/);
59
+ if (match) serial = JSON.parse(match[1]) as string;
60
+ } catch { /* A failed serial read becomes an empty value. The output may be sensitive, so it is not included in errors. */ }
61
+ }
62
+ let macs: string[] = [];
63
+ if (Buffer.byteLength(serial) < 4) {
64
+ try {
65
+ macs = isDarwin
66
+ ? system.run("/bin/sh", ["-c", MAC_COMMAND]).split(/\r?\n/).filter(Boolean)
67
+ : interfaceMACs(system.networkInterfaces());
68
+ } catch { /* A failed MAC collection yields an empty list. */ }
69
+ }
70
+ let username = "unknown";
71
+ try { username = system.username(); } catch { /* value used when the system call fails */ }
72
+ return { serial, macs, username };
73
+ }
@@ -0,0 +1,59 @@
1
+ import { createCipheriv, createHash, randomBytes } from "node:crypto";
2
+ import { performance } from "node:perf_hooks";
3
+ import { collectDeviceIdentity, type DeviceIdentity } from "./device.ts";
4
+
5
+ // Matches the CLI's fingerprint construction; verified with fake-token requests only.
6
+ // This is not encryption for credential storage. f contains the API token, so it is never logged.
7
+ const KEY_MATERIAL = "df97465349f38646af1d66c263e25e08";
8
+ const KEY = createHash("sha256").update(KEY_MATERIAL, "utf8").digest();
9
+
10
+ export interface FingerprintProvider {
11
+ create(): string;
12
+ device(): string;
13
+ }
14
+
15
+ export function hashDeviceIdentity(identity: DeviceIdentity): string {
16
+ const macs = Buffer.byteLength(identity.serial) >= 4 ? "" : identity.macs.join(",");
17
+ return createHash("sha512").update(macs + identity.serial + identity.username, "utf8").digest("hex");
18
+ }
19
+
20
+ export function createFingerprintProvider(sessionToken: string, options: {
21
+ collectIdentity?: () => DeviceIdentity;
22
+ nowNanoseconds?: () => bigint;
23
+ nonce?: () => Uint8Array;
24
+ } = {}): FingerprintProvider {
25
+ let device: string | undefined;
26
+ const getDevice = () => device ??= hashDeviceIdentity((options.collectIdentity ?? collectDeviceIdentity)());
27
+ return {
28
+ device: getDevice,
29
+ create: () => encodeFingerprint({
30
+ deviceFingerprint: getDevice(), sessionToken,
31
+ timestampNanoseconds: options.nowNanoseconds?.(), nonce: options.nonce?.(),
32
+ }),
33
+ };
34
+ }
35
+
36
+ export function unixNanoseconds(): bigint {
37
+ // High-resolution epoch clock from the public Node API. It does not guarantee the OS's raw CLOCK_REALTIME precision.
38
+ const originMilliseconds = Math.trunc(performance.timeOrigin);
39
+ const fractionalNanoseconds = Math.round((performance.timeOrigin - originMilliseconds + performance.now()) * 1_000_000);
40
+ return BigInt(originMilliseconds) * 1_000_000n + BigInt(fractionalNanoseconds);
41
+ }
42
+
43
+ export function encodeFingerprint(options: {
44
+ deviceFingerprint: string;
45
+ sessionToken: string;
46
+ timestampNanoseconds?: bigint;
47
+ nonce?: Uint8Array;
48
+ }): string {
49
+ if (!/^[0-9a-f]{128}$/.test(options.deviceFingerprint)) throw new Error("The device identifier must be 128 lowercase hex characters.");
50
+ if (!options.sessionToken || /[|\s\u0000-\u001f\u007f]/.test(options.sessionToken)) throw new Error("Invalid session token format.");
51
+ const timestamp = options.timestampNanoseconds ?? unixNanoseconds();
52
+ if (timestamp < 0n || timestamp >= (1n << 128n)) throw new Error("Invalid fingerprint timestamp.");
53
+ const nonce = options.nonce ?? randomBytes(12);
54
+ if (nonce.length !== 12) throw new Error("The fingerprint nonce must be 12 bytes.");
55
+ const plaintext = `${options.deviceFingerprint}|${options.sessionToken}|${timestamp}`;
56
+ const cipher = createCipheriv("aes-256-gcm", KEY, nonce);
57
+ const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
58
+ return Buffer.concat([nonce, ciphertext, cipher.getAuthTag()]).toString("hex");
59
+ }
@@ -0,0 +1,109 @@
1
+ import { Agent as HTTPAgent, request as httpRequest } from "node:http";
2
+ import { Agent as HTTPSAgent, request as httpsRequest } from "node:https";
3
+ import { Readable, pipeline } from "node:stream";
4
+ import type { ConnectionOptions } from "node:tls";
5
+ import { createGunzip } from "node:zlib";
6
+
7
+ export interface HTTPTransportOptions {
8
+ /** Trusts a user-supplied CA without disabling certificate or host verification. */
9
+ ca?: ConnectionOptions["ca"];
10
+ }
11
+
12
+ export interface HTTPTransport {
13
+ fetch: typeof fetch;
14
+ close(): void;
15
+ }
16
+
17
+ /**
18
+ * HTTP/1.1-only transport. Owns a per-client pool and verifies TLS certificates and hosts.
19
+ * The 5000ms socket timeout spells out the previous Node global agent setting as a PoC policy;
20
+ * it is not the overall RPC deadline. It does not claim to match the CLI's TLS or pool policy.
21
+ */
22
+ export function createHTTPTransport(options: HTTPTransportOptions = {}): HTTPTransport {
23
+ const httpAgent = new HTTPAgent({ keepAlive: true, timeout: 5000 });
24
+ const httpsAgent = new HTTPSAgent({
25
+ keepAlive: true, timeout: 5000, rejectUnauthorized: true, minVersion: "TLSv1.2",
26
+ ALPNProtocols: ["http/1.1"], ...(options.ca === undefined ? {} : { ca: options.ca }),
27
+ });
28
+ let isClosed = false;
29
+ const fetchHTTP: typeof fetch = async (input, init) => {
30
+ if (isClosed) throw new Error("The HTTP transport is closed.");
31
+ if (!(typeof input === "string" || input instanceof URL) || !["POST", "GET"].includes(init?.method ?? "") || init?.redirect !== "error") {
32
+ throw new Error("This transport supports only URL GET/POST without redirects.");
33
+ }
34
+ const url = new URL(input);
35
+ if (!["https:", "http:"].includes(url.protocol)) throw new Error("Unsupported HTTP protocol.");
36
+ const body = typeof init.body === "string" ? Buffer.from(init.body)
37
+ : init.body instanceof Uint8Array ? Buffer.from(init.body) : undefined;
38
+ if (init.method === "POST" && !body) throw new Error("A POST body must be a string or a byte array.");
39
+ if (init.method === "GET" && init.body != null) throw new Error("A GET body is not supported.");
40
+ const validated = new Headers(init.headers);
41
+ const entries = init.headers instanceof Headers ? [...init.headers.entries()]
42
+ : Array.isArray(init.headers) ? init.headers : Object.entries(init.headers ?? {});
43
+ const headers: Record<string, string> = {};
44
+ for (const [name] of entries) {
45
+ const key = name.toLowerCase();
46
+ if (key !== "host" && key !== "content-length") headers[key] = validated.get(name)!;
47
+ }
48
+ headers.host = url.host;
49
+ if (body) headers["content-length"] = String(body.byteLength);
50
+ return new Promise<Response>((resolve, reject) => {
51
+ const isHTTPS = url.protocol === "https:";
52
+ const send = isHTTPS ? httpsRequest : httpRequest;
53
+ const request = send(url, {
54
+ method: init.method, headers, signal: init.signal ?? undefined,
55
+ agent: isHTTPS ? httpsAgent : httpAgent,
56
+ }, (incoming) => {
57
+ try {
58
+ const responseHeaders = new Headers();
59
+ for (const [name, value] of Object.entries(incoming.headers)) {
60
+ for (const entry of Array.isArray(value) ? value : value === undefined ? [] : [value]) responseHeaders.append(name, entry);
61
+ }
62
+ const status = incoming.statusCode ?? 502;
63
+ const hasNoBody = [204, 205, 304].includes(status);
64
+ const encoding = responseHeaders.get("content-encoding")?.trim().toLowerCase();
65
+ let stream: Readable = incoming;
66
+ if (hasNoBody) incoming.resume();
67
+ else if (encoding && encoding !== "identity") {
68
+ if (encoding !== "gzip") {
69
+ // An encoding problem in an error body must not hide an already received 401/403/429 status.
70
+ if (status >= 200 && status < 300) throw new Error("Unsupported HTTP response compression.");
71
+ } else {
72
+ const decoded = createGunzip();
73
+ // Propagates consumer cancellation and compression errors to the original socket and keeps backpressure.
74
+ pipeline(incoming, decoded, () => {});
75
+ stream = decoded;
76
+ responseHeaders.delete("content-encoding");
77
+ responseHeaders.delete("content-length");
78
+ }
79
+ }
80
+ resolve(new Response(hasNoBody ? null : Readable.toWeb(stream) as ReadableStream<Uint8Array>, {
81
+ status, headers: responseHeaders,
82
+ }));
83
+ } catch (error) {
84
+ incoming.destroy();
85
+ reject(error);
86
+ }
87
+ });
88
+ request.once("error", reject);
89
+ request.once("upgrade", (_response, socket) => {
90
+ socket.destroy();
91
+ reject(new Error("HTTP upgrade is not supported."));
92
+ });
93
+ request.removeHeader("connection");
94
+ request.end(body);
95
+ });
96
+ };
97
+ return {
98
+ fetch: fetchHTTP,
99
+ close() {
100
+ if (isClosed) return;
101
+ isClosed = true;
102
+ httpAgent.destroy();
103
+ httpsAgent.destroy();
104
+ },
105
+ };
106
+ }
107
+
108
+ /** Default transport for existing direct callers. ConnectClient owns its own pool. */
109
+ export const requestHTTP: typeof fetch = createHTTPTransport().fetch;
@@ -0,0 +1,112 @@
1
+ import { readdirSync, statSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import protobuf from "protobufjs";
5
+ import { object, type JsonObject } from "../types.ts";
6
+
7
+ export const CHAT_METHOD = "exa.api_server_pb.ApiServerService/GetChatMessage";
8
+
9
+ export interface MessageCodec {
10
+ encode(value: JsonObject): Uint8Array;
11
+ decode(bytes: Uint8Array): JsonObject;
12
+ }
13
+
14
+ let root: protobuf.Root | undefined;
15
+ const camelCase = (name: string) => name.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());
16
+
17
+ const PROTO_ROOT = fileURLToPath(new URL("../../../proto/", import.meta.url));
18
+
19
+ function protoSources(directory: string, out: string[] = []): string[] {
20
+ for (const entry of readdirSync(directory)) {
21
+ const path = join(directory, entry);
22
+ if (statSync(path).isDirectory()) protoSources(path, out);
23
+ else if (entry.endsWith(".proto")) out.push(path);
24
+ }
25
+ return out;
26
+ }
27
+
28
+ function getRoot(): protobuf.Root {
29
+ if (!root) {
30
+ // The schema is the shipped `proto/` source tree, parsed directly. The four definitions this
31
+ // package added from its own protocol analysis are marked in the sources, so nothing is
32
+ // patched in at runtime. keepCase keeps the declared snake_case names the DTO boundary uses.
33
+ root = new protobuf.Root();
34
+ root.resolvePath = (_origin: string, target: string) => isAbsolute(target) ? target : join(PROTO_ROOT, target);
35
+ root.loadSync(protoSources(PROTO_ROOT), { keepCase: true });
36
+ root.resolveAll();
37
+ }
38
+ return root;
39
+ }
40
+
41
+ /** Converts between camelCase DTOs and the original snake_case at the boundary; user keys inside maps stay unchanged. */
42
+ function convertFields(type: protobuf.Type, value: JsonObject, isEncoding: boolean, path: string): JsonObject {
43
+ const result: [string, unknown][] = [];
44
+ for (const [name, entry] of Object.entries(value)) {
45
+ if (entry === undefined || entry === null) continue;
46
+ const field = isEncoding ? type.fieldsArray.find((candidate) => camelCase(candidate.name) === name)
47
+ : Object.hasOwn(type.fields, name) ? type.fields[name] : undefined;
48
+ if (!field) throw new Error(`Field not in the protobuf schema: ${path}.${name}`);
49
+ const convert = (item: unknown): unknown => {
50
+ if (field.resolvedType instanceof protobuf.Type) {
51
+ return convertFields(field.resolvedType, object(item, `${path}.${name}`), isEncoding, `${path}.${name}`);
52
+ }
53
+ // Prevents fromObject from silently turning an invalid enum into 0.
54
+ if (isEncoding && field.resolvedType instanceof protobuf.Enum) {
55
+ const isValid = typeof item === "string" ? Object.hasOwn(field.resolvedType.values, item)
56
+ : typeof item === "number" && Object.hasOwn(field.resolvedType.valuesById, item);
57
+ if (!isValid) throw new Error(`Unknown protobuf enum: ${path}.${name}`);
58
+ }
59
+ return item;
60
+ };
61
+ let converted: unknown;
62
+ if (field.map) converted = Object.fromEntries(Object.entries(object(entry, `${path}.${name}`)).map(([key, item]) => [key, convert(item)]));
63
+ else if (field.repeated) {
64
+ if (!Array.isArray(entry)) throw new Error(`Field requires an array: ${path}.${name}`);
65
+ converted = entry.map(convert);
66
+ } else converted = convert(entry);
67
+ result.push([isEncoding ? field.name : camelCase(field.name), converted]);
68
+ }
69
+ return Object.fromEntries(result);
70
+ }
71
+
72
+ function messageCodec(typeName: string): MessageCodec {
73
+ const getType = () => getRoot().lookupType(typeName);
74
+ return {
75
+ encode(value) {
76
+ const type = getType();
77
+ const message = type.fromObject(convertFields(type, value, true, typeName));
78
+ const error = type.verify(message);
79
+ if (error) throw new Error(`Invalid protobuf input: ${error}`);
80
+ return type.encode(message).finish();
81
+ },
82
+ decode(bytes) {
83
+ const type = getType();
84
+ try {
85
+ const decoded = object(type.toObject(type.decode(bytes), {
86
+ longs: String, enums: String, bytes: String, defaults: false,
87
+ }), typeName);
88
+ return convertFields(type, decoded, false, typeName);
89
+ } catch {
90
+ throw new Error(`Invalid protobuf response: ${typeName}`);
91
+ }
92
+ },
93
+ };
94
+ }
95
+
96
+ export function unaryCodecs(method: string): { request: MessageCodec; response: MessageCodec } {
97
+ const split = method.lastIndexOf("/");
98
+ if (split <= 0) throw new Error("A valid RPC path is required.");
99
+ const service = getRoot().lookupService(method.slice(0, split));
100
+ const definition = service.methods[method.slice(split + 1)];
101
+ definition?.resolve();
102
+ if (!definition?.resolvedRequestType || !definition.resolvedResponseType || definition.responseStream || definition.requestStream) {
103
+ throw new Error(`Unsupported unary RPC: ${method}`);
104
+ }
105
+ return {
106
+ request: messageCodec(definition.resolvedRequestType.fullName),
107
+ response: messageCodec(definition.resolvedResponseType.fullName),
108
+ };
109
+ }
110
+
111
+ export const chatRequestCodec = messageCodec("exa.api_server_pb.GetChatMessageRequest");
112
+ export const chatResponseCodec = messageCodec("exa.api_server_pb.GetChatMessageResponse");