@method-ai-hq/sdk 0.0.0-stage → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +23 -2
- package/dist/packages/contracts/src/identity.d.ts +16 -0
- package/dist/packages/contracts/src/identity.js +41 -0
- package/dist/packages/contracts/src/method-package.d.ts +71 -0
- package/dist/packages/contracts/src/method-package.js +22 -0
- package/dist/packages/method-document/src/conversion-report.d.ts +138 -0
- package/dist/packages/method-document/src/conversion-report.js +23 -0
- package/dist/packages/sdk/examples/daily-briefing/README.md +31 -0
- package/dist/packages/sdk/examples/daily-briefing/TASK.md +3 -0
- package/dist/packages/sdk/examples/daily-briefing/approved-report.md +138 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_artifacts.py +39 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_check_inputs.py +47 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_files.py +31 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_manifest.py +23 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_markdown.py +67 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_progress.py +18 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_render.py +71 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_sessions.py +37 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_times.py +158 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_validation.py +101 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_website.py +215 -0
- package/dist/packages/sdk/examples/daily-briefing/checks.json +19 -0
- package/dist/packages/sdk/examples/daily-briefing/daily-briefing.method +158 -0
- package/dist/packages/sdk/examples/daily-briefing/inputs.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/package-lock.json +10 -0
- package/dist/packages/sdk/examples/daily-briefing/package.json +4 -0
- package/dist/packages/sdk/examples/daily-briefing/pyproject.toml +8 -0
- package/dist/packages/sdk/examples/daily-briefing/reader/reader.css +35 -0
- package/dist/packages/sdk/examples/daily-briefing/reader/reader.js +42 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/context.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/documents/document-001.md +219 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/media.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/metadata/documents.json +39 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0001.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0002.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0003.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0004.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0005.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0006.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0007.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0008.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0009.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0010.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0011.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0012.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0013.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0014.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0015.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0016.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0017.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0018.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0019.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0020.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0021.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0022.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0023.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0024.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0025.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0026.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0027.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0028.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/sessions.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/timeline.jsonl +28 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/untimed.jsonl +0 -0
- package/dist/packages/sdk/examples/daily-briefing/sample-output.zip +0 -0
- package/dist/packages/sdk/examples/daily-briefing/sample-report.md +51 -0
- package/dist/packages/sdk/examples/daily-briefing/uv.lock +128 -0
- package/dist/packages/sdk/examples/daily-briefing/vendor/marked-LICENSE.md +44 -0
- package/dist/packages/sdk/examples/daily-briefing/vendor/marked.mjs +77 -0
- package/dist/packages/sdk/examples/files.json +126 -0
- package/dist/packages/sdk/examples/message-routing/README.md +23 -0
- package/dist/packages/sdk/examples/message-routing/TASK.md +5 -0
- package/dist/packages/sdk/examples/message-routing/choose-destination.mjs +10 -0
- package/dist/packages/sdk/examples/message-routing/create-ticket.mjs +15 -0
- package/dist/packages/sdk/examples/message-routing/inputs.json +1 -0
- package/dist/packages/sdk/examples/message-routing/message-routing.method +45 -0
- package/dist/packages/sdk/examples/message-routing/result.fixture.json +6 -0
- package/dist/packages/sdk/examples/message-routing/routing.mjs +29 -0
- package/dist/packages/sdk/examples/message-routing/routing.test.mjs +27 -0
- package/dist/packages/sdk/examples/message-routing/ticket.method +88 -0
- package/dist/packages/sdk/examples/message-routing/ticket.test.mjs +64 -0
- package/dist/packages/sdk/examples/notes-summary/README.md +23 -0
- package/dist/packages/sdk/examples/notes-summary/TASK.md +3 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/case.json +36 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/examples.json +16 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/files/0 +10 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/recording.json +195 -0
- package/dist/packages/sdk/examples/notes-summary/notes/2026-10-05-standup.md +4 -0
- package/dist/packages/sdk/examples/notes-summary/notes/2026-10-07-planning.md +3 -0
- package/dist/packages/sdk/examples/notes-summary/notes-summary.method +54 -0
- package/dist/packages/sdk/examples/notes-summary/out/.keep +0 -0
- package/dist/packages/sdk/examples/notes-summary/read-notes.mjs +4 -0
- package/dist/packages/sdk/examples/notes-summary/save.mjs +7 -0
- package/dist/packages/sdk/examples/outbound-management/README.md +119 -0
- package/dist/packages/sdk/examples/outbound-management/TASK.md +5 -0
- package/dist/packages/sdk/examples/outbound-management/check_plan.py +16 -0
- package/dist/packages/sdk/examples/outbound-management/check_sources.py +14 -0
- package/dist/packages/sdk/examples/outbound-management/crm.py +153 -0
- package/dist/packages/sdk/examples/outbound-management/crm_test.py +141 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/README.md +15 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/crm.json +54 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/day-1/messages.json +29 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/day-2/messages.json +38 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/observations.json +12 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/plan.json +69 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/receipt.json +1 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/state.json +99 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/tasks.md +16 -0
- package/dist/packages/sdk/examples/outbound-management/inputs.json +4 -0
- package/dist/packages/sdk/examples/outbound-management/outbound.method +277 -0
- package/dist/packages/sdk/examples/outbound-management/read_crm.py +4 -0
- package/dist/packages/sdk/examples/outbound-management/save_day.py +4 -0
- package/dist/packages/sdk/examples/outbound-management/starter/crm.json +11 -0
- package/dist/packages/sdk/examples/social-briefing/README.md +41 -0
- package/dist/packages/sdk/examples/social-briefing/TASK.md +5 -0
- package/dist/packages/sdk/examples/social-briefing/inputs.json +5 -0
- package/dist/packages/sdk/examples/social-briefing/result.fixture.json +14 -0
- package/dist/packages/sdk/examples/social-briefing/social-briefing.method +110 -0
- package/dist/packages/sdk/examples/support-triage/README.md +77 -0
- package/dist/packages/sdk/examples/support-triage/TASK.md +3 -0
- package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/case.json +40 -0
- package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/recording.json +460 -0
- package/dist/packages/sdk/examples/support-triage/fake-helpdesk.mjs +38 -0
- package/dist/packages/sdk/examples/support-triage/helpdesk.mjs +33 -0
- package/dist/packages/sdk/examples/support-triage/inputs.json +1 -0
- package/dist/packages/sdk/examples/support-triage/read-ticket.mjs +8 -0
- package/dist/packages/sdk/examples/support-triage/route.mjs +4 -0
- package/dist/packages/sdk/examples/support-triage/sample-tickets.json +5 -0
- package/dist/packages/sdk/examples/support-triage/set-ticket.mjs +9 -0
- package/dist/packages/sdk/examples/support-triage/support-triage.method +165 -0
- package/dist/packages/sdk/examples/support-triage/triage.mjs +38 -0
- package/dist/packages/sdk/examples/support-triage/triage.test.mjs +68 -0
- package/dist/packages/sdk/observers/mail.delivery/fetch.mjs +87 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce-by-recipient.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/delayed.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/delivered.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/empty.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/other-message.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/judge.mjs +13 -0
- package/dist/packages/sdk/observers/mail.delivery/observer.json +14 -0
- package/dist/packages/sdk/package.json +232 -0
- package/dist/packages/sdk/src/authoring-example.d.ts +91 -0
- package/dist/packages/sdk/src/authoring-example.js +50 -0
- package/dist/packages/sdk/src/authoring-instructions.d.ts +20 -0
- package/dist/packages/sdk/src/authoring-instructions.js +151 -0
- package/dist/packages/sdk/src/authoring-prompt.d.ts +2 -0
- package/dist/packages/sdk/src/authoring-prompt.js +8 -0
- package/dist/packages/sdk/src/authoring.d.ts +10 -0
- package/dist/packages/sdk/src/authoring.js +287 -0
- package/dist/packages/sdk/src/bindings.d.ts +4 -0
- package/dist/packages/sdk/src/bindings.js +124 -0
- package/dist/packages/sdk/src/bridge.d.ts +2 -0
- package/dist/packages/sdk/src/bridge.js +23 -0
- package/dist/packages/sdk/src/browser-runtime/pyproject.toml +5 -0
- package/dist/packages/sdk/src/browser-runtime/tools.json +189 -0
- package/dist/packages/sdk/src/browser-runtime/uv.lock +3894 -0
- package/dist/packages/sdk/src/browser-service.py +127 -0
- package/dist/packages/sdk/src/browser.d.ts +38 -0
- package/dist/packages/sdk/src/browser.js +203 -0
- package/dist/packages/sdk/src/capabilities.d.ts +13 -0
- package/dist/packages/sdk/src/capabilities.js +68 -0
- package/dist/packages/sdk/src/classification-client.d.ts +4 -0
- package/dist/packages/sdk/src/classification-client.js +27 -0
- package/dist/packages/sdk/src/cloud.d.ts +23 -0
- package/dist/packages/sdk/src/cloud.js +43 -0
- package/dist/packages/sdk/src/computer-settings.d.ts +28 -0
- package/dist/packages/sdk/src/computer-settings.js +57 -0
- package/dist/packages/sdk/src/connect.d.ts +47 -0
- package/dist/packages/sdk/src/connect.js +203 -0
- package/dist/packages/sdk/src/current-inspection.d.ts +3 -0
- package/dist/packages/sdk/src/current-inspection.js +146 -0
- package/dist/packages/sdk/src/current-runtime.d.ts +14 -0
- package/dist/packages/sdk/src/current-runtime.js +167 -0
- package/dist/packages/sdk/src/doctor.d.ts +17 -0
- package/dist/packages/sdk/src/doctor.js +68 -0
- package/dist/packages/sdk/src/executable.d.ts +2 -0
- package/dist/packages/sdk/src/executable.js +22 -0
- package/dist/packages/sdk/src/explain-effects.d.ts +32 -0
- package/dist/packages/sdk/src/explain-effects.js +192 -0
- package/dist/packages/sdk/src/explain.d.ts +115 -0
- package/dist/packages/sdk/src/explain.js +441 -0
- package/dist/packages/sdk/src/files.d.ts +9 -0
- package/dist/packages/sdk/src/files.js +50 -0
- package/dist/packages/sdk/src/guard-keys.d.ts +5 -0
- package/dist/packages/sdk/src/guard-keys.js +42 -0
- package/dist/packages/sdk/src/hosted-models.d.ts +49 -0
- package/dist/packages/sdk/src/hosted-models.js +81 -0
- package/dist/packages/sdk/src/improve.d.ts +55 -0
- package/dist/packages/sdk/src/improve.js +159 -0
- package/dist/packages/sdk/src/index.d.ts +13 -0
- package/dist/packages/sdk/src/index.js +13 -0
- package/dist/packages/sdk/src/inspect.d.ts +6 -0
- package/dist/packages/sdk/src/inspect.js +17 -0
- package/dist/packages/sdk/src/link.d.ts +12 -0
- package/dist/packages/sdk/src/link.js +53 -0
- package/dist/packages/sdk/src/local-cli.d.ts +24 -0
- package/dist/packages/sdk/src/local-cli.js +146 -0
- package/dist/packages/sdk/src/local-setup.d.ts +15 -0
- package/dist/packages/sdk/src/local-setup.js +42 -0
- package/dist/packages/sdk/src/method-client.d.ts +30 -0
- package/dist/packages/sdk/src/method-client.js +250 -0
- package/dist/packages/sdk/src/method-files.d.ts +29 -0
- package/dist/packages/sdk/src/method-files.js +93 -0
- package/dist/packages/sdk/src/method-help.d.ts +19 -0
- package/dist/packages/sdk/src/method-help.js +404 -0
- package/dist/packages/sdk/src/method-issues.d.ts +86 -0
- package/dist/packages/sdk/src/method-issues.js +192 -0
- package/dist/packages/sdk/src/method-sync.d.ts +46 -0
- package/dist/packages/sdk/src/method-sync.js +293 -0
- package/dist/packages/sdk/src/method.d.ts +5 -0
- package/dist/packages/sdk/src/method.js +359 -0
- package/dist/packages/sdk/src/model-checks.d.ts +96 -0
- package/dist/packages/sdk/src/model-checks.js +181 -0
- package/dist/packages/sdk/src/outbox.d.ts +120 -0
- package/dist/packages/sdk/src/outbox.js +297 -0
- package/dist/packages/sdk/src/own-model-key.d.ts +22 -0
- package/dist/packages/sdk/src/own-model-key.js +76 -0
- package/dist/packages/sdk/src/prepare.d.ts +24 -0
- package/dist/packages/sdk/src/prepare.js +245 -0
- package/dist/packages/sdk/src/process.d.ts +15 -0
- package/dist/packages/sdk/src/process.js +107 -0
- package/dist/packages/sdk/src/production.d.ts +182 -0
- package/dist/packages/sdk/src/production.js +284 -0
- package/dist/packages/sdk/src/progress.d.ts +12 -0
- package/dist/packages/sdk/src/progress.js +48 -0
- package/dist/packages/sdk/src/proposals.d.ts +73 -0
- package/dist/packages/sdk/src/proposals.js +284 -0
- package/dist/packages/sdk/src/quality.d.ts +82 -0
- package/dist/packages/sdk/src/quality.js +294 -0
- package/dist/packages/sdk/src/result-files.d.ts +5 -0
- package/dist/packages/sdk/src/result-files.js +149 -0
- package/dist/packages/sdk/src/result-transfer.d.ts +21 -0
- package/dist/packages/sdk/src/result-transfer.js +37 -0
- package/dist/packages/sdk/src/run-data.d.ts +11 -0
- package/dist/packages/sdk/src/run-data.js +80 -0
- package/dist/packages/sdk/src/run-method.d.ts +2 -0
- package/dist/packages/sdk/src/run-method.js +2 -0
- package/dist/packages/sdk/src/run-saved.d.ts +5 -0
- package/dist/packages/sdk/src/run-saved.js +138 -0
- package/dist/packages/sdk/src/run-worker.d.ts +6 -0
- package/dist/packages/sdk/src/run-worker.js +131 -0
- package/dist/packages/sdk/src/secrets.d.ts +37 -0
- package/dist/packages/sdk/src/secrets.js +196 -0
- package/dist/packages/sdk/src/update.d.ts +5 -0
- package/dist/packages/sdk/src/update.js +39 -0
- package/dist/packages/sdk/src/versions.d.ts +58 -0
- package/dist/packages/sdk/src/versions.js +144 -0
- package/dist/packages/sdk/src/worker.d.ts +120 -0
- package/dist/packages/sdk/src/worker.js +443 -0
- package/dist/packages/workflow-language/src/execution.d.ts +1 -0
- package/dist/packages/workflow-language/src/execution.js +1 -0
- package/dist/packages/workflow-language/src/inspection.d.ts +990 -0
- package/dist/packages/workflow-language/src/inspection.js +130 -0
- package/dist/packages/workflow-language/src/result-files.d.ts +160 -0
- package/dist/packages/workflow-language/src/result-files.js +67 -0
- package/dist/packages/workflow-language/src/schema.d.ts +207 -0
- package/dist/packages/workflow-language/src/schema.js +56 -0
- package/dist/packages/workflow-language/src/validate.d.ts +11 -0
- package/dist/packages/workflow-language/src/validate.js +77 -0
- package/package.json +230 -4
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { assertSchema } from "@withmethod/runtime/validate.js";
|
|
5
|
+
import { DEFAULT_SERVER, MethodClient, serverOrigin } from "./method-client.js";
|
|
6
|
+
import { inputsDigest, open, seal } from "./run-data.js";
|
|
7
|
+
import { ProductionWorker } from "./worker.js";
|
|
8
|
+
export class MethodApiError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
body;
|
|
12
|
+
constructor(status, code, message, body) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.body = body;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const terminal = new Set(["succeeded", "failed", "cancelled"]);
|
|
20
|
+
/** The production API client. Every request sends Authorization: Bearer CREDENTIAL. */
|
|
21
|
+
export class ServiceApi {
|
|
22
|
+
apiKey;
|
|
23
|
+
fetcher;
|
|
24
|
+
server;
|
|
25
|
+
constructor(apiKey, server, fetcher = fetch) {
|
|
26
|
+
this.apiKey = apiKey;
|
|
27
|
+
this.fetcher = fetcher;
|
|
28
|
+
if (!/^(mk_live_|method_)[A-Za-z0-9_-]{43}$/.test(apiKey))
|
|
29
|
+
throw Error("Use a Method service key (mk_live_…). Run method connect APP_FOLDER to make one, or method login on this computer.");
|
|
30
|
+
this.server = serverOrigin(server);
|
|
31
|
+
}
|
|
32
|
+
async request(path, method = "GET", body, timeoutMs = 30_000, signal) {
|
|
33
|
+
const response = await this.send(path, method, body === undefined ? undefined : JSON.stringify(body), body === undefined ? {} : { "content-type": "application/json" }, timeoutMs, signal);
|
|
34
|
+
if (response.status === 204)
|
|
35
|
+
return { status: 204, body: null };
|
|
36
|
+
const text = await response.text();
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new MethodApiError(response.status, "invalid_response", `${response.status}: Method returned a non-JSON response (${method} ${path}).`);
|
|
43
|
+
}
|
|
44
|
+
if (!response.ok)
|
|
45
|
+
throw new MethodApiError(response.status, String(parsed.code ?? parsed.error ?? "request_failed"), `${response.status}: ${String(parsed.message ?? parsed.error ?? "Method request failed")}`, parsed);
|
|
46
|
+
return { status: response.status, body: parsed };
|
|
47
|
+
}
|
|
48
|
+
/** Send or get raw bytes (a run folder). */
|
|
49
|
+
async bytes(path, method, body, headers = {}) {
|
|
50
|
+
const response = await this.send(path, method, body, { ...headers, ...(body ? { "content-type": "application/octet-stream" } : {}) }, 120_000);
|
|
51
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
let parsed = {};
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(data.toString("utf8"));
|
|
56
|
+
}
|
|
57
|
+
catch { /* Not JSON. */ }
|
|
58
|
+
throw new MethodApiError(response.status, String(parsed.code ?? "request_failed"), `${response.status}: ${String(parsed.message ?? "Method request failed")}`, parsed);
|
|
59
|
+
}
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
send(path, method, body, headers, timeoutMs, signal) {
|
|
63
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
64
|
+
return this.fetcher(this.server + path, {
|
|
65
|
+
method, redirect: "error", signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
66
|
+
headers: { authorization: `Bearer ${this.apiKey}`, ...headers }, ...(body === undefined ? {} : { body: body }),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Verify a webhook: Method-Signature is t=TIMESTAMP,v1=HEX(HMAC-SHA256(secret, `${t}.${body}`)). Pass the raw body. */
|
|
71
|
+
export function verifyWebhook(body, signature, secret, options = {}) {
|
|
72
|
+
const match = signature?.match(/^t=(\d+),v1=([a-f0-9]{64})$/);
|
|
73
|
+
if (!match)
|
|
74
|
+
return false;
|
|
75
|
+
const age = Math.abs((options.now ?? Date.now()) / 1000 - Number(match[1]));
|
|
76
|
+
if (age > (options.toleranceSeconds ?? 300))
|
|
77
|
+
return false;
|
|
78
|
+
const text = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
|
|
79
|
+
const expected = createHmac("sha256", secret).update(`${match[1]}.${text}`).digest();
|
|
80
|
+
return timingSafeEqual(expected, Buffer.from(match[2], "hex"));
|
|
81
|
+
}
|
|
82
|
+
/** METHOD_API_KEY and METHOD_SERVER from .env in a folder. Other lines are not read. */
|
|
83
|
+
export function dotenvValues(folder = process.cwd()) {
|
|
84
|
+
const file = join(folder, ".env");
|
|
85
|
+
if (!existsSync(file))
|
|
86
|
+
return {};
|
|
87
|
+
const found = {};
|
|
88
|
+
for (const line of readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
89
|
+
const match = line.match(/^\s*(?:export\s+)?(METHOD_API_KEY|METHOD_SERVER)\s*=\s*(.*?)\s*$/);
|
|
90
|
+
if (match)
|
|
91
|
+
found[match[1]] = match[2].replace(/^(['"])(.*)\1$/, "$2");
|
|
92
|
+
}
|
|
93
|
+
return found;
|
|
94
|
+
}
|
|
95
|
+
/** The credential of the libraries: apiKey, METHOD_API_KEY, .env, then the CLI sign-in. */
|
|
96
|
+
export function resolveCredential(options = {}) {
|
|
97
|
+
const dotenv = dotenvValues();
|
|
98
|
+
const server = options.server ?? process.env.METHOD_SERVER ?? dotenv.METHOD_SERVER ?? DEFAULT_SERVER;
|
|
99
|
+
const key = options.apiKey ?? process.env.METHOD_API_KEY ?? dotenv.METHOD_API_KEY;
|
|
100
|
+
if (key)
|
|
101
|
+
return { key, server };
|
|
102
|
+
let signedIn = null;
|
|
103
|
+
try {
|
|
104
|
+
signedIn = new MethodClient(server).token();
|
|
105
|
+
}
|
|
106
|
+
catch { /* No sign-in for this server. */ }
|
|
107
|
+
if (signedIn)
|
|
108
|
+
return { key: signedIn, server };
|
|
109
|
+
throw Error("Set METHOD_API_KEY (run method connect APP_FOLDER to write it to the app's .env), or sign in on this computer with method login.");
|
|
110
|
+
}
|
|
111
|
+
export class Method {
|
|
112
|
+
options;
|
|
113
|
+
api;
|
|
114
|
+
/** Results of runs that this process's worker finished. A device run's result exists only here. */
|
|
115
|
+
local = new Map();
|
|
116
|
+
waiting = new Map();
|
|
117
|
+
handlers = new Map();
|
|
118
|
+
asked = new Set();
|
|
119
|
+
runData = new Map();
|
|
120
|
+
lazyMethods = new Set();
|
|
121
|
+
lazyWorker;
|
|
122
|
+
lazyRuns = 0;
|
|
123
|
+
runs = {
|
|
124
|
+
start: async (options) => {
|
|
125
|
+
const inputs = options.inputs ?? {};
|
|
126
|
+
const send = async (sealed) => (await this.api.request("/v1/runs", "POST", {
|
|
127
|
+
method: options.method, ...(options.version ? { version: options.version } : {}),
|
|
128
|
+
...(sealed ? { inputs: {}, sealed_inputs: seal(this.api.apiKey, "inputs", options.method, inputs), inputs_digest: inputsDigest(this.api.apiKey, options.method, inputs) } : { inputs }),
|
|
129
|
+
...(options.idempotencyKey ? { idempotency_key: options.idempotencyKey } : {}), ...(options.webhookUrl ? { webhook_url: options.webhookUrl } : {}),
|
|
130
|
+
...(options.onAsk ?? this.options.onAsk ? { answered_by: "app" } : {}),
|
|
131
|
+
})).body;
|
|
132
|
+
// A device run's inputs are sealed here. The server says which Methods keep run content on devices.
|
|
133
|
+
const sealIt = Object.keys(inputs).length > 0 && await this.keepsOnDevice(options.method);
|
|
134
|
+
let started;
|
|
135
|
+
try {
|
|
136
|
+
started = await send(sealIt);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (!(error instanceof MethodApiError && error.code === "seal_inputs"))
|
|
140
|
+
throw error;
|
|
141
|
+
this.runData.set(options.method, { value: "device", at: Date.now() });
|
|
142
|
+
started = await send(true);
|
|
143
|
+
}
|
|
144
|
+
const handler = options.onAsk ?? this.options.onAsk;
|
|
145
|
+
if (handler)
|
|
146
|
+
this.handlers.set(started.run_id, handler);
|
|
147
|
+
return started;
|
|
148
|
+
},
|
|
149
|
+
get: async (runId) => this.withLocal((await this.api.request(`/v1/runs/${encodeURIComponent(runId)}`)).body),
|
|
150
|
+
cancel: async (runId) => this.withLocal((await this.api.request(`/v1/runs/${encodeURIComponent(runId)}/cancel`, "POST", {})).body),
|
|
151
|
+
/** Answer the waiting question of a run. The answer must match the question's form. */
|
|
152
|
+
answer: async (runId, answer) => {
|
|
153
|
+
const run = await this.runs.get(runId);
|
|
154
|
+
if (run.status !== "waiting" || !run.question)
|
|
155
|
+
throw new MethodApiError(409, "not_waiting", `The run is ${run.status}; it has no question to answer.`);
|
|
156
|
+
const asked = this.openQuestion(run);
|
|
157
|
+
try {
|
|
158
|
+
assertSchema(asked.form, answer, "Answer");
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
throw new MethodApiError(422, "invalid_answer", error.message);
|
|
162
|
+
}
|
|
163
|
+
const body = run.question.sealed ? { step: run.question.step, sealed_answer: seal(this.api.apiKey, "answer", run.method_id, answer) } : { step: run.question.step, answer };
|
|
164
|
+
return this.withLocal((await this.api.request(`/v1/runs/${encodeURIComponent(runId)}/answer`, "POST", body)).body);
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* Wait until the run ends. Checks the server every intervalMs, and returns at once when this process's worker finishes
|
|
168
|
+
* it. A question of a run with an ask handler goes to the handler.
|
|
169
|
+
*/
|
|
170
|
+
wait: async (runId, options = {}) => {
|
|
171
|
+
const deadline = Date.now() + (options.timeoutMs ?? 30 * 60_000);
|
|
172
|
+
for (;;) {
|
|
173
|
+
const run = await this.runs.get(runId);
|
|
174
|
+
// This process's worker saves a result before it completes the run, so a finished run has its local result here.
|
|
175
|
+
if (terminal.has(run.status)) {
|
|
176
|
+
this.handlers.delete(runId);
|
|
177
|
+
return run;
|
|
178
|
+
}
|
|
179
|
+
if (run.status === "waiting" && run.question)
|
|
180
|
+
await this.ask(run, options.onAsk);
|
|
181
|
+
if (Date.now() > deadline)
|
|
182
|
+
throw new MethodApiError(408, "wait_timeout", `The run is still ${run.status}.`);
|
|
183
|
+
await new Promise(resolve => {
|
|
184
|
+
const wake = () => { clearTimeout(timer); this.waiting.get(runId)?.delete(wake); resolve(); };
|
|
185
|
+
const timer = setTimeout(wake, Math.min(options.intervalMs ?? 2_000, Math.max(0, deadline - Date.now())));
|
|
186
|
+
this.waiting.set(runId, (this.waiting.get(runId) ?? new Set()).add(wake));
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
/** Start a run and wait for its result. Needs a worker: method.run() starts one in this process. */
|
|
191
|
+
run: async (options) => this.runs.wait((await this.runs.start(options)).run_id, options),
|
|
192
|
+
};
|
|
193
|
+
worker = {
|
|
194
|
+
/** Claim and run queued runs in this process. Results reach onResult, runs.wait, and the dashboard. */
|
|
195
|
+
start: (options = {}) => {
|
|
196
|
+
const worker = new ProductionWorker(this.api, { ...options,
|
|
197
|
+
onResult: async (result) => {
|
|
198
|
+
this.local.set(result.run_id, result);
|
|
199
|
+
this.wake(result.run_id);
|
|
200
|
+
await options.onResult?.(result);
|
|
201
|
+
},
|
|
202
|
+
onWaiting: async (waiting) => { this.wake(waiting.run_id); await options.onWaiting?.(waiting); } });
|
|
203
|
+
worker.start();
|
|
204
|
+
return worker;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
webhooks = { verify: verifyWebhook };
|
|
208
|
+
static webhooks = { verify: verifyWebhook };
|
|
209
|
+
constructor(options = {}) {
|
|
210
|
+
this.options = options;
|
|
211
|
+
const { key, server } = resolveCredential(options);
|
|
212
|
+
this.api = new ServiceApi(key, server, options.fetch);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Run a published Method and wait for its result. The first call starts a worker in this process (unless the options
|
|
216
|
+
* say worker: false), so the run needs nothing else running. The worker stops when no method.run() call is waiting,
|
|
217
|
+
* so a script that calls method.run() exits when it is done; close() is not needed.
|
|
218
|
+
*/
|
|
219
|
+
async run(options) {
|
|
220
|
+
this.ensureWorker(options.method);
|
|
221
|
+
this.lazyRuns++;
|
|
222
|
+
try {
|
|
223
|
+
return await this.runs.run(options);
|
|
224
|
+
}
|
|
225
|
+
// The worker stops claiming at once; a run it holds still finishes. run() returns without waiting for that.
|
|
226
|
+
finally {
|
|
227
|
+
if (--this.lazyRuns === 0)
|
|
228
|
+
void this.close().catch(() => { });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** Stop this process's worker. Runs in progress finish first. */
|
|
232
|
+
async close(options = {}) {
|
|
233
|
+
const worker = this.lazyWorker;
|
|
234
|
+
this.lazyWorker = undefined;
|
|
235
|
+
await worker?.stop(options);
|
|
236
|
+
}
|
|
237
|
+
ensureWorker(method) {
|
|
238
|
+
if (this.options.worker === false)
|
|
239
|
+
return;
|
|
240
|
+
this.lazyMethods.add(method);
|
|
241
|
+
if (this.lazyWorker?.running)
|
|
242
|
+
return;
|
|
243
|
+
const extra = typeof this.options.worker === "object" ? this.options.worker : {};
|
|
244
|
+
this.lazyWorker = this.worker.start({ ...extra, methods: this.lazyMethods });
|
|
245
|
+
}
|
|
246
|
+
wake(runId) { for (const wake of this.waiting.get(runId) ?? [])
|
|
247
|
+
wake(); }
|
|
248
|
+
async keepsOnDevice(method) {
|
|
249
|
+
const known = this.runData.get(method);
|
|
250
|
+
if (known && Date.now() - known.at < 5 * 60_000)
|
|
251
|
+
return known.value === "device";
|
|
252
|
+
try {
|
|
253
|
+
const found = (await this.api.request(`/v1/methods/${encodeURIComponent(method)}`)).body;
|
|
254
|
+
this.runData.set(method, { value: found.run_data, at: Date.now() });
|
|
255
|
+
return found.run_data === "device";
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return false; /* The server refuses plain inputs of a device run (seal_inputs), and start seals them then. */
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
openQuestion(run) {
|
|
262
|
+
const asked = run.question;
|
|
263
|
+
if (!asked.sealed)
|
|
264
|
+
return { question: asked.question ?? "", form: asked.form ?? {} };
|
|
265
|
+
return open(this.api.apiKey, "question", run.method_id, asked.sealed);
|
|
266
|
+
}
|
|
267
|
+
/** Give a waiting question to the run's ask handler, once. */
|
|
268
|
+
async ask(run, override) {
|
|
269
|
+
const handler = override ?? this.handlers.get(run.run_id);
|
|
270
|
+
const question = run.question, id = `${question.id}:${question.asked_at}`;
|
|
271
|
+
if (!handler || this.asked.has(id))
|
|
272
|
+
return;
|
|
273
|
+
this.asked.add(id);
|
|
274
|
+
const opened = this.openQuestion(run);
|
|
275
|
+
const answer = (value) => this.runs.answer(run.run_id, value);
|
|
276
|
+
const value = await handler({ run_id: run.run_id, step: question.step, question: opened.question, form: opened.form, answer });
|
|
277
|
+
if (value && typeof value === "object")
|
|
278
|
+
await answer(value);
|
|
279
|
+
}
|
|
280
|
+
withLocal(run) {
|
|
281
|
+
const local = this.local.get(run.run_id);
|
|
282
|
+
return local && local.result !== undefined && run.result === undefined ? { ...run, result: local.result } : run;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ProgressUpdate = {
|
|
2
|
+
message: string;
|
|
3
|
+
completed?: number;
|
|
4
|
+
total?: number;
|
|
5
|
+
unit?: string;
|
|
6
|
+
child?: string;
|
|
7
|
+
};
|
|
8
|
+
/** Opt-in public text only. Does not write to the script's result stdout. */
|
|
9
|
+
export declare function reportProgress(update: ProgressUpdate): boolean;
|
|
10
|
+
/** Relay only supported public Codex JSON events from a child process. */
|
|
11
|
+
export declare function relayCodexProgress(stream: AsyncIterable<Uint8Array>, child: string): Promise<void>;
|
|
12
|
+
export declare function progressMain(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { writeSync } from "node:fs";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { progressMessage, codexProgress } from "@withmethod/runtime/progress.js";
|
|
4
|
+
import { readLines } from "@withmethod/runtime/io.js";
|
|
5
|
+
/** Opt-in public text only. Does not write to the script's result stdout. */
|
|
6
|
+
export function reportProgress(update) {
|
|
7
|
+
const value = progressMessage(update);
|
|
8
|
+
const fd = Number(process.env.METHOD_PROGRESS_FD);
|
|
9
|
+
if (!value || !Number.isInteger(fd) || fd < 3)
|
|
10
|
+
return false;
|
|
11
|
+
try {
|
|
12
|
+
writeSync(fd, JSON.stringify(value) + "\n");
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (["EPIPE", "EBADF"].includes(error.code ?? ""))
|
|
17
|
+
return false;
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Relay only supported public Codex JSON events from a child process. */
|
|
22
|
+
export async function relayCodexProgress(stream, child) {
|
|
23
|
+
await readLines(stream, async (line) => {
|
|
24
|
+
let event;
|
|
25
|
+
try {
|
|
26
|
+
event = JSON.parse(line);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const value = codexProgress(event);
|
|
32
|
+
if (value)
|
|
33
|
+
reportProgress({ ...value, child });
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function progressMain(args) {
|
|
37
|
+
const { values } = parseArgs({ args, options: { message: { type: "string" }, completed: { type: "string" }, total: { type: "string" }, unit: { type: "string" }, child: { type: "string" }, codex: { type: "boolean" } }, strict: true });
|
|
38
|
+
if (values.codex) {
|
|
39
|
+
if (!values.child || values.message)
|
|
40
|
+
throw Error("Use method progress --codex --child NAME with Codex JSON on stdin.");
|
|
41
|
+
await relayCodexProgress(process.stdin, values.child);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (!values.message)
|
|
45
|
+
throw Error("Supply --message TEXT, or --codex --child NAME.");
|
|
46
|
+
reportProgress({ message: values.message, ...(values.child ? { child: values.child } : {}), ...(values.unit ? { unit: values.unit } : {}), ...(values.completed !== undefined ? { completed: Number(values.completed) } : {}), ...(values.total !== undefined ? { total: Number(values.total) } : {}) });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type MethodClient } from './method-client.js';
|
|
2
|
+
/** A proposed change to a Method, from the account (a server proposal) or from `method improve` on this computer. */
|
|
3
|
+
export type ProposalRecord = {
|
|
4
|
+
id: string;
|
|
5
|
+
source: 'account' | 'local';
|
|
6
|
+
method_id: string | null;
|
|
7
|
+
base_version_id: string | null;
|
|
8
|
+
status: string;
|
|
9
|
+
cause: string;
|
|
10
|
+
evidence: any;
|
|
11
|
+
created_at: string;
|
|
12
|
+
base_workflow: unknown;
|
|
13
|
+
proposed_workflow: unknown | null;
|
|
14
|
+
};
|
|
15
|
+
export type MergeConflict = {
|
|
16
|
+
path: string;
|
|
17
|
+
base: unknown;
|
|
18
|
+
ours: unknown;
|
|
19
|
+
theirs: unknown;
|
|
20
|
+
};
|
|
21
|
+
export type MergeResult = {
|
|
22
|
+
clean: true;
|
|
23
|
+
workflow: any;
|
|
24
|
+
} | {
|
|
25
|
+
clean: false;
|
|
26
|
+
conflicts: MergeConflict[];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Three-way merge of Method documents, per step and per top-level key. base is the version the proposal started from,
|
|
30
|
+
* ours is the local file, and theirs is the proposed version. A part that only one side changed takes that change; a part
|
|
31
|
+
* that both sides changed in different ways is a conflict.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergeMethod(base: any, ours: any, theirs: any): MergeResult;
|
|
34
|
+
/** The steps whose definition differs between two documents. */
|
|
35
|
+
export declare function changedSteps(before: any, after: any): string[];
|
|
36
|
+
export declare const localProposalFolder: (file: string) => string;
|
|
37
|
+
/** Save a local proposal as a private file. The .method folder is not for Git: its proposals can hold run content. */
|
|
38
|
+
export declare function saveLocalProposal(file: string, record: Omit<ProposalRecord, 'source'> & Record<string, unknown>): string;
|
|
39
|
+
export declare function localProposals(file: string): (ProposalRecord & {
|
|
40
|
+
path: string;
|
|
41
|
+
})[];
|
|
42
|
+
type RequestOptions = {
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
stallMs?: number;
|
|
45
|
+
};
|
|
46
|
+
type Applied = {
|
|
47
|
+
status: 'applied';
|
|
48
|
+
steps: string[];
|
|
49
|
+
version_id: string | undefined;
|
|
50
|
+
warning?: string;
|
|
51
|
+
} | {
|
|
52
|
+
status: 'conflict';
|
|
53
|
+
conflicts: MergeConflict[];
|
|
54
|
+
} | {
|
|
55
|
+
status: 'invalid';
|
|
56
|
+
error: string;
|
|
57
|
+
};
|
|
58
|
+
/** Merge a proposal into the file. A clean merge writes the file and marks the proposal applied. Never publishes. */
|
|
59
|
+
export declare function applyProposal(client: MethodClient | undefined, file: string, proposal: ProposalRecord, options?: RequestOptions): Promise<Applied>;
|
|
60
|
+
/**
|
|
61
|
+
* Before a command that takes a .method FILE: when this computer is signed in and the Method has an accepted proposal,
|
|
62
|
+
* merge it into the file and print one line. A conflict prints what the coding agent must resolve; the command goes on.
|
|
63
|
+
* Offline, signed out, or on any error, it does nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function autoApply(args: string[], clientFactory: (server: string) => MethodClient): Promise<void>;
|
|
66
|
+
/** Run folders of this Method on this computer, newest first, with the account run ID of each run that was saved to the account. */
|
|
67
|
+
export declare function localRuns(file: string): Promise<{
|
|
68
|
+
dir: string;
|
|
69
|
+
account_run_id: string | null;
|
|
70
|
+
}[]>;
|
|
71
|
+
/** `method improve`, `method proposals`, and `method apply`. */
|
|
72
|
+
export declare function proposalCommand(args: string[], clientFactory: (server: string) => MethodClient): Promise<boolean | undefined>;
|
|
73
|
+
export {};
|