@agentims/sdk 0.0.0-stage → 0.3.1

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 (113) hide show
  1. package/README.md +73 -2
  2. package/dist/api/client.d.ts +36 -0
  3. package/dist/api/client.js +54 -0
  4. package/dist/api/errors.d.ts +52 -0
  5. package/dist/api/errors.js +43 -0
  6. package/dist/api/options.d.ts +12 -0
  7. package/dist/api/options.js +1 -0
  8. package/dist/api/request-data.d.ts +10 -0
  9. package/dist/api/request-data.js +43 -0
  10. package/dist/features/contacts/client.d.ts +9 -0
  11. package/dist/features/contacts/client.js +12 -0
  12. package/dist/features/contacts/index.d.ts +1 -0
  13. package/dist/features/contacts/index.js +1 -0
  14. package/dist/features/contacts/types.d.ts +19 -0
  15. package/dist/features/contacts/types.js +3 -0
  16. package/dist/features/conversations/client.d.ts +20 -0
  17. package/dist/features/conversations/client.js +42 -0
  18. package/dist/features/conversations/index.d.ts +1 -0
  19. package/dist/features/conversations/index.js +1 -0
  20. package/dist/features/conversations/types.d.ts +64 -0
  21. package/dist/features/conversations/types.js +3 -0
  22. package/dist/features/deliveries/client.d.ts +17 -0
  23. package/dist/features/deliveries/client.js +31 -0
  24. package/dist/features/deliveries/index.d.ts +1 -0
  25. package/dist/features/deliveries/index.js +1 -0
  26. package/dist/features/deliveries/types.d.ts +54 -0
  27. package/dist/features/deliveries/types.js +1 -0
  28. package/dist/features/devices/client.d.ts +11 -0
  29. package/dist/features/devices/client.js +18 -0
  30. package/dist/features/devices/index.d.ts +1 -0
  31. package/dist/features/devices/index.js +1 -0
  32. package/dist/features/devices/types.d.ts +29 -0
  33. package/dist/features/devices/types.js +3 -0
  34. package/dist/features/discovery/client.d.ts +9 -0
  35. package/dist/features/discovery/client.js +9 -0
  36. package/dist/features/discovery/index.d.ts +1 -0
  37. package/dist/features/discovery/index.js +1 -0
  38. package/dist/features/discovery/types.d.ts +6 -0
  39. package/dist/features/discovery/types.js +3 -0
  40. package/dist/features/encryption/client.d.ts +16 -0
  41. package/dist/features/encryption/client.js +30 -0
  42. package/dist/features/encryption/index.d.ts +1 -0
  43. package/dist/features/encryption/index.js +1 -0
  44. package/dist/features/encryption/types.d.ts +54 -0
  45. package/dist/features/encryption/types.js +3 -0
  46. package/dist/features/identity/client.d.ts +21 -0
  47. package/dist/features/identity/client.js +43 -0
  48. package/dist/features/identity/index.d.ts +3 -0
  49. package/dist/features/identity/index.js +1 -0
  50. package/dist/features/identity/types.d.ts +105 -0
  51. package/dist/features/identity/types.js +1 -0
  52. package/dist/features/messages/client.d.ts +18 -0
  53. package/dist/features/messages/client.js +35 -0
  54. package/dist/features/messages/index.d.ts +1 -0
  55. package/dist/features/messages/index.js +1 -0
  56. package/dist/features/messages/types.d.ts +54 -0
  57. package/dist/features/messages/types.js +3 -0
  58. package/dist/features/outbox/client.d.ts +14 -0
  59. package/dist/features/outbox/client.js +25 -0
  60. package/dist/features/outbox/index.d.ts +1 -0
  61. package/dist/features/outbox/index.js +1 -0
  62. package/dist/features/outbox/types.d.ts +41 -0
  63. package/dist/features/outbox/types.js +3 -0
  64. package/dist/features/pairing/client.d.ts +11 -0
  65. package/dist/features/pairing/client.js +16 -0
  66. package/dist/features/pairing/index.d.ts +1 -0
  67. package/dist/features/pairing/index.js +1 -0
  68. package/dist/features/pairing/types.d.ts +21 -0
  69. package/dist/features/pairing/types.js +3 -0
  70. package/dist/features/profiles/client.d.ts +10 -0
  71. package/dist/features/profiles/client.js +15 -0
  72. package/dist/features/profiles/index.d.ts +1 -0
  73. package/dist/features/profiles/index.js +1 -0
  74. package/dist/features/profiles/types.d.ts +23 -0
  75. package/dist/features/profiles/types.js +3 -0
  76. package/dist/features/reliability/client.d.ts +9 -0
  77. package/dist/features/reliability/client.js +12 -0
  78. package/dist/features/reliability/index.d.ts +1 -0
  79. package/dist/features/reliability/index.js +1 -0
  80. package/dist/features/reliability/types.d.ts +27 -0
  81. package/dist/features/reliability/types.js +3 -0
  82. package/dist/features/timeline/client.d.ts +12 -0
  83. package/dist/features/timeline/client.js +21 -0
  84. package/dist/features/timeline/index.d.ts +1 -0
  85. package/dist/features/timeline/index.js +1 -0
  86. package/dist/features/timeline/types.d.ts +60 -0
  87. package/dist/features/timeline/types.js +3 -0
  88. package/dist/features/verification/client.d.ts +11 -0
  89. package/dist/features/verification/client.js +18 -0
  90. package/dist/features/verification/index.d.ts +1 -0
  91. package/dist/features/verification/index.js +1 -0
  92. package/dist/features/verification/types.d.ts +20 -0
  93. package/dist/features/verification/types.js +3 -0
  94. package/dist/index.d.ts +27 -0
  95. package/dist/index.js +4 -0
  96. package/dist/internal/protocol/codec.d.ts +4 -0
  97. package/dist/internal/protocol/codec.js +69 -0
  98. package/dist/internal/protocol/input.d.ts +2 -0
  99. package/dist/internal/protocol/input.js +38 -0
  100. package/dist/internal/protocol/operations.json +88 -0
  101. package/dist/internal/protocol/registry.d.ts +17 -0
  102. package/dist/internal/protocol/registry.js +72 -0
  103. package/dist/internal/protocol/schema.d.ts +23 -0
  104. package/dist/internal/protocol/schema.js +105 -0
  105. package/dist/internal/transport/client.d.ts +8 -0
  106. package/dist/internal/transport/client.js +102 -0
  107. package/dist/internal/transport/error-diagnostics.d.ts +3 -0
  108. package/dist/internal/transport/error-diagnostics.js +50 -0
  109. package/dist/internal/transport/request.d.ts +9 -0
  110. package/dist/internal/transport/request.js +55 -0
  111. package/dist/internal/transport/response.d.ts +2 -0
  112. package/dist/internal/transport/response.js +85 -0
  113. package/package.json +34 -4
