@pulseai/sdk 0.1.0 → 0.1.2

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/dist/index.js CHANGED
@@ -34,16 +34,17 @@ var TESTNET_ADDRESSES = {
34
34
  reputationRegistry: "0x8004B663056A597Dffe9eCcC1965A193B7388713",
35
35
  usdm: "0x939Ff43f7c4A2E94069af7DBbc4497377DdcCE3f"
36
36
  };
37
- var PLACEHOLDER_ADDRESS = "0x0";
38
37
  var MAINNET_ADDRESSES = {
39
- pulseExtension: PLACEHOLDER_ADDRESS,
40
- serviceMarketplace: PLACEHOLDER_ADDRESS,
41
- jobEngine: PLACEHOLDER_ADDRESS,
42
- feeDistributor: PLACEHOLDER_ADDRESS,
38
+ pulseExtension: "0xf1616D2008c4Ff5Ed7BDBd448DAE68615b7A71f0",
39
+ serviceMarketplace: "0xfC180058FCB69531818B832C12473302811dfFF6",
40
+ jobEngine: "0xb5E56262b55aE453E8B16470228F0a5Ef617FF67",
41
+ feeDistributor: "0x51EdD8E4C4B423b952821fc9e2a7dad15a858B56",
43
42
  identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
44
43
  reputationRegistry: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
45
- usdm: "0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7"
44
+ usdm: "0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7",
45
+ buyerRelay: "0x633054593db34c5aAe36F784faeAe51b9604e037"
46
46
  };
47
+ var PLATFORM_BUYER_AGENT_ID = 8154n;
47
48
  var DEFAULT_INDEXER_URLS = {
48
49
  testnet: "https://pulse-indexer.up.railway.app",
49
50
  mainnet: "https://pulse-indexer.up.railway.app"
@@ -2387,13 +2388,29 @@ async function acceptJob(client, jobId, warrenTermsHash) {
2387
2388
  args: [jobId, signature]
2388
2389
  });
2389
2390
  }
