# 编写脚本 (/docs/cloud/services/automation/guides/write-script)



脚本默认导出一个接收输入的函数；返回值成为运行的 `output`。

执行本页前，先在同一个 Bash 会话完成快速开始第 1–3 步：[设置 `PRAXIS_API_URL` 与 `PRAXIS_API_KEY`](/docs/cloud/services/automation/quickstart#environment)、[定义 `SOURCE` 并临时运行](/docs/cloud/services/automation/quickstart#run-source)、[定义并调用 `read_result` 辅助动作](/docs/cloud/services/automation/quickstart#read-result)。该动作检查结果是否为 42，与本页的加一示例对应。

## 入口与 schema [#入口与-schema]

把下面的内容保存为 `script.ts`：

```typescript
export default async (input: { n: number }): Promise<{ answer: number }> => {
  console.log("Adding one", input.n);
  return { answer: input.n + 1 };
};
```

输入 schema 从参数类型推导；输出 schema 从显式返回类型推导，`Promise` 会展开。支持基本类型、对象、数组、联合类型及可解析的本地类型别名。不能推导的类型会得到空 schema 和警告；不要把空 schema 当成已经验证了字段。省略返回类型时，不应依赖返回表达式自动推导出输出约束。

通过[校验定义](/docs/api/native/automation/validateDefinition)检查实际生成的 schema，再发布：

```bash
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/validate" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source script.ts '{kind:"script",source:$source}')" \
  | jq -e '.valid == true and .input_schema.properties.n.type == "number" and .output_schema.properties.answer.type == "number"'
```

期望：输出 `true`，退出码 0。读取完整校验响应中的 `errors` 与 `warnings` 可以定位不能推导的类型。

## 运行与日志 [#运行与日志]

```bash
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source script.ts \
    '{source:$source,language:"ts",input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
read_result "$RUN_ID"
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs/$RUN_ID/logs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"
```

期望：结果断言为 `true`；日志响应包含 `Adding one 41`。`console.log/info/debug` 记录为 `info`，`console.warn` 和 `console.error` 保留各自级别。日志不是返回值；需要交给调用方的数据必须 `return`。可通过[日志接口](/docs/api/native/automation/listRunLogs)读取，或使用 `Accept: text/event-stream` 订阅。

## 全局 praxis [#全局-praxis]

运行时已经提供 `praxis`，无需 import、API Key 或安装包。它提供以下 SDK 入口；方法参数采用参考页中的 `path`、`query`、`body` 形状，例如 `praxis.automation.runs.create({body:{target,input}})`。能否调用仍取决于发起者权限。

| 入口                    | 能力参考                                          |
| --------------------- | --------------------------------------------- |
| `praxis.agent`        | [Agent](/docs/api/native/agent)               |
| `praxis.model`        | [Model](/docs/api/native/model)               |
| `praxis.file`         | [File](/docs/api/native/file)                 |
| `praxis.sandbox`      | [Sandbox](/docs/api/native/sandbox)           |
| `praxis.search`       | [Search](/docs/api/native/search)             |
| `praxis.database`     | [Database](/docs/api/native/database)         |
| `praxis.automation`   | [Automation](/docs/api/native/automation)     |
| `praxis.organization` | [Organization](/docs/api/native/organization) |
| `praxis.meta`         | [Meta](/docs/api/native/meta)                 |

脚本不能直接访问外部网络。用平台提供的能力完成操作，并 `await` 调用以取得结果及处理异常。

## 调用平台内置动作 [#调用平台内置动作]

`call("命名空间/名字", 输入)` 接收 Action 引用和输入对象，返回 Action 的输出。它是当前运行里的一个步骤：有自己的输入、输出，并在[调用记录](/docs/api/native/automation/listRunCalls)里占一条，不会产生子运行。`praxis` 是平台命名空间；省略命名空间的裸名也只解析到平台 Action。调用总使用 Action 的当前修订，暂不支持 `@版本`；不再接受 `命名空间::名字` 写法。

调用失败时抛出错误，带 `code`、`message`、`hint`、调用记录序号 `seq` 和 `request_id`。多个 `call` 可以用 `Promise.all` 并发。`praxis.*` 只表示平台 SDK，`call(...)` 和 `praxis.ocr(...)` 这类写法已删除，运行时会报错并提示改用 `call`。

把下面内容保存为 `chat.ts`；它调用 `praxis/chat`。模型由动作内置并由维护者选择，调用时不选模型；`praxis/ocr`、`praxis/image`、`praxis/embed` 也一样。旧 `model` 字段已废弃，传入的值会被忽略。此调用会产生模型费用：

```typescript
export default async (input: { prompt: string }) => {
  return await call("praxis/chat", {
    messages: [{ role: "user", content: input.prompt }],
    stream: false,
  });
};
```

```bash
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source chat.ts \
    '{source:$source,language:"ts",input:{prompt:"Say hello in one sentence."},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs/$RUN_ID" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" | jq '{id,status,output,error}'
```

期望：终态为 `completed`，`output` 为模型结果，`error` 为 null。若仍是 `pending` 或 `running`，重复最后一条 GET，直到终态；`faulted` 时读取错误，不能把 HTTP 成功当成模型成功。动作输入输出的完整约定可用[读取动作](/docs/api/native/automation/getAction)查看 `praxis/chat`。

## 调用自建动作 [#调用自建动作]

以下示例[创建动作](/docs/api/native/automation/createAction)，再用它的完整名字调用。生成的脚本源码中引用是字符串字面量，便于发布前校验。

```bash
ACTION_NAME="add-one-$(date +%s)-$RANDOM"
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/actions" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg name "$ACTION_NAME" '{
    namespace:"docs-samples",name:$name,
    input_schema:{type:"object",properties:{n:{type:"number"}},required:["n"],additionalProperties:false},
    output_schema:{type:"object",properties:{answer:{type:"number"}},required:["answer"],additionalProperties:false},
    implementation:{kind:"code",language:"ts",source:"export default async (input: { n: number }) => ({ answer: input.n + 1 });"}
  }')" | jq -e '.id | startswith("act_")'
SOURCE="export default async (input: { n: number }) => await call(\"docs-samples/$ACTION_NAME\", input);"
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg source "$SOURCE" \
    '{source:$source,language:"ts",input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
read_result "$RUN_ID"
```

期望：动作创建与运行结果均输出 `true`，结果为 42。用完后可[归档动作](/docs/api/native/automation/archiveAction)。动作可复用，但不能在动作内部再编排其他动作、脚本或工作流。

## 调用另一个脚本或工作流 [#调用另一个脚本或工作流]

`invoke("命名空间/名字@版本", 输入)` 把另一个已发布的脚本或工作流作为**子运行**启动，等它结束后返回子运行的输出；省略 `@版本` 用最新版。和 `call` 的区别：`call` 是当前运行里的一个步骤，不产生子运行；`invoke` 产生一个有自己 ID、状态和日志的子运行，并在[调用记录](/docs/api/native/automation/listRunCalls)里用 `child_run_id` 指向它。

```typescript
export default async (input: { urls: string[] }) => {
  // 多个 invoke 用 Promise.all 并发；子运行经队列启动，不受单次请求链长度限制。
  const pages = await Promise.all(input.urls.map(url => invoke("team/fetch-page", { url })));
  return { pages };
};
```

* **等待有上限**：`invoke` 同步等待子运行结束，等待时间受父运行 `deadline` 限制。到期时抛出 `INVOKE_TIMEOUT`，消息里带子运行 ID；子运行不会因此被取消，可以用该 ID 另行读取结果。需要等很久的子运行，请在工作流里用 `run:` 步骤——工作流在等待期间不占用计算。
* **失败**：子运行失败、被取消或无法创建时抛出错误，`code` 是子运行的错误码，`message` 带子运行 ID。目标不存在或已归档时立即失败，不会排队。
* **取消与超时**：父运行被取消或失败时，仍在等待中的子运行会一起被取消。
* **查看子运行**：[列出运行](/docs/api/native/automation/listRuns)时用 `parent_run_id` 筛选，得到每个子运行的 ID、目标、状态和耗时。
* **限制**：每个运行最多 100 个子运行，同时执行的最多 20 个，多出的排队等待；嵌套最多 10 层，不允许自己调用自己。

脚本不能自行指定独立运行（detach）的子运行；需要“启动后不等结果”时，调用 [创建运行](/docs/api/native/automation/createRun)。

## 返回值和时间预算 [#返回值和时间预算]

返回 JSON 可序列化的值；不要返回 `undefined`、BigInt 或循环引用。序列化后的 UTF-8 返回值最多 1 MiB，超过会使运行失败。大结果应保存到文件等存储中，返回引用。

脚本墙钟时间默认最多 5 分钟；CPU 时间最多 30 秒，并受剩余 deadline 进一步约束。等待平台调用与纯计算的耗时不同，增加等待时长不能解决死循环。全部数值见[限制与错误](/docs/cloud/services/automation/limits-and-errors)。

## 有依据的错误与修法 [#有依据的错误与修法]

| code                 | 报错原文                              | 修法                                |
| -------------------- | --------------------------------- | --------------------------------- |
| `SCRIPT_ERROR`       | `脚本执行出错`                          | 阅读 detail 中的原始异常及用户代码行号，修正后创建新运行。 |
| `CPU_LIMIT_EXCEEDED` | `脚本 CPU 时间超出上限`                   | 终止无限循环，缩小每批纯计算量。                  |
| `PAYLOAD_TOO_LARGE`  | `Payload exceeds 1 MiB`           | 缩小 JSON 返回值，返回存储引用。               |
| `INPUT_INVALID`      | `Value must be JSON serializable` | 返回明确的 JSON 值，如对象或 null。           |

运行失败看 `Run.error`；创建请求被拒绝看 HTTP problem 响应。两者不能混用。

[API 参考 →](/docs/api/native/automation) · [Cookbook →](/docs/cookbook)