@@ -0,0 +1,72 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { ProtocolError } from "../../api/errors.js";
3
+ import { parseJSON } from "./codec.js";
4
+ import { isRecord } from "./schema.js";
5
+ // Local generated metadata is loaded losslessly, including exact u64 schema bounds.
6
+ function loadRegistry() {
7
+ const value = parseJSON(readFileSync(new URL("./operations.json", import.meta.url), "utf8"));
8
+ if (!isRecord(value) || value.version !== 1 || !isRecord(value.operations)) {
9
+ throw new ProtocolError("Unsupported generated operation registry");
10
+ }
11
+ for (const operation of Object.values(value.operations)) {
12
+ if (!isRecord(operation) || typeof operation.method !== "string"
13
+ || typeof operation.path !== "string" || !Array.isArray(operation.parameters)
14
+ || typeof operation.status !== "number" || typeof operation.binary !== "boolean"
15
+ || !(operation.request === null || isRecord(operation.request))
16
+ || !(operation.response === null || isRecord(operation.response))) {
17
+ throw new ProtocolError("Invalid generated operation metadata");
18
+ }
19
+ if (!["GET", "POST", "PUT"].includes(operation.method)
20
+ || !/^\/v[12]\//.test(operation.path) || !Number.isSafeInteger(operation.status)
21
+ || operation.status < 200 || operation.status >= 300) {
22
+ throw new ProtocolError("Unsupported generated operation semantics");
23
+ }
24
+ for (const parameter of operation.parameters) {
25
+ if (!isRecord(parameter) || typeof parameter.name !== "string"
26
+ || !["path", "query"].includes(String(parameter.in))
27
+ || typeof parameter.required !== "boolean" || !isRecord(parameter.schema)) {
28
+ throw new ProtocolError("Invalid generated parameter metadata");
29
+ }
30
+ checkSchema(parameter.schema);
31
+ }
32
+ if (operation.request !== null)
33
+ checkSchema(operation.request);
34
+ if (operation.response !== null)
35
+ checkSchema(operation.response);
36
+ }
37
+ // Trusted generated data has been checked above; network data is checked per call.
38
+ return value.operations;
39
+ }
40
+ function checkSchema(schema) {
41
+ const annotations = ["$id", "$schema", "title", "description", "default", "examples", "writeOnly", "contentEncoding"];
42
+ const assertions = ["type", "const", "enum", "anyOf", "properties", "required", "additionalProperties",
43
+ "items", "minItems", "maxItems", "uniqueItems", "minLength", "maxLength", "pattern", "minimum", "maximum", "format"];
44
+ for (const [key, value] of Object.entries(schema)) {
45
+ if (!annotations.includes(key) && !assertions.includes(key) && !key.startsWith("x-")) {
46
+ throw new ProtocolError("Unsupported generated schema assertion");
47
+ }
48
+ if (key === "format" && value !== "uri" && value !== "binary") {
49
+ throw new ProtocolError("Unsupported generated schema format");
50
+ }
51
+ if (key === "properties" && isRecord(value)) {
52
+ for (const child of Object.values(value))
53
+ if (isRecord(child))
54
+ checkSchema(child);
55
+ }
56
+ else if (key === "anyOf" && Array.isArray(value)) {
57
+ for (const child of value)
58
+ if (isRecord(child))
59
+ checkSchema(child);
60
+ }
61
+ else if (["items", "additionalProperties"].includes(key) && isRecord(value)) {
62
+ checkSchema(value);
63
+ }
64
+ }
65
+ }
66
+ const operations = loadRegistry();
67
+ export function operationFor(id) {
68
+ const operation = operations[id];
69
+ if (!operation)
70
+ throw new ProtocolError("Unknown local API operation");
71
+ return operation;
72
+ }
@@ -0,0 +1,23 @@
1
+ export interface Schema {
2
+ readonly type?: string | readonly string[];
3
+ readonly const?: unknown;
4
+ readonly enum?: readonly unknown[];
5
+ readonly anyOf?: readonly Schema[];
6
+ readonly properties?: Readonly<Record<string, Schema>>;
7
+ readonly required?: readonly string[];
8
+ readonly additionalProperties?: boolean | Schema;
9
+ readonly items?: Schema;
10
+ readonly minItems?: number;
11
+ readonly maxItems?: number;
12
+ readonly uniqueItems?: boolean;
13
+ readonly minLength?: number;
14
+ readonly maxLength?: number;
15
+ readonly pattern?: string;
16
+ readonly minimum?: number | bigint;
17
+ readonly maximum?: number | bigint;
18
+ readonly format?: string;
19
+ readonly "x-max-body-bytes"?: number | bigint;
20
+ }
21
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
22
+ /** Implements the assertions actually used by the canonical expanded schemas. */
23
+ export declare function schemaIssue(value: unknown, schema: Schema, path?: string): string | null;
@@ -0,0 +1,105 @@
1
+ import { stringifyJSON } from "./codec.js";
2
+ export function isRecord(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ /** Implements the assertions actually used by the canonical expanded schemas. */
6
+ export function schemaIssue(value, schema, path = "$") {
7
+ if (schema.anyOf && !schema.anyOf.some((part) => schemaIssue(value, part) === null)) {
8
+ return `${path}: does not match any allowed schema`;
9
+ }
10
+ if ("const" in schema && value !== schema.const)
11
+ return `${path}: invalid constant`;
12
+ if (schema.enum && !schema.enum.includes(value))
13
+ return `${path}: invalid enum value`;
14
+ const types = typeof schema.type === "string" ? [schema.type] : schema.type;
15
+ if (types && !types.some((type) => matchesType(value, type))) {
16
+ return `${path}: invalid type`;
17
+ }
18
+ if (typeof value === "number" || typeof value === "bigint") {
19
+ if (typeof value === "number" && !Number.isSafeInteger(value)) {
20
+ return `${path}: use bigint for integers outside the safe Number range`;
21
+ }
22
+ if (schema.minimum !== undefined && value < schema.minimum)
23
+ return `${path}: below minimum`;
24
+ if (schema.maximum !== undefined && value > schema.maximum)
25
+ return `${path}: above maximum`;
26
+ }
27
+ if (typeof value === "string") {
28
+ for (const character of value) {
29
+ const point = character.codePointAt(0);
30
+ if (point !== undefined && point >= 0xd800 && point <= 0xdfff)
31
+ return `${path}: invalid Unicode scalar`;
32
+ }
33
+ const length = [...value].length;
34
+ if (schema.minLength !== undefined && length < schema.minLength)
35
+ return `${path}: too short`;
36
+ if (schema.maxLength !== undefined && length > schema.maxLength)
37
+ return `${path}: too long`;
38
+ if (schema.pattern && !new RegExp(schema.pattern, "u").test(value))
39
+ return `${path}: invalid pattern`;
40
+ if (schema.format === "uri") {
41
+ // WHATWG URL normalizes whitespace/non-ASCII and accepts malformed escapes.
42
+ // Canonical URI values must already use URI characters and valid %HH tokens.
43
+ if (!/^[A-Za-z0-9._~:/?#\[\]@!$&'()*+,;=%-]+$/.test(value)
44
+ || /%(?![0-9A-Fa-f]{2})/.test(value))
45
+ return `${path}: invalid URI`;
46
+ try {
47
+ new URL(value);
48
+ }
49
+ catch {
50
+ return `${path}: invalid URI`;
51
+ }
52
+ }
53
+ }
54
+ if (Array.isArray(value)) {
55
+ if (schema.minItems !== undefined && value.length < schema.minItems)
56
+ return `${path}: too few items`;
57
+ if (schema.maxItems !== undefined && value.length > schema.maxItems)
58
+ return `${path}: too many items`;
59
+ if (schema.uniqueItems && new Set(value.map(stringifyJSON)).size !== value.length) {
60
+ return `${path}: duplicate items`;
61
+ }
62
+ if (schema.items) {
63
+ for (const [index, item] of value.entries()) {
64
+ const issue = schemaIssue(item, schema.items, `${path}[${index}]`);
65
+ if (issue)
66
+ return issue;
67
+ }
68
+ }
69
+ }
70
+ if (isRecord(value)) {
71
+ for (const name of schema.required ?? []) {
72
+ if (!Object.hasOwn(value, name))
73
+ return `${path}.${name}: required`;
74
+ }
75
+ for (const [name, field] of Object.entries(value)) {
76
+ const child = schema.properties && Object.hasOwn(schema.properties, name) ? schema.properties[name] : undefined;
77
+ if (child) {
78
+ const issue = schemaIssue(field, child, `${path}.${name}`);
79
+ if (issue)
80
+ return issue;
81
+ }
82
+ else if (schema.additionalProperties === false) {
83
+ return `${path}: unknown property`;
84
+ }
85
+ else if (isRecord(schema.additionalProperties)) {
86
+ const issue = schemaIssue(field, schema.additionalProperties, `${path}.${name}`);
87
+ if (issue)
88
+ return issue;
89
+ }
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ function matchesType(value, type) {
95
+ if (type === "null")
96
+ return value === null;
97
+ if (type === "object")
98
+ return isRecord(value);
99
+ if (type === "array")
100
+ return Array.isArray(value);
101
+ if (type === "integer") {
102
+ return typeof value === "bigint" || (typeof value === "number" && Number.isSafeInteger(value));
103
+ }
104
+ return typeof value === type;
105
+ }
@@ -0,0 +1,8 @@
1
+ import type { ClientOptions } from "../../api/options.js";
2
+ import type { CallOptions } from "./request.js";
3
+ export declare class Transport {
4
+ #private;
5
+ constructor(options: ClientOptions);
6
+ call<T>(id: string, options?: CallOptions): Promise<T>;
7
+ close(): Promise<void>;
8
+ }
@@ -0,0 +1,102 @@
1
+ import { AgentIMError, ClientClosedError, RequestCancelledError, RequestTimeoutError, TransportError, ValidationError, } from "../../api/errors.js";
2
+ import { operationFor } from "../protocol/registry.js";
3
+ import { requestBody, requestURL } from "./request.js";
4
+ import { readResponse } from "./response.js";
5
+ export class Transport {
6
+ #origin;
7
+ #token;
8
+ #timeoutMs;
9
+ #maxResponseBytes;
10
+ #pending = new Map();
11
+ #closed = false;
12
+ constructor(options) {
13
+ let url;
14
+ try {
15
+ url = new URL(options.baseUrl);
16
+ }
17
+ catch {
18
+ throw new ValidationError("Invalid local daemon URL");
19
+ }
20
+ if (url.protocol !== "http:" || !["127.0.0.1", "[::1]"].includes(url.hostname)
21
+ || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
22
+ throw new ValidationError("Daemon URL must be a loopback HTTP origin");
23
+ }
24
+ if (typeof options.apiToken !== "string" || !options.apiToken
25
+ || /[^\x21-\x7e]/.test(options.apiToken)) {
26
+ throw new ValidationError("A local daemon bearer token is required");
27
+ }
28
+ this.#origin = url.origin;
29
+ this.#token = options.apiToken;
30
+ this.#timeoutMs = positiveInteger(options.timeoutMs ?? 45_000, "timeoutMs");
31
+ this.#maxResponseBytes = positiveInteger(options.maxResponseBytes ?? 64 * 1024 * 1024, "maxResponseBytes");
32
+ }
33
+ call(id, options = {}) {
34
+ if (this.#closed)
35
+ return Promise.reject(new ClientClosedError("SDK client is closed"));
36
+ let operation;
37
+ let url;
38
+ let body;
39
+ let timeoutMs;
40
+ try {
41
+ operation = operationFor(id);
42
+ url = requestURL(this.#origin, operation, options);
43
+ body = requestBody(operation, options.body);
44
+ timeoutMs = positiveInteger(options.timeoutMs ?? this.#timeoutMs, "timeoutMs");
45
+ }
46
+ catch (error) {
47
+ return Promise.reject(error);
48
+ }
49
+ const controller = new AbortController();
50
+ const cancel = () => controller.abort(new RequestCancelledError("SDK request cancelled"));
51
+ if (options.signal?.aborted)
52
+ cancel();
53
+ else
54
+ options.signal?.addEventListener("abort", cancel, { once: true });
55
+ const timer = setTimeout(() => {
56
+ controller.abort(new RequestTimeoutError("SDK request timed out; write outcome may be unknown"));
57
+ }, timeoutMs);
58
+ const run = async () => {
59
+ try {
60
+ const headers = { Authorization: `Bearer ${this.#token}` };
61
+ if (body !== undefined)
62
+ headers["Content-Type"] = "application/json";
63
+ const response = await fetch(url, {
64
+ method: operation.method, headers, redirect: "error", signal: controller.signal,
65
+ ...(body === undefined ? {} : { body }),
66
+ });
67
+ return await readResponse(response, operation, this.#maxResponseBytes, this.#token);
68
+ }
69
+ catch (error) {
70
+ if (controller.signal.aborted) {
71
+ const reason = controller.signal.reason;
72
+ if (reason instanceof AgentIMError)
73
+ throw reason;
74
+ throw new RequestCancelledError("SDK request cancelled");
75
+ }
76
+ if (error instanceof AgentIMError)
77
+ throw error;
78
+ throw new TransportError("Local daemon connection failed; write outcome may be unknown");
79
+ }
80
+ };
81
+ const pending = run().finally(() => {
82
+ clearTimeout(timer);
83
+ options.signal?.removeEventListener("abort", cancel);
84
+ this.#pending.delete(pending);
85
+ });
86
+ this.#pending.set(pending, controller);
87
+ return pending;
88
+ }
89
+ async close() {
90
+ this.#closed = true;
91
+ for (const controller of this.#pending.values()) {
92
+ controller.abort(new ClientClosedError("SDK client closed; write outcome may be unknown"));
93
+ }
94
+ await Promise.allSettled(this.#pending.keys());
95
+ }
96
+ }
97
+ function positiveInteger(value, name) {
98
+ if (!Number.isSafeInteger(value) || value < 1 || value > 2_147_483_647) {
99
+ throw new ValidationError(`${name} must be a positive bounded integer`);
100
+ }
101
+ return value;
102
+ }
@@ -0,0 +1,3 @@
1
+ import type { ErrorDiagnostics } from "../../api/errors.js";
2
+ /** Accept new diagnostics only on the versioned public registration operation. */
3
+ export declare function errorDiagnostics(value: Record<string, unknown>, enabled: boolean, token: string): ErrorDiagnostics;
@@ -0,0 +1,50 @@
1
+ import { ProtocolError } from "../../api/errors.js";
2
+ /** Accept new diagnostics only on the versioned public registration operation. */
3
+ export function errorDiagnostics(value, enabled, token) {
4
+ const keys = ["code", "message", ...(enabled ? ["request_id", "retry_after_ms", "server_code", "next_action", "edge_request_id", "stage", "transport", "upstream_status", "safe_to_retry", "attempt_id", "device_created"] : [])];
5
+ if (Object.keys(value).some((key) => !keys.includes(key)))
6
+ throw new ProtocolError("Invalid error envelope");
7
+ const requestId = value.request_id;
8
+ const retryAfterMs = value.retry_after_ms;
9
+ const serverCode = value.server_code;
10
+ const nextAction = value.next_action;
11
+ const edgeRequestId = value.edge_request_id;
12
+ if (edgeRequestId !== undefined && (typeof edgeRequestId !== "string" || !/^[A-Za-z0-9_-]{1,80}$/.test(edgeRequestId)))
13
+ throw new ProtocolError("Invalid edge correlation");
14
+ if (requestId !== undefined && (typeof requestId !== "string" || !/^[a-f0-9]{32}$/.test(requestId)))
15
+ throw new ProtocolError("Invalid request correlation");
16
+ if (retryAfterMs !== undefined && (typeof retryAfterMs !== "number" || !Number.isSafeInteger(retryAfterMs) || retryAfterMs < 0 || retryAfterMs > 86_400_000))
17
+ throw new ProtocolError("Invalid retry time");
18
+ if (serverCode !== undefined && (typeof serverCode !== "string" || !/^[A-Z0-9_]{1,64}$/.test(serverCode)))
19
+ throw new ProtocolError("Invalid server error code");
20
+ if (nextAction !== undefined && (typeof nextAction !== "string" || !/^[a-z_]{1,64}$/.test(nextAction)))
21
+ throw new ProtocolError("Invalid next action");
22
+ const more = {};
23
+ const strings = [["stage", "stage", ["matrix_login", "account_exchange", "restore_session", "persist_session"]],
24
+ ["transport", "transport", ["http", "connect", "timeout", "non_json", "sdk", "unknown"]],
25
+ ["device_created", "deviceCreated", ["yes", "no", "unknown"]]];
26
+ for (const [wire, field, allowed] of strings) {
27
+ const item = value[wire];
28
+ if (item !== undefined && (typeof item !== "string" || !allowed.includes(item)))
29
+ throw new ProtocolError("Invalid login diagnostic");
30
+ if (typeof item === "string")
31
+ Object.assign(more, { [field]: item });
32
+ }
33
+ if (value.upstream_status !== undefined && (typeof value.upstream_status !== "number" || !Number.isInteger(value.upstream_status) || value.upstream_status < 100 || value.upstream_status > 599))
34
+ throw new ProtocolError("Invalid upstream status");
35
+ if (value.safe_to_retry !== undefined && typeof value.safe_to_retry !== "boolean")
36
+ throw new ProtocolError("Invalid retry classification");
37
+ if (value.attempt_id !== undefined && (typeof value.attempt_id !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.attempt_id)))
38
+ throw new ProtocolError("Invalid login attempt");
39
+ return {
40
+ ...more,
41
+ ...(typeof value.upstream_status === "number" ? { upstreamStatus: value.upstream_status } : {}),
42
+ ...(typeof value.safe_to_retry === "boolean" ? { safeToRetry: value.safe_to_retry } : {}),
43
+ ...(typeof value.attempt_id === "string" && !value.attempt_id.includes(token) ? { attemptId: value.attempt_id } : {}),
44
+ ...(typeof edgeRequestId === "string" && !edgeRequestId.includes(token) ? { edgeRequestId } : {}),
45
+ ...(typeof requestId === "string" && !requestId.includes(token) ? { requestId } : {}),
46
+ ...(typeof retryAfterMs === "number" ? { retryAfterMs } : {}),
47
+ ...(typeof serverCode === "string" && !serverCode.includes(token) ? { serverCode } : {}),
48
+ ...(typeof nextAction === "string" && !nextAction.includes(token) ? { nextAction } : {}),
49
+ };
50
+ }
@@ -0,0 +1,9 @@
1
+ import type { RequestOptions } from "../../api/options.js";
2
+ import type { Operation } from "../protocol/registry.js";
3
+ export interface CallOptions extends RequestOptions {
4
+ readonly path?: Readonly<Record<string, unknown>>;
5
+ readonly query?: object;
6
+ readonly body?: unknown;
7
+ }
8
+ export declare function requestURL(origin: string, operation: Operation, options: CallOptions): URL;
9
+ export declare function requestBody(operation: Operation, body: unknown): string | undefined;
@@ -0,0 +1,55 @@
1
+ import { ValidationError } from "../../api/errors.js";
2
+ import { stringifyJSON } from "../protocol/codec.js";
3
+ import { snapshotInput } from "../protocol/input.js";
4
+ import { isRecord, schemaIssue } from "../protocol/schema.js";
5
+ export function requestURL(origin, operation, options) {
6
+ let path = operation.path;
7
+ const query = snapshotInput(options.query === undefined ? {} : options.query);
8
+ const pathValues = snapshotInput(options.path ?? {});
9
+ for (const [location, values] of [["path", pathValues], ["query", query]]) {
10
+ if (!isRecord(values)) {
11
+ throw new ValidationError(`Invalid ${location} parameters`);
12
+ }
13
+ const entries = Object.entries(values);
14
+ const expected = operation.parameters.filter((parameter) => parameter.in === location);
15
+ if (entries.some(([name]) => !expected.some((parameter) => parameter.name === name))) {
16
+ throw new ValidationError(`Unknown ${location} parameter`);
17
+ }
18
+ for (const parameter of expected) {
19
+ const value = values[parameter.name];
20
+ if (value === undefined && !Object.hasOwn(values, parameter.name) && !parameter.required)
21
+ continue;
22
+ const issue = schemaIssue(value, parameter.schema);
23
+ if (issue)
24
+ throw new ValidationError(`Invalid ${location} parameter ${parameter.name}: ${issue}`);
25
+ if (location === "path") {
26
+ path = path.replace(`{${parameter.name}}`, encodeURIComponent(String(value)));
27
+ }
28
+ }
29
+ }
30
+ if (/[{}]/.test(path))
31
+ throw new ValidationError("Missing path parameter");
32
+ const url = new URL(path, origin);
33
+ if (!isRecord(query))
34
+ throw new ValidationError("Invalid query parameters");
35
+ for (const [key, value] of Object.entries(query))
36
+ url.searchParams.set(key, String(value));
37
+ return url;
38
+ }
39
+ export function requestBody(operation, body) {
40
+ if (operation.request === null) {
41
+ if (body !== undefined)
42
+ throw new ValidationError("Operation has no request body");
43
+ return undefined;
44
+ }
45
+ const snapshot = snapshotInput(body);
46
+ const issue = schemaIssue(snapshot, operation.request);
47
+ if (issue)
48
+ throw new ValidationError(`Invalid request body: ${issue}`);
49
+ const encoded = stringifyJSON(snapshot);
50
+ const limit = Math.min(8 * 1024 * 1024, Number(operation.request["x-max-body-bytes"] ?? Infinity));
51
+ if (new TextEncoder().encode(encoded).byteLength > limit) {
52
+ throw new ValidationError("Request body exceeds the local API byte limit");
53
+ }
54
+ return encoded;
55
+ }
@@ -0,0 +1,2 @@
1
+ import type { Operation } from "../protocol/registry.js";
2
+ export declare function readResponse<T>(response: Response, operation: Operation, limit: number, token: string): Promise<T>;
@@ -0,0 +1,85 @@
1
+ import { AuthenticationError, OperationError, ProtocolError, ResponseTooLargeError } from "../../api/errors.js";
2
+ import { parseJSON } from "../protocol/codec.js";
3
+ import { isRecord, schemaIssue } from "../protocol/schema.js";
4
+ import { errorDiagnostics } from "./error-diagnostics.js";
5
+ async function readBounded(response, limit) {
6
+ const length = response.headers.get("content-length");
7
+ if (length !== null && (!/^\d+$/.test(length) || BigInt(length) > BigInt(limit))) {
8
+ await response.body?.cancel();
9
+ throw new ResponseTooLargeError("Response exceeds the configured byte budget");
10
+ }
11
+ if (!response.body)
12
+ return new Uint8Array();
13
+ const reader = response.body.getReader();
14
+ const chunks = [];
15
+ let total = 0;
16
+ try {
17
+ while (true) {
18
+ const { done, value } = await reader.read();
19
+ if (done)
20
+ break;
21
+ total += value.byteLength;
22
+ if (total > limit) {
23
+ await reader.cancel();
24
+ throw new ResponseTooLargeError("Response exceeds the configured byte budget");
25
+ }
26
+ chunks.push(value);
27
+ }
28
+ }
29
+ finally {
30
+ reader.releaseLock();
31
+ }
32
+ const result = new Uint8Array(total);
33
+ let offset = 0;
34
+ for (const chunk of chunks) {
35
+ result.set(chunk, offset);
36
+ offset += chunk.length;
37
+ }
38
+ return result;
39
+ }
40
+ export async function readResponse(response, operation, limit, token) {
41
+ const bytes = await readBounded(response, limit);
42
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
43
+ if (response.status === operation.status && operation.binary) {
44
+ if (contentType !== "application/octet-stream")
45
+ throw new ProtocolError("Expected attachment bytes");
46
+ return bytes;
47
+ }
48
+ if (contentType !== "application/json")
49
+ throw new ProtocolError("Expected a JSON response");
50
+ let text;
51
+ try {
52
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
53
+ }
54
+ catch {
55
+ throw new ProtocolError("Invalid UTF-8 response");
56
+ }
57
+ const value = parseJSON(text);
58
+ if (response.status !== operation.status) {
59
+ if (response.status >= 200 && response.status < 300) {
60
+ throw new ProtocolError("Unexpected success status");
61
+ }
62
+ if (!isRecord(value) || Object.keys(value).length !== 1 || !isRecord(value.error)
63
+ || typeof value.error.code !== "string"
64
+ || value.error.code.length < 1 || value.error.code.length > 64
65
+ || typeof value.error.message !== "string" || [...value.error.message].length > 512) {
66
+ throw new ProtocolError("Invalid error envelope");
67
+ }
68
+ const code = /^[A-Za-z0-9._:-]+$/.test(value.error.code) && !value.error.code.includes(token)
69
+ ? value.error.code : "remote_error";
70
+ const bodyDiagnostics = errorDiagnostics(value.error, operation.path.startsWith("/v2/identity/"), token);
71
+ const requestId = response.headers.get("x-agentim-request-id");
72
+ const diagnostics = !bodyDiagnostics.requestId && requestId && !requestId.includes(token) && /^[a-f0-9]{32}$/.test(requestId)
73
+ ? { ...bodyDiagnostics, requestId } : bodyDiagnostics;
74
+ // A daemon may reflect input in its message; retain only bounded status/code.
75
+ const ErrorClass = response.status === 401 ? AuthenticationError : OperationError;
76
+ throw new ErrorClass(response.status, code, `Local API rejected request (HTTP ${response.status})`, diagnostics);
77
+ }
78
+ if (operation.response === null)
79
+ throw new ProtocolError("Missing response schema");
80
+ const issue = schemaIssue(value, operation.response);
81
+ if (issue)
82
+ throw new ProtocolError(`Response does not match the local API contract: ${issue}`);
83
+ // Narrow only after checking the canonical operation response at the boundary.
84
+ return value;
85
+ }
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "@agentims/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.1",
4
+ "description": "Typed Node.js client for the AgentIM local daemon API",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "engines": {
8
+ "node": ">=24.22.2 <25"
9
+ },
10
+ "packageManager": "pnpm@10.32.1",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "node scripts/compiler.mjs build",
24
+ "typecheck": "node scripts/compiler.mjs typecheck",
25
+ "format:check": "node scripts/source-check.mjs format",
26
+ "lint": "node scripts/source-check.mjs lint",
27
+ "test": "node scripts/run-tests.mjs",
28
+ "test:consumer": "node tests/consumer/package-check.mjs",
29
+ "pack:check": "node tests/consumer/package-check.mjs"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "homepage": "https://agentims.shipkitx.com/downloads/agent/"
36
+ }