2390
- async function submitDeliverable(client, jobId, deliverableHash) {
2391
- return write(client, {
2391
+ async function submitDeliverable(client, jobId, deliverableHash, options) {
2392
+ const txHash = await write(client, {
2392
2393
  address: client.addresses.jobEngine,
2393
2394
  abi: jobEngineAbi,
2394
2395
  functionName: "submitDeliverable",
2395
2396
  args: [jobId, deliverableHash]
2396
2397
  });
2398
+ if (options?.content && options?.indexerUrl) {
2399
+ try {
2400
+ await fetch(`${options.indexerUrl.replace(/\/$/, "")}/deliverables`, {
2401
+ method: "POST",
2402
+ headers: { "Content-Type": "application/json" },
2403
+ body: JSON.stringify({
2404
+ jobId: Number(jobId),
2405
+ content: options.content,
2406
+ contentHash: deliverableHash,
2407
+ storageType: "indexer"
2408
+ })
2409
+ });
2410
+ } catch {
2411
+ }
2412
+ }
2413
+ return txHash;
2397
2414
  }
2398
2415
  async function evaluate(client, jobId, approved, feedback) {
2399
2416
  return write(client, {
@@ -2435,6 +2452,17 @@ async function getJobCount(client) {
2435
2452
  functionName: "getJobCount"
2436
2453
  });
2437
2454
  }
2455
+ async function rejectJob(client, jobId, feedback) {
2456
+ return evaluate(client, jobId, false, feedback);
2457
+ }
2458
+ async function resolveDispute(client, jobId, favorBuyer) {
2459
+ return write(client, {
2460
+ address: client.addresses.jobEngine,
2461
+ abi: jobEngineAbi,
2462
+ functionName: "resolveDispute",
2463
+ args: [jobId, favorBuyer]
2464
+ });
2465
+ }
2438
2466
 
2439
2467
  // src/warren/index.ts
2440
2468
  import { keccak256, toHex as toHex2 } from "viem";
@@ -2638,10 +2666,17 @@ async function deployJobTerms(client, agentId, params) {
2638
2666
  const { masterAddress } = await deployWarrenMaster(client, agentId, pageAddress, contentBytes.length, 0);
2639
2667
  return { masterAddress, pageAddress, hash, txHash };
2640
2668
  }
2669
+ function stripCodeFence(content) {
2670
+ let result = content.trim();
2671
+ result = result.replace(/^```\w*\n?/, "");
2672
+ result = result.replace(/\n?```\s*$/, "");
2673
+ return result;
2674
+ }
2641
2675
  async function deployDeliverable(client, agentId, jobId, params, indexerUrl) {
2642
2676
  const { json, hash } = createDeliverable(params);
2643
- const contentBytes = new TextEncoder().encode(json);
2644
- const { pageAddress, txHash } = await deployWarrenPage(client, json);
2677
+ const warrenContent = params.type === "inline" && params.content ? stripCodeFence(params.content) : json;
2678
+ const contentBytes = new TextEncoder().encode(warrenContent);
2679
+ const { pageAddress, txHash } = await deployWarrenPage(client, warrenContent);
2645
2680
  const { masterAddress } = await deployWarrenMaster(client, agentId, pageAddress, contentBytes.length, 1);
2646
2681
  await write(client, {
2647
2682
  address: client.addresses.jobEngine,
@@ -2664,6 +2699,21 @@ async function deployDeliverable(client, agentId, jobId, params, indexerUrl) {
2664
2699
  } catch {
2665
2700
  }
2666
2701
  }
2702
+ if (indexerUrl) {
2703
+ try {
2704
+ await fetch(`${indexerUrl.replace(/\/$/, "")}/deliverables`, {
2705
+ method: "POST",
2706
+ headers: { "Content-Type": "application/json" },
2707
+ body: JSON.stringify({
2708
+ jobId: Number(jobId),
2709
+ content: json,
2710
+ contentHash: hash,
2711
+ storageType: "warren"
2712
+ })
2713
+ });
2714
+ } catch {
2715
+ }
2716
+ }
2667
2717
  return { masterAddress, pageAddress, hash, txHash };
2668
2718
  }
2669
2719
  async function readDeliverable(client, jobId, indexerUrl) {
@@ -2787,6 +2837,520 @@ function createRequirements(params) {
2787
2837
  return { json, hash };
2788
2838
  }
2789
2839
 
2840
+ // src/ai/provider.ts
2841
+ function parseErrorMessage(payload) {
2842
+ if (typeof payload !== "object" || payload === null) return null;
2843
+ const record = payload;
2844
+ const error = record.error;
2845
+ if (typeof error === "string") return error;
2846
+ if (typeof error === "object" && error !== null) {
2847
+ const message2 = error.message;
2848
+ if (typeof message2 === "string" && message2.length > 0) return message2;
2849
+ }
2850
+ const message = record.message;
2851
+ if (typeof message === "string" && message.length > 0) return message;
2852
+ return null;
2853
+ }
2854
+ function extractTextContent(content) {
2855
+ if (typeof content === "string") return content.trim();
2856
+ if (!Array.isArray(content)) return "";
2857
+ return content.map((entry) => {
2858
+ if (typeof entry === "string") return entry;
2859
+ if (typeof entry !== "object" || entry === null) return "";
2860
+ const text = entry.text;
2861
+ return typeof text === "string" ? text : "";
2862
+ }).join("\n").trim();
2863
+ }
2864
+ async function parseJsonResponse(response) {
2865
+ try {
2866
+ return await response.json();
2867
+ } catch {
2868
+ return null;
2869
+ }
2870
+ }
2871
+ async function callOpenAI(params) {
2872
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
2873
+ method: "POST",
2874
+ headers: {
2875
+ "Content-Type": "application/json",
2876
+ Authorization: `Bearer ${params.apiKey}`
2877
+ },
2878
+ body: JSON.stringify({
2879
+ model: params.model,
2880
+ messages: params.messages.map((message) => ({
2881
+ role: message.role,
2882
+ content: message.content
2883
+ })),
2884
+ max_tokens: params.maxTokens
2885
+ }),
2886
+ signal: params.signal
2887
+ });
2888
+ const payload = await parseJsonResponse(response);
2889
+ if (!response.ok) {
2890
+ throw new Error(
2891
+ `OpenAI request failed (${response.status}): ${parseErrorMessage(payload) ?? response.statusText}`
2892
+ );
2893
+ }
2894
+ const content = extractTextContent(payload?.choices?.[0]?.message?.content);
2895
+ if (!content) {
2896
+ throw new Error("OpenAI returned an empty response");
2897
+ }
2898
+ const truncated = payload?.choices?.[0]?.finish_reason === "length";
2899
+ return { content, truncated, raw: payload };
2900
+ }
2901
+ async function callAnthropic(params) {
2902
+ const systemPrompt = params.messages.filter((message) => message.role === "system").map((message) => message.content).join("\n\n").trim();
2903
+ const messages = params.messages.filter((message) => message.role !== "system").map((message) => ({
2904
+ role: message.role === "assistant" ? "assistant" : "user",
2905
+ content: message.content
2906
+ }));
2907
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
2908
+ method: "POST",
2909
+ headers: {
2910
+ "Content-Type": "application/json",
2911
+ "x-api-key": params.apiKey,
2912
+ "anthropic-version": "2023-06-01"
2913
+ },
2914
+ body: JSON.stringify({
2915
+ model: params.model,
2916
+ max_tokens: params.maxTokens ?? 1024,
2917
+ system: systemPrompt.length > 0 ? systemPrompt : void 0,
2918
+ messages
2919
+ }),
2920
+ signal: params.signal
2921
+ });
2922
+ const payload = await parseJsonResponse(response);
2923
+ if (!response.ok) {
2924
+ throw new Error(
2925
+ `Anthropic request failed (${response.status}): ${parseErrorMessage(payload) ?? response.statusText}`
2926
+ );
2927
+ }
2928
+ const content = extractTextContent(payload?.content);
2929
+ if (!content) {
2930
+ throw new Error("Anthropic returned an empty response");
2931
+ }
2932
+ const truncated = payload?.stop_reason === "max_tokens";
2933
+ return { content, truncated, raw: payload };
2934
+ }
2935
+ async function callGoogle(params) {
2936
+ const systemPrompt = params.messages.filter((message) => message.role === "system").map((message) => message.content).join("\n\n").trim();
2937
+ const contents = params.messages.filter((message) => message.role !== "system").map((message) => ({
2938
+ role: message.role === "assistant" ? "model" : "user",
2939
+ parts: [{ text: message.content }]
2940
+ }));
2941
+ const url = new URL(
2942
+ `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(params.model)}:generateContent`
2943
+ );
2944
+ url.searchParams.set("key", params.apiKey);
2945
+ const response = await fetch(url, {
2946
+ method: "POST",
2947
+ headers: {
2948
+ "Content-Type": "application/json"
2949
+ },
2950
+ body: JSON.stringify({
2951
+ contents,
2952
+ systemInstruction: systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
2953
+ generationConfig: params.maxTokens !== void 0 ? { maxOutputTokens: params.maxTokens } : void 0
2954
+ }),
2955
+ signal: params.signal
2956
+ });
2957
+ const payload = await parseJsonResponse(response);
2958
+ if (!response.ok) {
2959
+ throw new Error(
2960
+ `Google request failed (${response.status}): ${parseErrorMessage(payload) ?? response.statusText}`
2961
+ );
2962
+ }
2963
+ const content = extractTextContent(payload?.candidates?.[0]?.content?.parts);
2964
+ if (!content) {
2965
+ throw new Error("Google returned an empty response");
2966
+ }
2967
+ const truncated = payload?.candidates?.[0]?.finishReason === "MAX_TOKENS";
2968
+ return { content, truncated, raw: payload };
2969
+ }
2970
+ async function callAI(params) {
2971
+ if (params.provider === "openai") {
2972
+ return callOpenAI(params);
2973
+ }
2974
+ if (params.provider === "anthropic") {
2975
+ return callAnthropic(params);
2976
+ }
2977
+ if (params.provider === "google") {
2978
+ return callGoogle(params);
2979
+ }
2980
+ throw new Error(`Unsupported AI provider: ${String(params.provider)}`);
2981
+ }
2982
+
2983
+ // src/handler/site-modifier.ts
2984
+ var FETCH_TIMEOUT_MS = 3e4;
2985
+ var MAX_HTML_BYTES = 500 * 1024;
2986
+ var DEFAULT_MAX_TOKENS = 16384;
2987
+ var AI_MAX_RETRIES = 3;
2988
+ var RETRY_DELAY_MS = 2e3;
2989
+ var GOOGLE_FALLBACK_MODEL = "gemini-2.5-pro";
2990
+ var SUPPORTED_PROVIDERS = ["anthropic", "google", "openai"];
2991
+ var DEFAULT_MODELS = {
2992
+ openai: "gpt-4o-mini",
2993
+ anthropic: "claude-3-5-sonnet-latest",
2994
+ google: "gemini-3.1-pro-preview"
2995
+ };
2996
+ var DEFAULT_SYSTEM_PROMPT = [
2997
+ "You are an expert frontend engineer modifying an existing HTML document.",
2998
+ "Return a complete, self-contained HTML file.",
2999
+ "Inline all CSS in <style> tags and do not use external stylesheets.",
3000
+ "Do not add any external dependencies (no CDNs, external scripts, fonts, or assets).",
3001
+ "Preserve all existing functionality unless explicitly requested to change it.",
3002
+ "Keep the output valid HTML and include all required tags.",
3003
+ "Wrap the final HTML output in a ```html code block."
3004
+ ].join("\n");
3005
+ function isRecord(value) {
3006
+ return typeof value === "object" && value !== null;
3007
+ }
3008
+ function isNonEmptyString(value) {
3009
+ return typeof value === "string" && value.trim().length > 0;
3010
+ }
3011
+ function isSupportedProvider(value) {
3012
+ return typeof value === "string" && SUPPORTED_PROVIDERS.includes(value);
3013
+ }
3014
+ function normalizeDomain(domain) {
3015
+ return domain.trim().toLowerCase().replace(/^\*\./, "").replace(/\.$/, "");
3016
+ }
3017
+ function isAllowedDomain(hostname, allowedDomains) {
3018
+ const host = normalizeDomain(hostname);
3019
+ return allowedDomains.some((domain) => {
3020
+ const normalized = normalizeDomain(domain);
3021
+ if (!normalized) return false;
3022
+ return host === normalized || host.endsWith(`.${normalized}`);
3023
+ });
3024
+ }
3025
+ function parseAndValidateUrl(urlString) {
3026
+ let url;
3027
+ try {
3028
+ url = new URL(urlString);
3029
+ } catch {
3030
+ throw new Error("siteUrl must be a valid URL");
3031
+ }
3032
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
3033
+ throw new Error("siteUrl must use http or https");
3034
+ }
3035
+ return url;
3036
+ }
3037
+ function ensureAllowedUrl(url, allowedDomains) {
3038
+ if (!allowedDomains || allowedDomains.length === 0) return;
3039
+ if (!isAllowedDomain(url.hostname, allowedDomains)) {
3040
+ throw new Error(`siteUrl domain "${url.hostname}" is not allowed`);
3041
+ }
3042
+ }
3043
+ function isHtmlContentType(contentType) {
3044
+ const lower = contentType.toLowerCase();
3045
+ return lower.includes("text/html") || lower.includes("application/xhtml+xml");
3046
+ }
3047
+ function isJsonContentType(contentType) {
3048
+ const lower = contentType.toLowerCase();
3049
+ return lower.includes("application/json") || lower.includes("text/json");
3050
+ }
3051
+ function extractHtmlFromText(text) {
3052
+ const htmlFence = text.match(/```html\s*([\s\S]*?)```/i);
3053
+ if (htmlFence?.[1]) {
3054
+ const fencedHtml = htmlFence[1].trim();
3055
+ if (fencedHtml) return fencedHtml;
3056
+ }
3057
+ const anyFence = text.match(/```\s*([\s\S]*?)```/);
3058
+ if (anyFence?.[1]) {
3059
+ const fencedContent = anyFence[1].trim();
3060
+ if (/<html[\s>]|<!doctype html/i.test(fencedContent)) {
3061
+ return fencedContent;
3062
+ }
3063
+ }
3064
+ const openFence = text.match(/```html?\s*\n?([\s\S]+)/i);
3065
+ if (openFence?.[1]) {
3066
+ const content = openFence[1].trim();
3067
+ if (/<html[\s>]|<!doctype html/i.test(content)) {
3068
+ return content;
3069
+ }
3070
+ }
3071
+ const trimmed = text.trim();
3072
+ if (/<html[\s>]|<!doctype html/i.test(trimmed)) {
3073
+ return trimmed;
3074
+ }
3075
+ return null;
3076
+ }
3077
+ function extractHtmlFromJson(jsonText) {
3078
+ try {
3079
+ const parsed = JSON.parse(jsonText);
3080
+ if (typeof parsed === "string") {
3081
+ if (/<html[\s>]|<!doctype html/i.test(parsed)) return parsed;
3082
+ return null;
3083
+ }
3084
+ if (typeof parsed !== "object" || parsed === null) return null;
3085
+ const obj = parsed;
3086
+ const nested = obj.data && typeof obj.data === "object" ? obj.data : null;
3087
+ const candidates = [
3088
+ obj.html,
3089
+ obj.content,
3090
+ obj.body,
3091
+ nested?.content,
3092
+ nested?.html,
3093
+ nested?.body
3094
+ ];
3095
+ for (const candidate of candidates) {
3096
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
3097
+ if (/<html[\s>]|<!doctype html|<head[\s>]|<body[\s>]|<div[\s>]/i.test(candidate)) {
3098
+ return candidate.trim();
3099
+ }
3100
+ }
3101
+ }
3102
+ return null;
3103
+ } catch {
3104
+ return null;
3105
+ }
3106
+ }
3107
+ function getByteLength(content) {
3108
+ return new TextEncoder().encode(content).byteLength;
3109
+ }
3110
+ var SiteModifierHandler = class {
3111
+ offeringId;
3112
+ autoAccept;
3113
+ config;
3114
+ constructor(offeringId, config, options) {
3115
+ this.offeringId = offeringId;
3116
+ this.config = config;
3117
+ this.autoAccept = options?.autoAccept;
3118
+ }
3119
+ async validateRequirements(context) {
3120
+ try {
3121
+ this.parseRequirements(context.requirements);
3122
+ return { valid: true };
3123
+ } catch (error) {
3124
+ return {
3125
+ valid: false,
3126
+ reason: error instanceof Error ? error.message : String(error)
3127
+ };
3128
+ }
3129
+ }
3130
+ async executeJob(context) {
3131
+ const requirements = this.parseRequirements(context.requirements);
3132
+ const targetUrl = parseAndValidateUrl(requirements.siteUrl);
3133
+ ensureAllowedUrl(targetUrl, this.config.allowedDomains);
3134
+ const provider = requirements.provider ?? this.config.defaultProvider ?? "openai";
3135
+ const model = requirements.model?.trim() || this.config.defaultModel?.trim() || DEFAULT_MODELS[provider];
3136
+ const maxTokens = this.config.maxTokens && this.config.maxTokens > 0 ? this.config.maxTokens : DEFAULT_MAX_TOKENS;
3137
+ if (!model) {
3138
+ throw new Error(
3139
+ `No model configured for provider "${provider}". Set defaultModel or provide requirements.model`
3140
+ );
3141
+ }
3142
+ const originalHtml = await this.fetchHtmlWithTimeout(
3143
+ targetUrl.toString(),
3144
+ context.abortSignal
3145
+ );
3146
+ const apiKey = await this.config.getApiKey(provider);
3147
+ if (!isNonEmptyString(apiKey)) {
3148
+ throw new Error(
3149
+ `Missing API key for provider "${provider}". Check BYOK setup for indexer ${this.config.indexerUrl}`
3150
+ );
3151
+ }
3152
+ const systemPrompt = this.config.systemPrompt?.trim() || DEFAULT_SYSTEM_PROMPT;
3153
+ const userPrompt = [
3154
+ `Modification request: ${requirements.modificationRequest}`,
3155
+ "",
3156
+ "Current HTML:",
3157
+ "```html",
3158
+ originalHtml,
3159
+ "```"
3160
+ ].join("\n");
3161
+ let aiResponse = null;
3162
+ let lastError = null;
3163
+ const aiMessages = [
3164
+ { role: "system", content: systemPrompt },
3165
+ { role: "user", content: userPrompt }
3166
+ ];
3167
+ for (let attempt = 1; attempt <= AI_MAX_RETRIES; attempt++) {
3168
+ try {
3169
+ aiResponse = await callAI({
3170
+ provider,
3171
+ model,
3172
+ maxTokens,
3173
+ apiKey,
3174
+ signal: context.abortSignal,
3175
+ messages: aiMessages
3176
+ });
3177
+ if (aiResponse.truncated) {
3178
+ throw new Error(
3179
+ "AI output was truncated (hit token limit). The generated HTML is incomplete."
3180
+ );
3181
+ }
3182
+ break;
3183
+ } catch (err) {
3184
+ lastError = err instanceof Error ? err : new Error(String(err));
3185
+ console.error(
3186
+ `[site-modifier] ${model} failed (attempt ${attempt}/${AI_MAX_RETRIES}): ${lastError.message}`
3187
+ );
3188
+ if (provider === "google" && model !== GOOGLE_FALLBACK_MODEL) {
3189
+ try {
3190
+ console.log(
3191
+ `[site-modifier] falling back to ${GOOGLE_FALLBACK_MODEL} (attempt ${attempt}/${AI_MAX_RETRIES})`
3192
+ );
3193
+ aiResponse = await callAI({
3194
+ provider,
3195
+ model: GOOGLE_FALLBACK_MODEL,
3196
+ maxTokens,
3197
+ apiKey,
3198
+ signal: context.abortSignal,
3199
+ messages: aiMessages
3200
+ });
3201
+ if (aiResponse.truncated) {
3202
+ throw new Error(
3203
+ "AI output was truncated (hit token limit). The generated HTML is incomplete."
3204
+ );
3205
+ }
3206
+ break;
3207
+ } catch (fallbackErr) {
3208
+ lastError = fallbackErr instanceof Error ? fallbackErr : new Error(String(fallbackErr));
3209
+ console.error(
3210
+ `[site-modifier] ${GOOGLE_FALLBACK_MODEL} also failed (attempt ${attempt}/${AI_MAX_RETRIES}): ${lastError.message}`
3211
+ );
3212
+ }
3213
+ }
3214
+ }
3215
+ if (attempt < AI_MAX_RETRIES) {
3216
+ const delay = RETRY_DELAY_MS * attempt;
3217
+ console.log(`[site-modifier] retrying in ${delay}ms...`);
3218
+ await new Promise((r) => setTimeout(r, delay));
3219
+ }
3220
+ }
3221
+ if (!aiResponse) {
3222
+ throw new Error(
3223
+ `AI generation failed after ${AI_MAX_RETRIES} attempts. Please try again later. Last error: ${lastError?.message ?? "unknown"}`
3224
+ );
3225
+ }
3226
+ const modifiedHtml = extractHtmlFromText(aiResponse.content);
3227
+ if (!modifiedHtml) {
3228
+ throw new Error("AI response did not contain valid HTML output");
3229
+ }
3230
+ if (getByteLength(modifiedHtml) > MAX_HTML_BYTES) {
3231
+ throw new Error(
3232
+ `Modified HTML exceeds size limit (${MAX_HTML_BYTES} bytes)`
3233
+ );
3234
+ }
3235
+ return {
3236
+ type: "inline",
3237
+ content: modifiedHtml,
3238
+ mimeType: "text/html"
3239
+ };
3240
+ }
3241
+ parseRequirements(requirements) {
3242
+ if (!isRecord(requirements)) {
3243
+ throw new Error("Missing requirements object");
3244
+ }
3245
+ const siteUrl = requirements.siteUrl;
3246
+ if (!isNonEmptyString(siteUrl)) {
3247
+ throw new Error("requirements.siteUrl must be a non-empty string");
3248
+ }
3249
+ const parsedUrl = parseAndValidateUrl(siteUrl);
3250
+ ensureAllowedUrl(parsedUrl, this.config.allowedDomains);
3251
+ const modificationRequest = requirements.modificationRequest;
3252
+ if (!isNonEmptyString(modificationRequest)) {
3253
+ throw new Error(
3254
+ "requirements.modificationRequest must be a non-empty string"
3255
+ );
3256
+ }
3257
+ const provider = requirements.provider;
3258
+ if (provider !== void 0 && !isSupportedProvider(provider)) {
3259
+ throw new Error(
3260
+ `requirements.provider must be one of: ${SUPPORTED_PROVIDERS.join(", ")}`
3261
+ );
3262
+ }
3263
+ const model = requirements.model;
3264
+ if (model !== void 0 && !isNonEmptyString(model)) {
3265
+ throw new Error("requirements.model must be a non-empty string");
3266
+ }
3267
+ return {
3268
+ siteUrl: siteUrl.trim(),
3269
+ modificationRequest: modificationRequest.trim(),
3270
+ provider,
3271
+ model: typeof model === "string" ? model.trim() : void 0
3272
+ };
3273
+ }
3274
+ async fetchHtmlWithTimeout(siteUrl, parentSignal) {
3275
+ const controller = new AbortController();
3276
+ const timeoutId = setTimeout(() => {
3277
+ controller.abort(
3278
+ new Error(`Site fetch timed out after ${FETCH_TIMEOUT_MS}ms for ${siteUrl}`)
3279
+ );
3280
+ }, FETCH_TIMEOUT_MS);
3281
+ const parentAbort = () => controller.abort(parentSignal?.reason);
3282
+ if (parentSignal) {
3283
+ if (parentSignal.aborted) {
3284
+ controller.abort(parentSignal.reason);
3285
+ } else {
3286
+ parentSignal.addEventListener("abort", parentAbort, { once: true });
3287
+ }
3288
+ }
3289
+ try {
3290
+ const response = await fetch(siteUrl, {
3291
+ method: "GET",
3292
+ headers: { Accept: "text/html,application/xhtml+xml" },
3293
+ signal: controller.signal
3294
+ });
3295
+ if (!response.ok) {
3296
+ throw new Error(
3297
+ `Failed to fetch site HTML: ${response.status} ${response.statusText}`
3298
+ );
3299
+ }
3300
+ const contentType = response.headers.get("content-type") ?? "";
3301
+ const isHtml = !contentType || isHtmlContentType(contentType);
3302
+ const isJson = isJsonContentType(contentType);
3303
+ if (!isHtml && !isJson) {
3304
+ throw new Error(
3305
+ `Fetched content is not HTML or JSON (content-type: ${contentType}, url: ${siteUrl})`
3306
+ );
3307
+ }
3308
+ const contentLengthHeader = response.headers.get("content-length");
3309
+ if (contentLengthHeader) {
3310
+ const contentLength = Number.parseInt(contentLengthHeader, 10);
3311
+ if (Number.isFinite(contentLength) && contentLength > MAX_HTML_BYTES) {
3312
+ throw new Error(
3313
+ `HTML too large (${contentLength} bytes). Limit is ${MAX_HTML_BYTES} bytes`
3314
+ );
3315
+ }
3316
+ }
3317
+ const htmlBuffer = await response.arrayBuffer();
3318
+ if (htmlBuffer.byteLength > MAX_HTML_BYTES) {
3319
+ throw new Error(
3320
+ `HTML too large (${htmlBuffer.byteLength} bytes). Limit is ${MAX_HTML_BYTES} bytes`
3321
+ );
3322
+ }
3323
+ let html = new TextDecoder().decode(htmlBuffer).trim();
3324
+ if (!html) {
3325
+ throw new Error("Fetched content is empty");
3326
+ }
3327
+ if (isJson) {
3328
+ const extracted = extractHtmlFromJson(html);
3329
+ if (!extracted) {
3330
+ throw new Error(
3331
+ `JSON response from ${siteUrl} does not contain extractable HTML content`
3332
+ );
3333
+ }
3334
+ html = extracted;
3335
+ }
3336
+ return html;
3337
+ } catch (error) {
3338
+ if (error instanceof Error && error.name === "AbortError") {
3339
+ if (parentSignal?.aborted) {
3340
+ throw new Error("Site fetch aborted");
3341
+ }
3342
+ throw new Error(`Site fetch timed out after ${FETCH_TIMEOUT_MS}ms for ${siteUrl}`);
3343
+ }
3344
+ throw error;
3345
+ } finally {
3346
+ clearTimeout(timeoutId);
3347
+ if (parentSignal) {
3348
+ parentSignal.removeEventListener("abort", parentAbort);
3349
+ }
3350
+ }
3351
+ }
3352
+ };
3353
+
2790
3354
  // src/utils.ts
