Commit dc48ebe1ea6c0f5ed7ccd39a5c9f0b0f36fd3408

Authored by yibo
1 parent 708d4103

docs(open-api): 存一份对外交付页面的 HTML 源文件

跟已发布的 Artifact 页面同一份内容,存本地留档(docs/done/open-api-manual.md
是内部真相来源,这份是排版过、可以直接发给外部团队的版本)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Showing 1 changed file with 520 additions and 0 deletions
docs/done/open-api-manual.html 0 → 100644
  1 +<title>Agent Invocation API</title>
  2 +<link rel="preconnect" href="https://fonts.googleapis.com">
  3 +<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  4 +<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400..800&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
  5 +
  6 +<style>
  7 + :root {
  8 + --paper: #f6f7f2;
  9 + --paper-raised: #ffffff;
  10 + --ink: #1c211d;
  11 + --ink-soft: #4d564e;
  12 + --ink-faint: #7c8579;
  13 + --line: #dce0d5;
  14 + --line-soft: #e8ebe2;
  15 + --accent: #7c8f1f;
  16 + --accent-ink: #fdfff2;
  17 + --accent-soft: #eef1de;
  18 + --code-bg: #1f241f;
  19 + --code-ink: #e7ead9;
  20 + --code-line: #343a30;
  21 + --status-queued: #5b7590;
  22 + --status-queued-bg: #e9eef3;
  23 + --status-running: #b8811f;
  24 + --status-running-bg: #f7edd9;
  25 + --status-completed: #3f8f5f;
  26 + --status-completed-bg: #e3f1e7;
  27 + --status-failed: #b4453a;
  28 + --status-failed-bg: #f7e4e1;
  29 + --shadow: 0 1px 2px rgba(28,33,29,0.04), 0 8px 24px -12px rgba(28,33,29,0.12);
  30 + }
  31 +
  32 + @media (prefers-color-scheme: dark) {
  33 + :root:not([data-theme="light"]) {
  34 + --paper: #14170f;
  35 + --paper-raised: #1b1f16;
  36 + --ink: #eef0e6;
  37 + --ink-soft: #b7bfae;
  38 + --ink-faint: #808875;
  39 + --line: #333a2b;
  40 + --line-soft: #262b1f;
  41 + --accent: #b8cf3f;
  42 + --accent-ink: #14170f;
  43 + --accent-soft: #232a17;
  44 + --code-bg: #0e100c;
  45 + --code-ink: #dfe3d3;
  46 + --code-line: #2a2f24;
  47 + --status-queued: #8fb0d6;
  48 + --status-queued-bg: #1c2733;
  49 + --status-running: #e0b25a;
  50 + --status-running-bg: #332a17;
  51 + --status-completed: #7fcb9c;
  52 + --status-completed-bg: #17301f;
  53 + --status-failed: #e0847a;
  54 + --status-failed-bg: #331d1a;
  55 + --shadow: 0 1px 2px rgba(0,0,0,0.3), 0 12px 32px -16px rgba(0,0,0,0.6);
  56 + }
  57 + }
  58 +
  59 + :root[data-theme="dark"] {
  60 + --paper: #14170f;
  61 + --paper-raised: #1b1f16;
  62 + --ink: #eef0e6;
  63 + --ink-soft: #b7bfae;
  64 + --ink-faint: #808875;
  65 + --line: #333a2b;
  66 + --line-soft: #262b1f;
  67 + --accent: #b8cf3f;
  68 + --accent-ink: #14170f;
  69 + --accent-soft: #232a17;
  70 + --code-bg: #0e100c;
  71 + --code-ink: #dfe3d3;
  72 + --code-line: #2a2f24;
  73 + --status-queued: #8fb0d6;
  74 + --status-queued-bg: #1c2733;
  75 + --status-running: #e0b25a;
  76 + --status-running-bg: #332a17;
  77 + --status-completed: #7fcb9c;
  78 + --status-completed-bg: #17301f;
  79 + --status-failed: #e0847a;
  80 + --status-failed-bg: #331d1a;
  81 + --shadow: 0 1px 2px rgba(0,0,0,0.3), 0 12px 32px -16px rgba(0,0,0,0.6);
  82 + }
  83 +
  84 + * { box-sizing: border-box; }
  85 + html { scroll-behavior: smooth; }
  86 + @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }
  87 +
  88 + body {
  89 + margin: 0;
  90 + background: var(--paper);
  91 + color: var(--ink);
  92 + font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, sans-serif;
  93 + font-size: 16px;
  94 + line-height: 1.6;
  95 + -webkit-font-smoothing: antialiased;
  96 + }
  97 +
  98 + code, .mono, .badge, .endpoint, .tabular { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
  99 +
  100 + h1, h2, h3 { font-family: 'Bricolage Grotesque', 'IBM Plex Sans', sans-serif; text-wrap: balance; color: var(--ink); letter-spacing: -0.01em; }
  101 +
  102 + a { color: var(--accent); }
  103 + a:focus-visible, button:focus-visible, summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
  104 +
  105 + .shell { display: grid; grid-template-columns: 236px minmax(0, 1fr); gap: 0; max-width: 1180px; margin: 0 auto; }
  106 +
  107 + /* ── Sidebar ── */
  108 + nav.toc {
  109 + position: sticky; top: 0; align-self: start; height: 100vh; overflow-y: auto;
  110 + padding: 40px 20px 40px 32px; border-right: 1px solid var(--line-soft);
  111 + }
  112 + .brand { display: flex; align-items: center; gap: 9px; margin-bottom: 30px; }
  113 + .brand-mark { width: 26px; height: 26px; border-radius: 7px; background: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 14px; flex: none; }
  114 + .brand-text { font-family: 'Bricolage Grotesque', sans-serif; font-weight: 700; font-size: 15px; letter-spacing: -0.01em; }
  115 + .brand-sub { font-size: 11px; color: var(--ink-faint); letter-spacing: 0.04em; text-transform: uppercase; margin-top: 1px; }
  116 +
  117 + nav.toc ol { list-style: none; margin: 0; padding: 0; counter-reset: toc; }
  118 + nav.toc li { counter-increment: toc; margin: 0; }
  119 + nav.toc a {
  120 + display: flex; gap: 9px; align-items: baseline; padding: 6px 8px; margin: 0 -8px; border-radius: 6px;
  121 + color: var(--ink-soft); text-decoration: none; font-size: 13.5px; transition: background 0.12s, color 0.12s;
  122 + }
  123 + nav.toc a:hover { background: var(--line-soft); color: var(--ink); }
  124 + nav.toc a::before { content: counter(toc, decimal-leading-zero); font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--ink-faint); flex: none; }
  125 +
  126 + .toc-foot { margin-top: 28px; padding-top: 18px; border-top: 1px solid var(--line-soft); font-size: 12px; color: var(--ink-faint); }
  127 + .toc-foot .base-url { display: block; margin-top: 6px; font-family: 'IBM Plex Mono', monospace; font-size: 11.5px; color: var(--ink-soft); word-break: break-all; }
  128 +
  129 + /* ── Main ── */
  130 + main { padding: 56px 48px 120px; min-width: 0; }
  131 + .eyebrow { font-family: 'IBM Plex Mono', monospace; font-size: 12px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--accent); margin: 0 0 14px; }
  132 + h1.title { font-size: clamp(30px, 4vw, 40px); margin: 0 0 14px; line-height: 1.08; }
  133 + .lede { font-size: 17px; color: var(--ink-soft); max-width: 62ch; margin: 0 0 40px; }
  134 +
  135 + /* pipeline diagram */
  136 + .pipeline { display: flex; align-items: stretch; gap: 0; margin: 0 0 56px; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--paper-raised); box-shadow: var(--shadow); }
  137 + .pipe-step { flex: 1; padding: 18px 16px; position: relative; min-width: 0; }
  138 + .pipe-step + .pipe-step { border-left: 1px solid var(--line); }
  139 + .pipe-label { font-family: 'IBM Plex Mono', monospace; font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 6px; }
  140 + .pipe-verb { font-size: 14px; font-weight: 600; }
  141 + .pipe-step[data-state="request"] .pipe-verb { color: var(--ink); }
  142 + .pipe-step[data-state="queued"] { background: var(--status-queued-bg); }
  143 + .pipe-step[data-state="queued"] .pipe-verb { color: var(--status-queued); }
  144 + .pipe-step[data-state="running"] { background: var(--status-running-bg); }
  145 + .pipe-step[data-state="running"] .pipe-verb { color: var(--status-running); }
  146 + .pipe-step[data-state="completed"] { background: var(--status-completed-bg); }
  147 + .pipe-step[data-state="completed"] .pipe-verb { color: var(--status-completed); }
  148 +
  149 + section { margin: 0 0 64px; scroll-margin-top: 24px; }
  150 + section h2 { font-size: 23px; margin: 0 0 6px; display: flex; align-items: center; gap: 10px; }
  151 + .h2-num { font-family: 'IBM Plex Mono', monospace; font-size: 14px; color: var(--accent); font-weight: 500; }
  152 + .section-dek { color: var(--ink-soft); max-width: 66ch; margin: 0 0 26px; font-size: 15px; }
  153 + h3 { font-size: 16.5px; margin: 30px 0 10px; font-weight: 600; }
  154 + p { max-width: 68ch; }
  155 + ul, ol { max-width: 68ch; padding-left: 22px; }
  156 + li { margin: 4px 0; }
  157 +
  158 + /* endpoint card */
  159 + .endpoint-card { border: 1px solid var(--line); border-radius: 10px; background: var(--paper-raised); box-shadow: var(--shadow); margin: 18px 0 22px; overflow: hidden; }
  160 + .endpoint-head { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--line-soft); flex-wrap: wrap; }
  161 + .verb { font-family: 'IBM Plex Mono', monospace; font-size: 12px; font-weight: 600; padding: 3px 8px; border-radius: 5px; background: var(--accent); color: var(--accent-ink); letter-spacing: 0.03em; }
  162 + .verb.get { background: var(--status-queued); color: var(--accent-ink); }
  163 + .endpoint { font-size: 13.5px; color: var(--ink); overflow-x: auto; white-space: nowrap; }
  164 + .endpoint-body { padding: 16px 18px 18px; }
  165 + .endpoint-note { font-size: 13.5px; color: var(--ink-soft); margin: 0 0 12px; }
  166 +
  167 + /* two-up compare */
  168 + .compare { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 18px 0 26px; }
  169 + @media (max-width: 720px) { .compare { grid-template-columns: 1fr; } }
  170 + .compare-card { border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px; background: var(--paper-raised); }
  171 + .compare-card h4 { margin: 0 0 4px; font-size: 14px; font-family: 'IBM Plex Mono', monospace; color: var(--accent); }
  172 + .compare-card p { margin: 0; font-size: 13.5px; color: var(--ink-soft); }
  173 + .compare-card .who { font-size: 12.5px; color: var(--ink-faint); margin-top: 8px; }
  174 +
  175 + table { width: 100%; border-collapse: collapse; font-size: 13.5px; margin: 6px 0 20px; }
  176 + .table-wrap { overflow-x: auto; }
  177 + th, td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
  178 + th { font-family: 'IBM Plex Mono', monospace; font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-faint); font-weight: 500; }
  179 + td code { font-size: 12.5px; background: var(--accent-soft); padding: 1px 5px; border-radius: 4px; }
  180 + .req { color: var(--accent); font-weight: 600; font-size: 12px; }
  181 + .opt { color: var(--ink-faint); font-size: 12px; }
  182 +
  183 + /* code blocks */
  184 + .code-block { position: relative; background: var(--code-bg); color: var(--code-ink); border-radius: 10px; margin: 0 0 20px; box-shadow: var(--shadow); }
  185 + .code-block .code-label { display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; border-bottom: 1px solid var(--code-line); font-family: 'IBM Plex Mono', monospace; font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; color: #9fa693; }
  186 + .code-block pre { margin: 0; padding: 16px 18px 18px; overflow-x: auto; font-size: 13px; line-height: 1.6; }
  187 + .copy-btn { font-family: 'IBM Plex Sans', sans-serif; font-size: 11.5px; background: transparent; border: 1px solid var(--code-line); color: #9fa693; padding: 3px 9px; border-radius: 5px; cursor: pointer; transition: border-color 0.15s, color 0.15s; }
  188 + .copy-btn:hover { border-color: var(--accent); color: var(--accent); }
  189 + .copy-btn.copied { border-color: var(--status-completed); color: var(--status-completed); }
  190 +
  191 + .tok-key { color: #8fd6c9; }
  192 + .tok-str { color: #d7c98a; }
  193 + .tok-num { color: #e0a26f; }
  194 + .tok-bool { color: #b8a0e0; }
  195 + .tok-comment { color: #6f7965; font-style: italic; }
  196 + .tok-verb { color: #9fc773; }
  197 +
  198 + /* status badges */
  199 + .badge { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; padding: 3px 10px; border-radius: 100px; }
  200 + .badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
  201 + .badge.queued { background: var(--status-queued-bg); color: var(--status-queued); }
  202 + .badge.running { background: var(--status-running-bg); color: var(--status-running); }
  203 + .badge.completed { background: var(--status-completed-bg); color: var(--status-completed); }
  204 + .badge.failed { background: var(--status-failed-bg); color: var(--status-failed); }
  205 +
  206 + .status-grid { display: grid; grid-template-columns: auto 1fr; gap: 10px 18px; margin: 18px 0 26px; align-items: baseline; }
  207 + .status-grid dt { grid-column: 1; }
  208 + .status-grid dd { grid-column: 2; margin: 0; color: var(--ink-soft); font-size: 14px; max-width: 56ch; }
  209 +
  210 + .callout { border: 1px solid var(--line); border-left: 3px solid var(--accent); background: var(--accent-soft); border-radius: 0 8px 8px 0; padding: 14px 18px; margin: 20px 0; font-size: 14px; color: var(--ink); max-width: 68ch; }
  211 + .callout strong { color: var(--ink); }
  212 +
  213 + .worked-example { border: 1px solid var(--line); border-radius: 12px; background: var(--paper-raised); box-shadow: var(--shadow); padding: 24px 26px 28px; }
  214 + .we-step { display: flex; gap: 16px; padding: 16px 0; }
  215 + .we-step + .we-step { border-top: 1px solid var(--line-soft); }
  216 + .we-num { font-family: 'IBM Plex Mono', monospace; font-size: 12px; color: var(--accent); font-weight: 600; width: 20px; flex: none; padding-top: 2px; }
  217 + .we-body { min-width: 0; flex: 1; }
  218 + .we-body h4 { margin: 0 0 6px; font-size: 14.5px; }
  219 + .we-body .code-block { margin: 10px 0 0; }
  220 +
  221 + footer.page-foot { border-top: 1px solid var(--line-soft); margin-top: 40px; padding-top: 22px; font-size: 13px; color: var(--ink-faint); }
  222 +
  223 + ::selection { background: var(--accent); color: var(--accent-ink); }
  224 +
  225 + @media (max-width: 860px) {
  226 + .shell { grid-template-columns: 1fr; }
  227 + nav.toc { position: static; height: auto; border-right: none; border-bottom: 1px solid var(--line-soft); padding: 24px 24px 20px; }
  228 + main { padding: 36px 22px 90px; }
  229 + }
  230 +</style>
  231 +
  232 +<div class="shell">
  233 + <nav class="toc" aria-label="目录">
  234 + <div class="brand">
  235 + <span class="brand-mark" aria-hidden="true">🍋</span>
  236 + <div>
  237 + <div class="brand-text">LemonBoss</div>
  238 + <div class="brand-sub">Open API</div>
  239 + </div>
  240 + </div>
  241 + <ol>
  242 + <li><a href="#overview">总览</a></li>
  243 + <li><a href="#auth">身份验证</a></li>
  244 + <li><a href="#endpoints">两种调用形态</a></li>
  245 + <li><a href="#invoke">发起调用</a></li>
  246 + <li><a href="#poll">轮询结果</a></li>
  247 + <li><a href="#status">任务状态参考</a></li>
  248 + <li><a href="#memory">携带上下文</a></li>
  249 + <li><a href="#example">完整示例</a></li>
  250 + <li><a href="#errors">错误参考</a></li>
  251 + </ol>
  252 + <div class="toc-foot">
  253 + Base URL
  254 + <span class="base-url">https://lemonboss.wudaima.com/api/open</span>
  255 + </div>
  256 + </nav>
  257 +
  258 + <main>
  259 + <section id="overview">
  260 + <p class="eyebrow">Agent Invocation · v1</p>
  261 + <h1 class="title">把任意 System Agent<br>变成一个可调用的接口</h1>
  262 + <p class="lede">LemonBoss 上的每一个 System Agent —— 无论是挂在某家公司名下,还是像 <code>h3-video-prompt</code> 这样的全局 specialist —— 都可以被外部系统当作一个异步任务接口来调用:传参、拿任务号、轮询结果。跟调用一个 LLM API 的心智模型一致,只是执行体是一个会思考、会用工具的 Agent,单次任务通常要跑几十秒到几分钟。</p>
  263 +
  264 + <div class="pipeline" role="img" aria-label="调用流程:发起请求,进入队列,Agent 执行中,任务完成">
  265 + <div class="pipe-step" data-state="request">
  266 + <div class="pipe-label">Step 1</div>
  267 + <div class="pipe-verb">POST /invoke</div>
  268 + </div>
  269 + <div class="pipe-step" data-state="queued">
  270 + <div class="pipe-label">Step 2 · queued</div>
  271 + <div class="pipe-verb">拿到 jobId</div>
  272 + </div>
  273 + <div class="pipe-step" data-state="running">
  274 + <div class="pipe-label">Step 3 · running</div>
  275 + <div class="pipe-verb">Agent 执行中</div>
  276 + </div>
  277 + <div class="pipe-step" data-state="completed">
  278 + <div class="pipe-label">Step 4 · completed</div>
  279 + <div class="pipe-verb">GET 轮询拿结果</div>
  280 + </div>
  281 + </div>
  282 + </section>
  283 +
  284 + <section id="auth">
  285 + <h2><span class="h2-num">01</span> 身份验证</h2>
  286 + <p class="section-dek">每一次请求都要在 Header 里带上 API Key —— 系统级 Key(<code>OPEN_API_KEYS</code> 配置)或某个用户账号自己的 Key,两者等效。</p>
  287 + <div class="table-wrap">
  288 + <table>
  289 + <thead><tr><th>Header</th><th>格式</th><th>备注</th></tr></thead>
  290 + <tbody>
  291 + <tr><td><code>Authorization</code></td><td><code>Bearer &lt;API_KEY&gt;</code></td><td>推荐写法</td></tr>
  292 + <tr><td><code>X-API-Key</code></td><td><code>&lt;API_KEY&gt;</code></td><td>等效的备选写法</td></tr>
  293 + </tbody>
  294 + </table>
  295 + </div>
  296 + <p style="font-size:14px; color:var(--ink-soft); margin-top:-8px;">Key 不对或没带 → <code>401 Unauthorized</code>。</p>
  297 + </section>
  298 +
  299 + <section id="endpoints">
  300 + <h2><span class="h2-num">02</span> 两种调用形态</h2>
  301 + <p class="section-dek">选哪一条,只取决于目标 Agent 挂不挂在某家公司名下。请求体、响应结构、轮询方式两者完全一致,区别只在地址和归属校验。</p>
  302 + <div class="compare">
  303 + <div class="compare-card">
  304 + <h4>公司级 Agent</h4>
  305 + <p><code>POST /companies/:companyId/agents/:agentId/invoke</code></p>
  306 + <p class="who">这家公司必须已经雇过这个 Agent(否则 404)。</p>
  307 + </div>
  308 + <div class="compare-card">
  309 + <h4>全局 Agent</h4>
  310 + <p><code>POST /agents/:agentId/invoke</code></p>
  311 + <p class="who">不挂公司的系统级 specialist(如 <code>h3-video-prompt</code>)。只查 Agent 本体是否存在,不查雇佣关系。<code>agentId</code> 不能带 <code>companyId:</code> 前缀,带了会 400。</p>
  312 + </div>
  313 + </div>
  314 + </section>
  315 +
  316 + <section id="invoke">
  317 + <h2><span class="h2-num">03</span> 发起调用</h2>
  318 + <p class="section-dek">POST 一次任务描述,接口立刻返回一个 <code>jobId</code>,不会等 Agent 跑完再响应。</p>
  319 +
  320 + <div class="endpoint-card">
  321 + <div class="endpoint-head">
  322 + <span class="verb">POST</span>
  323 + <span class="endpoint">/api/open/agents/:agentId/invoke</span>
  324 + </div>
  325 + <div class="endpoint-body">
  326 + <div class="table-wrap">
  327 + <table>
  328 + <thead><tr><th>字段</th><th>类型</th><th></th><th>说明</th></tr></thead>
  329 + <tbody>
  330 + <tr><td><code>task</code></td><td class="mono">string</td><td class="req">必填</td><td>用自然语言描述这次要 Agent 做什么。</td></tr>
  331 + <tr><td><code>contextFiles</code></td><td class="mono">array</td><td class="opt">选填</td><td><code>{name, content}</code> 对象数组,作为附件喂给 Agent(见「携带上下文」)。</td></tr>
  332 + <tr><td><code>webhookUrl</code></td><td class="mono">string</td><td class="opt">选填</td><td>任务终态时主动 POST 结果到这个地址,免去轮询。</td></tr>
  333 + </tbody>
  334 + </table>
  335 + </div>
  336 +
  337 + <div class="code-block">
  338 + <div class="code-label"><span>cURL</span><button class="copy-btn" data-copy="invoke">复制</button></div>
  339 + <pre id="invoke"><span class="tok-comment"># 全局 Agent —— 不需要先建公司、不需要先雇佣</span>
  340 +curl -X POST https://lemonboss.wudaima.com/api/open/agents/h3-video-prompt/invoke \
  341 + -H <span class="tok-str">"Authorization: Bearer sk-your-key"</span> \
  342 + -H <span class="tok-str">"Content-Type: application/json"</span> \
  343 + -d <span class="tok-str">'{
  344 + "task": "写一条 5 秒产品特写镜头的提示词,主体是一杯冒热气的咖啡"
  345 + }'</span></pre>
  346 + </div>
  347 +
  348 + <div class="code-block">
  349 + <div class="code-label"><span>Response · 202 Accepted</span><button class="copy-btn" data-copy="invoke-res">复制</button></div>
  350 + <pre id="invoke-res">{
  351 + <span class="tok-key">"jobId"</span>: <span class="tok-str">"job-1787655618610-e367c068"</span>,
  352 + <span class="tok-key">"status"</span>: <span class="tok-str">"queued"</span>,
  353 + <span class="tok-key">"message"</span>: <span class="tok-str">"Agent invocation job queued. Poll GET /api/open/jobs/job-... for status."</span>
  354 +}</pre>
  355 + </div>
  356 + <p class="endpoint-note">把 <code>jobId</code> 存下来——下一步全靠它。</p>
  357 + </div>
  358 + </div>
  359 + </section>
  360 +
  361 + <section id="poll">
  362 + <h2><span class="h2-num">04</span> 轮询结果</h2>
  363 + <p class="section-dek">建议每 3~5 秒查一次,直到 <code>status</code> 变成终态(<code>completed</code> 或 <code>failed</code>)。</p>
  364 +
  365 + <div class="endpoint-card">
  366 + <div class="endpoint-head">
  367 + <span class="verb get">GET</span>
  368 + <span class="endpoint">/api/open/jobs/:jobId</span>
  369 + </div>
  370 + <div class="endpoint-body">
  371 + <div class="code-block">
  372 + <div class="code-label"><span>cURL</span><button class="copy-btn" data-copy="poll">复制</button></div>
  373 + <pre id="poll">curl https://lemonboss.wudaima.com/api/open/jobs/job-1787655618610-e367c068 \
  374 + -H <span class="tok-str">"Authorization: Bearer sk-your-key"</span></pre>
  375 + </div>
  376 +
  377 + <div class="code-block">
  378 + <div class="code-label"><span>Response · completed</span><button class="copy-btn" data-copy="poll-res">复制</button></div>
  379 + <pre id="poll-res">{
  380 + <span class="tok-key">"jobId"</span>: <span class="tok-str">"job-1787655618610-e367c068"</span>,
  381 + <span class="tok-key">"status"</span>: <span class="tok-str">"completed"</span>,
  382 + <span class="tok-key">"progress"</span>: <span class="tok-str">"Invocation completed"</span>,
  383 + <span class="tok-key">"result"</span>: {
  384 + <span class="tok-key">"output"</span>: <span class="tok-str">"你好,我是 H3视频提示词专家 🎬 —— 专为 MiniMax H3……"</span>
  385 + }
  386 +}</pre>
  387 + </div>
  388 + </div>
  389 + </div>
  390 + </section>
  391 +
  392 + <section id="status">
  393 + <h2><span class="h2-num">05</span> 任务状态参考</h2>
  394 + <p class="section-dek">轮询时判断这四个 <code>status</code> 取值:</p>
  395 + <dl class="status-grid">
  396 + <dt><span class="badge queued">queued</span></dt>
  397 + <dd>已接收,正在排队等待执行槽位。</dd>
  398 + <dt><span class="badge running">running</span></dt>
  399 + <dd>Agent 正在思考/执行,<code>progress</code> 字段会给出简短进展描述。</dd>
  400 + <dt><span class="badge completed">completed</span></dt>
  401 + <dd>已完成,<code>result.output</code> 里是最终文本结果。</dd>
  402 + <dt><span class="badge failed">failed</span></dt>
  403 + <dd>执行失败,看 <code>error</code> 字段定位原因。</dd>
  404 + </dl>
  405 + </section>
  406 +
  407 + <section id="memory">
  408 + <h2><span class="h2-num">06</span> 携带上下文</h2>
  409 + <p class="section-dek">Agent 不会跨调用自动记住上一次说过什么。想让它接着上次的结果继续干活,把上一轮的输出通过 <code>contextFiles</code> 传回来。</p>
  410 + <div class="code-block">
  411 + <div class="code-label"><span>下一轮调用的 Body</span><button class="copy-btn" data-copy="memory">复制</button></div>
  412 + <pre id="memory">{
  413 + <span class="tok-key">"task"</span>: <span class="tok-str">"基于昨天的日志总结,开展新一轮数据清理"</span>,
  414 + <span class="tok-key">"contextFiles"</span>: [
  415 + { <span class="tok-key">"name"</span>: <span class="tok-str">"logs-yesterday.txt"</span>, <span class="tok-key">"content"</span>: <span class="tok-str">"……昨日任务输出……"</span> },
  416 + { <span class="tok-key">"name"</span>: <span class="tok-str">"data.csv"</span>, <span class="tok-key">"content"</span>: <span class="tok-str">"id,name\n1,apple"</span> }
  417 + ]
  418 +}</pre>
  419 + </div>
  420 + <div class="callout"><strong>外部系统充当记忆保管员</strong> —— 拿到 <code>result.output</code> 后自己存起来,下次调用再喂回去。文件按文本落地,支持上万字,这样每个 Agent 都能被当成可控、随时插拔的函数来编排。</div>
  421 + </section>
  422 +
  423 + <section id="example">
  424 + <h2><span class="h2-num">07</span> 完整示例</h2>
  425 + <p class="section-dek">下面这次调用真实跑过:从发起请求到拿到结果,全程 5 秒。</p>
  426 + <div class="worked-example">
  427 + <div class="we-step">
  428 + <div class="we-num">01</div>
  429 + <div class="we-body">
  430 + <h4>发起调用</h4>
  431 + <div class="code-block">
  432 + <div class="code-label"><span>Request</span><button class="copy-btn" data-copy="ex1">复制</button></div>
  433 + <pre id="ex1">curl -X POST https://lemonboss.wudaima.com/api/open/agents/h3-video-prompt/invoke \
  434 + -H <span class="tok-str">"X-API-Key: sk-your-key"</span> -H <span class="tok-str">"Content-Type: application/json"</span> \
  435 + -d <span class="tok-str">'{"task": "用一句话自我介绍一下你是做什么的"}'</span></pre>
  436 + </div>
  437 + </div>
  438 + </div>
  439 + <div class="we-step">
  440 + <div class="we-num">02</div>
  441 + <div class="we-body">
  442 + <h4>立即拿到 jobId <span class="badge queued" style="margin-left:6px;">queued</span></h4>
  443 + <div class="code-block">
  444 + <div class="code-label"><span>Response · 202</span><button class="copy-btn" data-copy="ex2">复制</button></div>
  445 + <pre id="ex2">{ <span class="tok-key">"jobId"</span>: <span class="tok-str">"job-1787655618610-e367c068"</span>, <span class="tok-key">"status"</span>: <span class="tok-str">"queued"</span> }</pre>
  446 + </div>
  447 + </div>
  448 + </div>
  449 + <div class="we-step">
  450 + <div class="we-num">03</div>
  451 + <div class="we-body">
  452 + <h4>约 5 秒后轮询命中 <span class="badge completed" style="margin-left:6px;">completed</span></h4>
  453 + <div class="code-block">
  454 + <div class="code-label"><span>Response · GET /jobs/:jobId</span><button class="copy-btn" data-copy="ex3">复制</button></div>
  455 + <pre id="ex3">{
  456 + <span class="tok-key">"status"</span>: <span class="tok-str">"completed"</span>,
  457 + <span class="tok-key">"result"</span>: {
  458 + <span class="tok-key">"output"</span>: <span class="tok-str">"你好,我是 H3视频提示词专家 🎬 —— 专为 MiniMax H3(海螺)
  459 +视频生成模型撰写高质量结构化提示词,把你的视频创意翻译成
  460 +H3 能一次读懂的英文结构化描述……把你想拍的内容告诉我,
  461 +我就帮你写好成片提示词。"</span>
  462 + }
  463 +}</pre>
  464 + </div>
  465 + </div>
  466 + </div>
  467 + </div>
  468 + </section>
  469 +
  470 + <section id="errors">
  471 + <h2><span class="h2-num">08</span> 错误参考</h2>
  472 + <div class="table-wrap">
  473 + <table>
  474 + <thead><tr><th>状态码</th><th>触发条件</th></tr></thead>
  475 + <tbody>
  476 + <tr><td class="mono">400</td><td>缺 <code>task</code> 字段;或对全局端点传了带 <code>companyId:</code> 前缀的 <code>agentId</code>。</td></tr>
  477 + <tr><td class="mono">401</td><td>API Key 缺失或无效。</td></tr>
  478 + <tr><td class="mono">404</td><td>Agent 不存在;或公司级调用里这家公司没雇过这个 Agent。</td></tr>
  479 + <tr><td class="mono">409</td><td>(仅 <code>/jobs/:jobId/result</code>)任务还没到 <code>completed</code>。</td></tr>
  480 + </tbody>
  481 + </table>
  482 + </div>
  483 + </section>
  484 +
  485 + <footer class="page-foot">
  486 + LemonBoss Open API · Agent Invocation · 内部真相来源见仓库 <code>docs/done/open-api-manual.md</code>
  487 + </footer>
  488 + </main>
  489 +</div>
  490 +
  491 +<script>
  492 + document.querySelectorAll('.copy-btn').forEach(function (btn) {
  493 + btn.addEventListener('click', function () {
  494 + var target = document.getElementById(btn.getAttribute('data-copy'));
  495 + if (!target) return;
  496 + var text = target.innerText;
  497 + var done = function () {
  498 + var original = btn.textContent;
  499 + btn.textContent = '已复制';
  500 + btn.classList.add('copied');
  501 + setTimeout(function () { btn.textContent = original; btn.classList.remove('copied'); }, 1400);
  502 + };
  503 + try {
  504 + if (navigator.clipboard && navigator.clipboard.writeText) {
  505 + navigator.clipboard.writeText(text).then(done).catch(function () { fallbackCopy(text, done); });
  506 + } else {
  507 + fallbackCopy(text, done);
  508 + }
  509 + } catch (e) { fallbackCopy(text, done); }
  510 + });
  511 + });
  512 + function fallbackCopy(text, done) {
  513 + var ta = document.createElement('textarea');
  514 + ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
  515 + document.body.appendChild(ta); ta.select();
  516 + try { document.execCommand('copy'); } catch (e) {}
  517 + document.body.removeChild(ta);
  518 + done();
  519 + }
  520 +</script>
... ...