2791
3355
  var USDM_DECIMALS = 18;
2792
3356
  var USDM_SCALE = 10n ** BigInt(USDM_DECIMALS);
@@ -2858,7 +3422,7 @@ var IndexerClientError = class extends Error {
2858
3422
  this.body = options.body;
2859
3423
  }
2860
3424
  };
2861
- function isRecord(value) {
3425
+ function isRecord2(value) {
2862
3426
  return typeof value === "object" && value !== null;
2863
3427
  }
2864
3428
  function toIndexerAgent(raw) {
@@ -2986,6 +3550,31 @@ var IndexerClient = class {
2986
3550
  );
2987
3551
  return toIndexerWarrenLinks(payload.data);
2988
3552
  }
3553
+ async postDeliverable(jobId, content, contentHash, storageType) {
3554
+ await this.requestJson("/deliverables", {
3555
+ method: "POST",
3556
+ headers: { "Content-Type": "application/json" },
3557
+ body: JSON.stringify({
3558
+ jobId,
3559
+ content,
3560
+ contentHash,
3561
+ storageType: storageType ?? "indexer"
3562
+ })
3563
+ });
3564
+ }
3565
+ async getDeliverable(jobId) {
3566
+ try {
3567
+ const response = await this.request(`/deliverables/${jobId}`);
3568
+ if (response.status === 404) return null;
3569
+ const payload = await this.parseResponse(
3570
+ response,
3571
+ `/deliverables/${jobId}`
3572
+ );
3573
+ return { content: payload.data.content, storageType: payload.data.storage_type };
3574
+ } catch {
3575
+ return null;
3576
+ }
3577
+ }
2989
3578
  async request(path, init) {
2990
3579
  const url = `${this.baseUrl}${path}`;
2991
3580
  const controller = new AbortController();
@@ -3021,7 +3610,7 @@ var IndexerClient = class {
3021
3610
  });
3022
3611
  }
3023
3612
  if (!response.ok) {
3024
- const apiError = isRecord(payload) ? payload : void 0;
3613
+ const apiError = isRecord2(payload) ? payload : void 0;
3025
3614
  const message = response.status === 404 && notFoundMessage ? notFoundMessage : apiError?.error ?? apiError?.message ?? `Indexer request failed (${response.status})`;
3026
3615
  throw new IndexerClientError(message, {
3027
3616
  url,
@@ -3075,10 +3664,11 @@ var ProviderRuntime = class {
3075
3664
  });
3076
3665
  const jobs = await this.indexer.getJobs({ status: 0 });
3077
3666
  for (const job of jobs) {
3078
- if (this.processedJobs.has(job.jobId)) continue;
3667
+ const newKey = `new:${job.jobId}`;
3668
+ if (this.processedJobs.has(newKey)) continue;
3079
3669
  const matchesOffering = offerings.some((o) => o.offeringId === job.offeringId);
3080
3670
  if (!matchesOffering) continue;
3081
- this.processedJobs.add(job.jobId);
3671
+ this.processedJobs.add(newKey);
3082
3672
  if (this.callbacks.onJobReceived) {
3083
3673
  try {
3084
3674
  const accept = await this.callbacks.onJobReceived(job);
@@ -3103,7 +3693,7 @@ var ProviderRuntime = class {
3103
3693
  agentId: Number(this.agentId)
3104
3694
  });
3105
3695
  for (const job of inProgressJobs) {
3106
- const deliverKey = job.jobId * 1e3;
3696
+ const deliverKey = `deliver:${job.jobId}`;
3107
3697
  if (this.processedJobs.has(deliverKey)) continue;
3108
3698
  if (this.callbacks.onDeliverableRequested) {
3109
3699
  try {
@@ -3130,7 +3720,7 @@ var ProviderRuntime = class {
3130
3720
  agentId: Number(this.agentId)
3131
3721
  });
3132
3722
  for (const job of completedJobs) {
3133
- const completeKey = job.jobId * 1e4;
3723
+ const completeKey = `complete:${job.jobId}`;
3134
3724
  if (this.processedJobs.has(completeKey)) continue;
3135
3725
  this.processedJobs.add(completeKey);
3136
3726
  this.callbacks.onJobCompleted?.(job);
@@ -3241,8 +3831,9 @@ var BuyerRuntime = class {
3241
3831
  agentId: Number(this.agentId)
3242
3832
  });
3243
3833
  for (const job of deliveredJobs) {
3244
- if (this.processedJobs.has(job.jobId)) continue;
3245
- this.processedJobs.add(job.jobId);
3834
+ const deliveredKey = `delivered:${job.jobId}`;
3835
+ if (this.processedJobs.has(deliveredKey)) continue;
3836
+ this.processedJobs.add(deliveredKey);
3246
3837
  try {
3247
3838
  const deliverable = await readDeliverable(
3248
3839
  this.client,
@@ -3267,7 +3858,7 @@ var BuyerRuntime = class {
3267
3858
  agentId: Number(this.agentId)
3268
3859
  });
3269
3860
  for (const job of completedJobs) {
3270
- const completeKey = job.jobId * 1e3;
3861
+ const completeKey = `complete:${job.jobId}`;
3271
3862
  if (this.processedJobs.has(completeKey)) continue;
3272
3863
  this.processedJobs.add(completeKey);
3273
3864
  this.callbacks.onJobCompleted?.(job);
@@ -3283,8 +3874,11 @@ var HandlerProviderRuntime = class {
3283
3874
  indexer;
3284
3875
  indexerUrl;
3285
3876
  pollInterval;
3877
+ executionTimeoutMs;
3286
3878
  running = false;
3287
3879
  processedJobs = /* @__PURE__ */ new Set();
3880
+ failedJobs = /* @__PURE__ */ new Map();
3881
+ maxRetries = 3;
3288
3882
  onError;
3289
3883
  constructor(client, agentId, options) {
3290
3884
  this.client = client;
@@ -3292,6 +3886,7 @@ var HandlerProviderRuntime = class {
3292
3886
  this.indexer = new IndexerClient({ baseUrl: options.indexerUrl });
3293
3887
  this.indexerUrl = options.indexerUrl.replace(/\/$/, "");
3294
3888
  this.pollInterval = options.pollInterval ?? 5e3;
3889
+ this.executionTimeoutMs = options.executionTimeoutMs ?? 5 * 60 * 1e3;
3295
3890
  }
3296
3891
  registerHandler(handler) {
3297
3892
  this.handlers.set(handler.offeringId, handler);
@@ -3311,6 +3906,7 @@ var HandlerProviderRuntime = class {
3311
3906
  try {
3312
3907
  await this.checkNewJobs();
3313
3908
  await this.checkInProgressJobs();
3909
+ await this.checkEvaluatedJobs();
3314
3910
  } catch (e) {
3315
3911
  this.onError?.(e instanceof Error ? e : new Error(String(e)));
3316
3912
  }
@@ -3324,13 +3920,38 @@ var HandlerProviderRuntime = class {
3324
3920
  });
3325
3921
  const jobs = await this.indexer.getJobs({ status: 0 });
3326
3922
  for (const job of jobs) {
3327
- if (this.processedJobs.has(job.jobId)) continue;
3923
+ const newKey = `new:${job.jobId}`;
3924
+ if (!this.canProcess(newKey)) continue;
3328
3925
  const matchesOffering = offerings.some((o) => o.offeringId === job.offeringId);
3329
3926
  if (!matchesOffering) continue;
3330
3927
  const handler = this.handlers.get(job.offeringId);
3331
3928
  if (!handler) continue;
3332
- this.processedJobs.add(job.jobId);
3333
3929
  try {
3930
+ if (job.slaMinutes === null) {
3931
+ throw new Error(`Job ${job.jobId} is missing slaMinutes`);
3932
+ }
3933
+ const context = {
3934
+ jobId: BigInt(job.jobId),
3935
+ offeringId: BigInt(job.offeringId),
3936
+ buyerAgentId: BigInt(job.buyerAgentId),
3937
+ providerAgentId: BigInt(job.providerAgentId),
3938
+ priceUsdm: job.priceUsdm,
3939
+ slaMinutes: job.slaMinutes
3940
+ };
3941
+ const reqData = await readRequirements(
3942
+ this.client,
3943
+ BigInt(job.jobId),
3944
+ this.indexerUrl
3945
+ );
3946
+ if (reqData) {
3947
+ context.requirements = reqData.requirements;
3948
+ }
3949
+ if (handler.validateRequirements) {
3950
+ const validation = await handler.validateRequirements(context);
3951
+ if (!validation.valid) {
3952
+ throw new Error(`Validation failed for job ${job.jobId}: ${validation.reason}`);
3953
+ }
3954
+ }
3334
3955
  if (handler.autoAccept !== false) {
3335
3956
  await acceptJob(
3336
3957
  this.client,
@@ -3338,8 +3959,9 @@ var HandlerProviderRuntime = class {
3338
3959
  job.warrenTermsHash
3339
3960
  );
3340
3961
  }
3962
+ this.markProcessed(newKey);
3341
3963
  } catch (e) {
3342
- this.onError?.(e instanceof Error ? e : new Error(String(e)));
3964
+ this.markFailed(newKey, e);
3343
3965
  }
3344
3966
  }
3345
3967
  }
@@ -3349,15 +3971,13 @@ var HandlerProviderRuntime = class {
3349
3971
  agentId: Number(this.agentId)
3350
3972
  });
3351
3973
  for (const job of inProgressJobs) {
3352
- const deliverKey = job.jobId * 1e3;
3353
- if (this.processedJobs.has(deliverKey)) continue;
3974
+ const deliverKey = `deliver:${job.jobId}`;
3975
+ if (!this.canProcess(deliverKey)) continue;
3354
3976
  const handler = this.handlers.get(job.offeringId);
3355
3977
  if (!handler) continue;
3356
- this.processedJobs.add(deliverKey);
3357
3978
  try {
3358
3979
  if (job.slaMinutes === null) {
3359
- this.onError?.(new Error(`Job ${job.jobId} is missing slaMinutes`));
3360
- continue;
3980
+ throw new Error(`Job ${job.jobId} is missing slaMinutes`);
3361
3981
  }
3362
3982
  const context = {
3363
3983
  jobId: BigInt(job.jobId),
@@ -3378,32 +3998,102 @@ var HandlerProviderRuntime = class {
3378
3998
  if (handler.validateRequirements) {
3379
3999
  const validation = await handler.validateRequirements(context);
3380
4000
  if (!validation.valid) {
3381
- this.onError?.(
4001
+ this.markFailed(
4002
+ deliverKey,
3382
4003
  new Error(`Validation failed for job ${job.jobId}: ${validation.reason}`)
3383
4004
  );
3384
4005
  continue;
3385
4006
  }
3386
4007
  }
3387
- const result = await handler.executeJob(context);
3388
- const deliverableParams = {
3389
- type: result.type,
3390
- content: result.content,
3391
- url: result.url,
3392
- mimeType: result.mimeType,
3393
- jobId: BigInt(job.jobId)
3394
- };
3395
- await deployDeliverable(
3396
- this.client,
3397
- this.agentId,
3398
- BigInt(job.jobId),
3399
- deliverableParams,
3400
- this.indexerUrl
3401
- );
4008
+ const remainingSlaMs = this.getRemainingSlaMs(job.createdAt, job.acceptedAt, job.slaMinutes);
4009
+ if (remainingSlaMs !== null && remainingSlaMs < 10 * 60 * 1e3) {
4010
+ console.warn(
4011
+ `Skipping job ${job.jobId}: SLA remaining ${Math.max(0, Math.floor(remainingSlaMs / 1e3))}s is below 10 minutes`
4012
+ );
4013
+ this.markProcessed(deliverKey);
4014
+ continue;
4015
+ }
4016
+ const controller = new AbortController();
4017
+ const executionContext = { ...context, abortSignal: controller.signal };
4018
+ let timeoutId;
4019
+ try {
4020
+ const timeoutPromise = new Promise((_, reject) => {
4021
+ timeoutId = setTimeout(() => {
4022
+ controller.abort();
4023
+ reject(
4024
+ new Error(
4025
+ `Execution timed out for job ${job.jobId} after ${this.executionTimeoutMs}ms`
4026
+ )
4027
+ );
4028
+ }, this.executionTimeoutMs);
4029
+ });
4030
+ const result = await Promise.race([
4031
+ handler.executeJob(executionContext),
4032
+ timeoutPromise
4033
+ ]);
4034
+ const deliverableParams = {
4035
+ type: result.type,
4036
+ content: result.content,
4037
+ url: result.url,
4038
+ mimeType: result.mimeType,
4039
+ jobId: BigInt(job.jobId)
4040
+ };
4041
+ await deployDeliverable(
4042
+ this.client,
4043
+ this.agentId,
4044
+ BigInt(job.jobId),
4045
+ deliverableParams,
4046
+ this.indexerUrl
4047
+ );
4048
+ } finally {
4049
+ if (timeoutId) clearTimeout(timeoutId);
4050
+ }
4051
+ this.markProcessed(deliverKey);
3402
4052
  } catch (e) {
3403
- this.onError?.(e instanceof Error ? e : new Error(String(e)));
4053
+ this.markFailed(deliverKey, e);
4054
+ }
4055
+ }
4056
+ }
4057
+ async checkEvaluatedJobs() {
4058
+ const evaluatedJobs = await this.indexer.getJobs({
4059
+ status: 4,
4060
+ agentId: Number(this.agentId)
4061
+ });
4062
+ for (const job of evaluatedJobs) {
4063
+ const settleKey = `settle:${job.jobId}`;
4064
+ if (!this.canProcess(settleKey)) continue;
4065
+ try {
4066
+ await settle(this.client, BigInt(job.jobId));
4067
+ console.log(`[handler-provider] auto-settled job ${job.jobId}`);
4068
+ this.markProcessed(settleKey);
4069
+ } catch (e) {
4070
+ this.markFailed(settleKey, e);
3404
4071
  }
3405
4072
  }
3406
4073
  }
4074
+ canProcess(key) {
4075
+ if (this.processedJobs.has(key)) return false;
4076
+ return (this.failedJobs.get(key) ?? 0) < this.maxRetries;
4077
+ }
4078
+ markProcessed(key) {
4079
+ this.failedJobs.delete(key);
4080
+ this.processedJobs.add(key);
4081
+ }
4082
+ markFailed(key, error) {
4083
+ const err = error instanceof Error ? error : new Error(String(error));
4084
+ this.onError?.(err);
4085
+ const retries = (this.failedJobs.get(key) ?? 0) + 1;
4086
+ this.failedJobs.set(key, retries);
4087
+ if (retries >= this.maxRetries) {
4088
+ this.onError?.(new Error(`Giving up on ${key} after ${retries} failed attempts`));
4089
+ }
4090
+ }
4091
+ getRemainingSlaMs(createdAt, acceptedAt, slaMinutes) {
4092
+ const start = acceptedAt ?? createdAt;
4093
+ if (start === null) return null;
4094
+ const startMs = start > 1e12 ? start : start * 1e3;
4095
+ return startMs + slaMinutes * 60 * 1e3 - Date.now();
4096
+ }
3407
4097
  };
3408
4098
 
3409
4099
  // src/abis/FeeDistributor.ts
@@ -4253,6 +4943,528 @@ var erc8004ReputationAbi = [
4253
4943
  "anonymous": false
4254
4944
  }
4255
4945
  ];
4946
+
4947
+ // src/abis/BuyerRelay.ts
4948
+ var buyerRelayAbi = [
4949
+ {
4950
+ "type": "constructor",
4951
+ "inputs": [
4952
+ {
4953
+ "name": "owner_",
4954
+ "type": "address",
4955
+ "internalType": "address"
4956
+ },
4957
+ {
4958
+ "name": "identityRegistry_",
4959
+ "type": "address",
4960
+ "internalType": "address"
4961
+ },
4962
+ {
4963
+ "name": "pulseExtension_",
4964
+ "type": "address",
4965
+ "internalType": "address"
4966
+ },
4967
+ {
4968
+ "name": "jobEngine_",
4969
+ "type": "address",
4970
+ "internalType": "address"
4971
+ },
4972
+ {
4973
+ "name": "serviceMarketplace_",
4974
+ "type": "address",
4975
+ "internalType": "address"
4976
+ },
4977
+ {
4978
+ "name": "usdm_",
4979
+ "type": "address",
4980
+ "internalType": "address"
4981
+ }
4982
+ ],
4983
+ "stateMutability": "nonpayable"
4984
+ },
4985
+ {
4986
+ "type": "function",
4987
+ "name": "buyerAgentId",
4988
+ "inputs": [],
4989
+ "outputs": [
4990
+ {
4991
+ "name": "",
4992
+ "type": "uint256",
4993
+ "internalType": "uint256"
4994
+ }
4995
+ ],
4996
+ "stateMutability": "view"
4997
+ },
4998
+ {
4999
+ "type": "function",
5000
+ "name": "cancelFor",
5001
+ "inputs": [
5002
+ {
5003
+ "name": "jobId",
5004
+ "type": "uint256",
5005
+ "internalType": "uint256"
5006
+ }
5007
+ ],
5008
+ "outputs": [],
5009
+ "stateMutability": "nonpayable"
5010
+ },
5011
+ {
5012
+ "type": "function",
5013
+ "name": "claimRefund",
5014
+ "inputs": [
5015
+ {
5016
+ "name": "jobId",
5017
+ "type": "uint256",
5018
+ "internalType": "uint256"
5019
+ }
5020
+ ],
5021
+ "outputs": [],
5022
+ "stateMutability": "nonpayable"
5023
+ },
5024
+ {
5025
+ "type": "function",
5026
+ "name": "createJobFor",
5027
+ "inputs": [
5028
+ {
5029
+ "name": "offeringId",
5030
+ "type": "uint256",
5031
+ "internalType": "uint256"
5032
+ },
5033
+ {
5034
+ "name": "termsHash",
5035
+ "type": "bytes32",
5036
+ "internalType": "bytes32"
5037
+ },
5038
+ {
5039
+ "name": "maxPrice",
5040
+ "type": "uint256",
5041
+ "internalType": "uint256"
5042
+ }
5043
+ ],
5044
+ "outputs": [
5045
+ {
5046
+ "name": "jobId",
5047
+ "type": "uint256",
5048
+ "internalType": "uint256"
5049
+ }
5050
+ ],
5051
+ "stateMutability": "nonpayable"
5052
+ },
5053
+ {
5054
+ "type": "function",
5055
+ "name": "evaluateFor",
5056
+ "inputs": [
5057
+ {
5058
+ "name": "jobId",
5059
+ "type": "uint256",
5060
+ "internalType": "uint256"
5061
+ },
5062
+ {
5063
+ "name": "approved",
5064
+ "type": "bool",
5065
+ "internalType": "bool"
5066
+ },
5067
+ {
5068
+ "name": "feedback",
5069
+ "type": "string",
5070
+ "internalType": "string"
5071
+ }
5072
+ ],
5073
+ "outputs": [],
5074
+ "stateMutability": "nonpayable"
5075
+ },
5076
+ {
5077
+ "type": "function",
5078
+ "name": "identityRegistry",
5079
+ "inputs": [],
5080
+ "outputs": [
5081
+ {
5082
+ "name": "",
5083
+ "type": "address",
5084
+ "internalType": "contract IERC8004Identity"
5085
+ }
5086
+ ],
5087
+ "stateMutability": "view"
5088
+ },
5089
+ {
5090
+ "type": "function",
5091
+ "name": "jobDeposit",
5092
+ "inputs": [
5093
+ {
5094
+ "name": "",
5095
+ "type": "uint256",
5096
+ "internalType": "uint256"
5097
+ }
5098
+ ],
5099
+ "outputs": [
5100
+ {
5101
+ "name": "",
5102
+ "type": "uint256",
5103
+ "internalType": "uint256"
5104
+ }
5105
+ ],
5106
+ "stateMutability": "view"
5107
+ },
5108
+ {
5109
+ "type": "function",
5110
+ "name": "jobEngine",
5111
+ "inputs": [],
5112
+ "outputs": [
5113
+ {
5114
+ "name": "",
5115
+ "type": "address",
5116
+ "internalType": "contract IJobEngine"
5117
+ }
5118
+ ],
5119
+ "stateMutability": "view"
5120
+ },
5121
+ {
5122
+ "type": "function",
5123
+ "name": "jobPayer",
5124
+ "inputs": [
5125
+ {
5126
+ "name": "",
5127
+ "type": "uint256",
5128
+ "internalType": "uint256"
5129
+ }
5130
+ ],
5131
+ "outputs": [
5132
+ {
5133
+ "name": "",
5134
+ "type": "address",
5135
+ "internalType": "address"
5136
+ }
5137
+ ],
5138
+ "stateMutability": "view"
5139
+ },
5140
+ {
5141
+ "type": "function",
5142
+ "name": "jobRefundState",
5143
+ "inputs": [
5144
+ {
5145
+ "name": "",
5146
+ "type": "uint256",
5147
+ "internalType": "uint256"
5148
+ }
5149
+ ],
5150
+ "outputs": [
5151
+ {
5152
+ "name": "",
5153
+ "type": "uint8",
5154
+ "internalType": "enum IBuyerRelay.RefundState"
5155
+ }
5156
+ ],
5157
+ "stateMutability": "view"
5158
+ },
5159
+ {
5160
+ "type": "function",
5161
+ "name": "onERC721Received",
5162
+ "inputs": [
5163
+ {
5164
+ "name": "",
5165
+ "type": "address",
5166
+ "internalType": "address"
5167
+ },
5168
+ {
5169
+ "name": "",
5170
+ "type": "address",
5171
+ "internalType": "address"
5172
+ },
5173
+ {
5174
+ "name": "",
5175
+ "type": "uint256",
5176
+ "internalType": "uint256"
5177
+ },
5178
+ {
5179
+ "name": "",
5180
+ "type": "bytes",
5181
+ "internalType": "bytes"
5182
+ }
5183
+ ],
5184
+ "outputs": [
5185
+ {
5186
+ "name": "",
5187
+ "type": "bytes4",
5188
+ "internalType": "bytes4"
5189
+ }
5190
+ ],
5191
+ "stateMutability": "pure"
5192
+ },
5193
+ {
5194
+ "type": "function",
5195
+ "name": "owner",
5196
+ "inputs": [],
5197
+ "outputs": [
5198
+ {
5199
+ "name": "",
5200
+ "type": "address",
5201
+ "internalType": "address"
5202
+ }
5203
+ ],
5204
+ "stateMutability": "view"
5205
+ },
5206
+ {
5207
+ "type": "function",
5208
+ "name": "pulseExtension",
5209
+ "inputs": [],
5210
+ "outputs": [
5211
+ {
5212
+ "name": "",
5213
+ "type": "address",
5214
+ "internalType": "contract IPulseExtension"
5215
+ }
5216
+ ],
5217
+ "stateMutability": "view"
5218
+ },
5219
+ {
5220
+ "type": "function",
5221
+ "name": "renounceOwnership",
5222
+ "inputs": [],
5223
+ "outputs": [],
5224
+ "stateMutability": "nonpayable"
5225
+ },
5226
+ {
5227
+ "type": "function",
5228
+ "name": "serviceMarketplace",
5229
+ "inputs": [],
5230
+ "outputs": [
5231
+ {
5232
+ "name": "",
5233
+ "type": "address",
5234
+ "internalType": "contract IServiceMarketplace"
5235
+ }
5236
+ ],
5237
+ "stateMutability": "view"
5238
+ },
5239
+ {
5240
+ "type": "function",
5241
+ "name": "setup",
5242
+ "inputs": [
5243
+ {
5244
+ "name": "agentURI",
5245
+ "type": "string",
5246
+ "internalType": "string"
5247
+ }
5248
+ ],
5249
+ "outputs": [],
5250
+ "stateMutability": "nonpayable"
5251
+ },
5252
+ {
5253
+ "type": "function",
5254
+ "name": "transferOwnership",
5255
+ "inputs": [
5256
+ {
5257
+ "name": "newOwner",
5258
+ "type": "address",
5259
+ "internalType": "address"
5260
+ }
5261
+ ],
5262
+ "outputs": [],
5263
+ "stateMutability": "nonpayable"
5264
+ },
5265
+ {
5266
+ "type": "function",
5267
+ "name": "usdm",
5268
+ "inputs": [],
5269
+ "outputs": [
5270
+ {
5271
+ "name": "",
5272
+ "type": "address",
5273
+ "internalType": "address"
5274
+ }
5275
+ ],
5276
+ "stateMutability": "view"
5277
+ },
5278
+ {
5279
+ "type": "event",
5280
+ "name": "EvaluatedFor",
5281
+ "inputs": [
5282
+ {
5283
+ "name": "jobId",
5284
+ "type": "uint256",
5285
+ "indexed": true,
5286
+ "internalType": "uint256"
5287
+ },
5288
+ {
5289
+ "name": "payer",
5290
+ "type": "address",
5291
+ "indexed": true,
5292
+ "internalType": "address"
5293
+ },
5294
+ {
5295
+ "name": "approved",
5296
+ "type": "bool",
5297
+ "indexed": false,
5298
+ "internalType": "bool"
5299
+ }
5300
+ ],
5301
+ "anonymous": false
5302
+ },
5303
+ {
5304
+ "type": "event",
5305
+ "name": "JobCreatedFor",
5306
+ "inputs": [
5307
+ {
5308
+ "name": "jobId",
5309
+ "type": "uint256",
5310
+ "indexed": true,
5311
+ "internalType": "uint256"
5312
+ },
5313
+ {
5314
+ "name": "payer",
5315
+ "type": "address",
5316
+ "indexed": true,
5317
+ "internalType": "address"
5318
+ },
5319
+ {
5320
+ "name": "price",
5321
+ "type": "uint256",
5322
+ "indexed": false,
5323
+ "internalType": "uint256"
5324
+ }
5325
+ ],
5326
+ "anonymous": false
5327
+ },
5328
+ {
5329
+ "type": "event",
5330
+ "name": "OwnershipTransferred",
5331
+ "inputs": [
5332
+ {
5333
+ "name": "previousOwner",
5334
+ "type": "address",
5335
+ "indexed": true,
5336
+ "internalType": "address"
5337
+ },
5338
+ {
5339
+ "name": "newOwner",
5340
+ "type": "address",
5341
+ "indexed": true,
5342
+ "internalType": "address"
5343
+ }
5344
+ ],
5345
+ "anonymous": false
5346
+ },
5347
+ {
5348
+ "type": "event",
5349
+ "name": "RefundClaimed",
5350
+ "inputs": [
5351
+ {
5352
+ "name": "jobId",
5353
+ "type": "uint256",
5354
+ "indexed": true,
5355
+ "internalType": "uint256"
5356
+ },
5357
+ {
5358
+ "name": "payer",
5359
+ "type": "address",
5360
+ "indexed": true,
5361
+ "internalType": "address"
5362
+ },
5363
+ {
5364
+ "name": "amount",
5365
+ "type": "uint256",
5366
+ "indexed": false,
5367
+ "internalType": "uint256"
5368
+ }
5369
+ ],
5370
+ "anonymous": false
5371
+ },
5372
+ {
5373
+ "type": "error",
5374
+ "name": "AgentAlreadyInitialized",
5375
+ "inputs": []
5376
+ },
5377
+ {
5378
+ "type": "error",
5379
+ "name": "AgentNotActive",
5380
+ "inputs": []
5381
+ },
5382
+ {
5383
+ "type": "error",
5384
+ "name": "InsufficientPayment",
5385
+ "inputs": [
5386
+ {
5387
+ "name": "required",
5388
+ "type": "uint256",
5389
+ "internalType": "uint256"
5390
+ },
5391
+ {
5392
+ "name": "provided",
5393
+ "type": "uint256",
5394
+ "internalType": "uint256"
5395
+ }
5396
+ ]
5397
+ },
5398
+ {
5399
+ "type": "error",
5400
+ "name": "InvalidJobStatus",
5401
+ "inputs": [
5402
+ {
5403
+ "name": "current",
5404
+ "type": "uint8",
5405
+ "internalType": "enum DataTypes.JobStatus"
5406
+ },
5407
+ {
5408
+ "name": "expected",
5409
+ "type": "uint8",
5410
+ "internalType": "enum DataTypes.JobStatus"
5411
+ }
5412
+ ]
5413
+ },
5414
+ {
5415
+ "type": "error",
5416
+ "name": "InvalidRefundState",
5417
+ "inputs": [
5418
+ {
5419
+ "name": "current",
5420
+ "type": "uint8",
5421
+ "internalType": "enum IBuyerRelay.RefundState"
5422
+ },
5423
+ {
5424
+ "name": "expected",
5425
+ "type": "uint8",
5426
+ "internalType": "enum IBuyerRelay.RefundState"
5427
+ }
5428
+ ]
5429
+ },
5430
+ {
5431
+ "type": "error",
5432
+ "name": "OnlyBuyer",
5433
+ "inputs": []
5434
+ },
5435
+ {
5436
+ "type": "error",
5437
+ "name": "OwnableInvalidOwner",
5438
+ "inputs": [
5439
+ {
5440
+ "name": "owner",
5441
+ "type": "address",
5442
+ "internalType": "address"
5443
+ }
5444
+ ]
5445
+ },
5446
+ {
5447
+ "type": "error",
5448
+ "name": "OwnableUnauthorizedAccount",
5449
+ "inputs": [
5450
+ {
5451
+ "name": "account",
5452
+ "type": "address",
5453
+ "internalType": "address"
5454
+ }
5455
+ ]
5456
+ },
5457
+ {
5458
+ "type": "error",
5459
+ "name": "Reentrancy",
5460
+ "inputs": []
5461
+ },
5462
+ {
5463
+ "type": "error",
5464
+ "name": "ZeroAddress",
5465
+ "inputs": []
5466
+ }
5467
+ ];
4256
5468
  export {
4257
5469
  ACCEPT_TIMEOUT,
4258
5470
  BPS_DENOMINATOR,
@@ -4266,15 +5478,19 @@ export {
4266
5478
  JobStatus,
4267
5479
  MAINNET_ADDRESSES,
4268
5480
  MEMO_TYPES,
5481
+ PLATFORM_BUYER_AGENT_ID,
4269
5482
  PULSE_DOMAIN,
4270
5483
  ProviderRuntime,
4271
5484
  RISK_POOL_BPS,
4272
5485
  ServiceType,
5486
+ SiteModifierHandler,
4273
5487
  TESTNET_ADDRESSES,
4274
5488
  TREASURY_BPS,
4275
5489
  USDM_MAINNET,
4276
5490
  acceptJob,
4277
5491
  activateOffering,
5492
+ buyerRelayAbi,
5493
+ callAI,
4278
5494
  cancelJob,
4279
5495
  createAgentCard,
4280
5496
  createDeliverable,
@@ -4317,6 +5533,8 @@ export {
4317
5533
  readWarrenMaster,
4318
5534
  readWarrenPage,
4319
5535
  registerAgent,
5536
+ rejectJob,
5537
+ resolveDispute,
4320
5538
  serviceMarketplaceAbi,
4321
5539
  setOperator,
4322
5540
  setWarrenContract,